1use std::{future::Future, pin::Pin, sync::Arc};
5
6use schemars::JsonSchema;
7use serde::{Deserialize, de::IgnoredAny};
8use serde_json::{Map, Value};
9
10use crate::{
11 error::ActionError,
12 snapshot::Outcome,
13 ui::{OnUi, Ui},
14};
15
16#[derive(Debug, Default, Clone, Copy)]
19pub struct NoArgs;
20
21impl<'de> Deserialize<'de> for NoArgs {
22 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
23 where
24 D: serde::Deserializer<'de>,
25 {
26 IgnoredAny::deserialize(deserializer)?;
27 Ok(NoArgs)
28 }
29}
30
31#[derive(Debug, Clone, Default)]
33pub(crate) struct Annotations {
34 pub read_only: Option<bool>,
35 pub destructive: Option<bool>,
36 pub idempotent: Option<bool>,
37 pub open_world: Option<bool>,
38}
39
40impl Annotations {
41 pub(crate) fn is_empty(&self) -> bool {
42 self.read_only.is_none()
43 && self.destructive.is_none()
44 && self.idempotent.is_none()
45 && self.open_world.is_none()
46 }
47}
48
49pub(crate) type HandlerFuture = Pin<Box<dyn Future<Output = Result<Outcome, ActionError>> + Send>>;
50pub(crate) type BoxedHandler = Box<dyn Fn(Value) -> HandlerFuture + Send + Sync>;
51
52pub(crate) struct ActionSpec {
57 pub title: String,
58 pub description: Option<String>,
59 pub annotations: Annotations,
60 pub input_schema: Arc<Map<String, Value>>,
61 pub output_schema: Arc<Map<String, Value>>,
62 pub available: bool,
63 pub handler: BoxedHandler,
64}
65
66fn deserialize_params<P>(params: Value) -> Result<P, ActionError>
67where
68 P: for<'de> Deserialize<'de>,
69{
70 serde_json::from_value(params).map_err(|e| ActionError::field("_params", e.to_string()))
71}
72
73#[doc(hidden)]
76pub struct RegisteredHandler(pub(crate) BoxedHandler);
77
78pub trait IntoActionHandler<Marker, P, Cx> {
85 #[doc(hidden)]
88 fn into_handler(self, ui: Ui<Cx>) -> RegisteredHandler;
89}
90
91#[doc(hidden)]
93pub struct AsyncMarker;
94
95#[doc(hidden)]
98pub struct AsyncBareMarker;
99
100#[doc(hidden)]
103pub struct UiMarker;
104
105#[doc(hidden)]
108pub struct UiCtxMarker;
109
110#[doc(hidden)]
113pub struct UiCtxOnlyMarker;
114
115#[doc(hidden)]
118pub struct UiBareMarker;
119
120impl<P, Cx, F, Fut, O> IntoActionHandler<AsyncMarker, P, Cx> for F
121where
122 P: for<'de> Deserialize<'de> + Send + 'static,
123 O: Into<Outcome> + Send + 'static,
124 F: Fn(P) -> Fut + Send + Sync + 'static,
125 Fut: Future<Output = Result<O, ActionError>> + Send + 'static,
126{
127 fn into_handler(self, _ui: Ui<Cx>) -> RegisteredHandler {
128 let f = Arc::new(self);
129 RegisteredHandler(Box::new(move |params| {
130 let f = f.clone();
131 Box::pin(async move {
132 let p = deserialize_params::<P>(params)?;
133 f(p).await.map(Into::into)
134 })
135 }))
136 }
137}
138
139impl<Cx, F, Fut, O> IntoActionHandler<AsyncBareMarker, NoArgs, Cx> for F
142where
143 O: Into<Outcome> + Send + 'static,
144 F: Fn() -> Fut + Send + Sync + 'static,
145 Fut: Future<Output = Result<O, ActionError>> + Send + 'static,
146{
147 fn into_handler(self, _ui: Ui<Cx>) -> RegisteredHandler {
148 let f = Arc::new(self);
149 RegisteredHandler(Box::new(move |_params| {
150 let f = f.clone();
151 Box::pin(async move { f().await.map(Into::into) })
152 }))
153 }
154}
155
156impl<P, Cx, G, O> IntoActionHandler<UiMarker, P, Cx> for OnUi<G>
157where
158 P: for<'de> Deserialize<'de> + Send + 'static,
159 Cx: 'static,
160 O: Into<Outcome> + Send + 'static,
161 G: Fn(P) -> Result<O, ActionError> + Send + Sync + 'static,
162{
163 fn into_handler(self, ui: Ui<Cx>) -> RegisteredHandler {
164 let g = Arc::new(self.0);
165 RegisteredHandler(Box::new(move |params| {
166 let g = g.clone();
167 let ui = ui.clone();
168 Box::pin(async move {
169 let p = deserialize_params::<P>(params)?;
170 ui.run(move |_cx| g(p)).await.map(Into::into)
171 })
172 }))
173 }
174}
175
176impl<P, Cx, G, O> IntoActionHandler<UiCtxMarker, P, Cx> for OnUi<G>
177where
178 P: for<'de> Deserialize<'de> + Send + 'static,
179 Cx: 'static,
180 O: Into<Outcome> + Send + 'static,
181 G: Fn(P, &mut Cx) -> Result<O, ActionError> + Send + Sync + 'static,
182{
183 fn into_handler(self, ui: Ui<Cx>) -> RegisteredHandler {
184 let g = Arc::new(self.0);
185 RegisteredHandler(Box::new(move |params| {
186 let g = g.clone();
187 let ui = ui.clone();
188 Box::pin(async move {
189 let p = deserialize_params::<P>(params)?;
190 ui.run(move |cx: &mut Cx| g(p, cx)).await.map(Into::into)
191 })
192 }))
193 }
194}
195
196impl<Cx, G, O> IntoActionHandler<UiCtxOnlyMarker, NoArgs, Cx> for OnUi<G>
200where
201 Cx: 'static,
202 O: Into<Outcome> + Send + 'static,
203 G: Fn(&mut Cx) -> Result<O, ActionError> + Send + Sync + 'static,
204{
205 fn into_handler(self, ui: Ui<Cx>) -> RegisteredHandler {
206 let g = Arc::new(self.0);
207 RegisteredHandler(Box::new(move |_params| {
208 let g = g.clone();
209 let ui = ui.clone();
210 Box::pin(async move { ui.run(move |cx: &mut Cx| g(cx)).await.map(Into::into) })
211 }))
212 }
213}
214
215impl<Cx, G, O> IntoActionHandler<UiBareMarker, NoArgs, Cx> for OnUi<G>
219where
220 Cx: 'static,
221 O: Into<Outcome> + Send + 'static,
222 G: Fn() -> Result<O, ActionError> + Send + Sync + 'static,
223{
224 fn into_handler(self, ui: Ui<Cx>) -> RegisteredHandler {
225 let g = Arc::new(self.0);
226 RegisteredHandler(Box::new(move |_params| {
227 let g = g.clone();
228 let ui = ui.clone();
229 Box::pin(async move { ui.run(move |_cx: &mut Cx| g()).await.map(Into::into) })
230 }))
231 }
232}
233
234pub(crate) fn schema_object<T>() -> Map<String, Value>
236where
237 T: JsonSchema,
238{
239 match serde_json::to_value(schemars::schema_for!(T)) {
240 Ok(Value::Object(m)) => m,
241 _ => empty_object_schema(),
242 }
243}
244
245pub(crate) fn empty_object_schema() -> Map<String, Value> {
246 let mut m = Map::new();
247 m.insert("type".into(), Value::String("object".into()));
248 m
249}
250
251pub(crate) fn inject_expected_version(schema: &mut Map<String, Value>) {
253 let props = schema
254 .entry("properties")
255 .or_insert_with(|| Value::Object(Map::new()));
256 if let Value::Object(props) = props {
257 props.insert(
258 "expectedVersion".into(),
259 serde_json::json!({
260 "type": "integer",
261 "description": "the window.version this write is based on (optimistic concurrency, §7.1)"
262 }),
263 );
264 }
265 schema
266 .entry("type")
267 .or_insert_with(|| Value::String("object".into()));
268}
269
270pub(crate) fn build_output_schema(result_schema: Option<Map<String, Value>>) -> Map<String, Value> {
273 let mut props = Map::new();
274 props.insert("message".into(), serde_json::json!({ "type": "string" }));
275 props.insert(
276 "openedWindowIds".into(),
277 serde_json::json!({ "type": "array", "items": { "type": "string" } }),
278 );
279 if let Some(rs) = result_schema {
280 props.insert("result".into(), Value::Object(rs));
281 }
282 let mut schema = Map::new();
283 schema.insert("type".into(), Value::String("object".into()));
284 schema.insert("properties".into(), Value::Object(props));
285 schema
286}