roas/v3_0/
callback.rs

1use crate::common::helpers::{Context, ValidateWithContext};
2use crate::v3_0::path_item::PathItem;
3use crate::v3_0::spec::Spec;
4use serde::de::{Error, MapAccess, Visitor};
5use serde::ser::SerializeMap;
6use serde::{Deserialize, Deserializer, Serialize, Serializer};
7use std::collections::BTreeMap;
8use std::fmt;
9
10/// A map of possible out-of band callbacks related to the parent operation.
11/// Each value in the map is a Path Item Object that describes a set of requests
12/// that may be initiated by the API provider and the expected responses.
13/// The key value used to identify the path item object is an expression, evaluated at runtime,
14/// that identifies a URL to use for the callback operation.
15///
16/// Specification example:
17///
18/// ```yaml
19/// onData:
20///   # when data is sent, it will be sent to the `callbackUrl` provided
21///   # when making the subscription PLUS the suffix `/data`
22///   '{$request.query.callbackUrl}/data':
23///     post:
24///       requestBody:
25///         description: subscription payload
26///         content:
27///           application/json:
28///             schema:
29///               type: object
30///               properties:
31///                 timestamp:
32///                   type: string
33///                   format: date-time
34///                 userData:
35///                   type: string
36///       responses:
37///         '202':
38///           description: |
39///             Your server implementation should return this HTTP status code
40///             if the data was received successfully
41///         '204':
42///           description: |
43///             Your server should return this HTTP status code if no longer interested
44///             in further updates
45/// ```
46#[derive(Clone, Debug, PartialEq, Default)]
47pub struct Callback {
48    /// A Path Item Object used to define a callback request and expected responses.
49    pub paths: BTreeMap<String, PathItem>,
50
51    /// This object MAY be extended with Specification Extensions.
52    /// The field name MUST begin with `x-`, for example, `x-internal-id`.
53    /// The value can be null, a primitive, an array or an object.
54    pub extensions: Option<BTreeMap<String, serde_json::Value>>,
55}
56
57impl Serialize for Callback {
58    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
59    where
60        S: Serializer,
61    {
62        let mut len = self.paths.len();
63        if let Some(ext) = &self.extensions {
64            len += ext.len();
65        }
66        let mut map = serializer.serialize_map(Some(len))?;
67
68        for (k, v) in &self.paths {
69            map.serialize_entry(&k, &v)?;
70        }
71
72        if let Some(ext) = &self.extensions {
73            for (k, v) in ext {
74                if k.starts_with("x-") {
75                    map.serialize_entry(&k, &v)?;
76                }
77            }
78        }
79
80        map.end()
81    }
82}
83
84impl<'de> Deserialize<'de> for Callback {
85    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
86    where
87        D: Deserializer<'de>,
88    {
89        const FIELDS: &[&str] = &["<path name>", "x-<ext name>"];
90
91        struct CallbackVisitor;
92
93        impl<'de> Visitor<'de> for CallbackVisitor {
94            type Value = Callback;
95
96            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
97                formatter.write_str("struct Callback")
98            }
99
100            fn visit_map<V>(self, mut map: V) -> Result<Callback, V::Error>
101            where
102                V: MapAccess<'de>,
103            {
104                let mut res = Callback {
105                    paths: BTreeMap::new(),
106                    ..Default::default()
107                };
108                let mut extensions: BTreeMap<String, serde_json::Value> = BTreeMap::new();
109                while let Some(key) = map.next_key::<String>()? {
110                    if key.starts_with("x-") {
111                        if extensions.contains_key(key.as_str()) {
112                            return Err(Error::custom(format_args!("duplicate field `{key}`")));
113                        }
114                        extensions.insert(key, map.next_value()?);
115                    } else {
116                        if res.paths.contains_key(key.as_str()) {
117                            return Err(Error::custom(format_args!("duplicate field `{key}`")));
118                        }
119                        res.paths.insert(key, map.next_value()?);
120                    }
121                }
122                if !extensions.is_empty() {
123                    res.extensions = Some(extensions);
124                }
125                Ok(res)
126            }
127        }
128
129        deserializer.deserialize_struct("Callback", FIELDS, CallbackVisitor)
130    }
131}
132
133impl ValidateWithContext<Spec> for Callback {
134    fn validate_with_context(&self, ctx: &mut Context<Spec>, path: String) {
135        for (name, path_item) in &self.paths {
136            path_item.validate_with_context(ctx, format!("{path}[{name}]"));
137        }
138    }
139}