zng_view_api/
api_extension.rs

1//! API extension types.
2
3use std::{fmt, ops};
4
5use serde::{Deserialize, Serialize};
6use zng_txt::Txt;
7
8/// Custom serialized data, in a format defined by the extension.
9///
10/// Note that the bytes here should represent a serialized small `struct` only, you
11/// can add an [`IpcBytes`] or [`IpcBytesReceiver`] field to this struct to transfer
12/// large payloads.
13///
14/// [`IpcBytes`]: crate::ipc::IpcBytes
15/// [`IpcBytesReceiver`]: crate::ipc::IpcBytesReceiver
16#[derive(Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
17pub struct ApiExtensionPayload(#[serde(with = "serde_bytes")] pub Vec<u8>);
18impl ApiExtensionPayload {
19    /// Serialize the payload.
20    pub fn serialize<T: Serialize>(payload: &T) -> bincode::Result<Self> {
21        bincode::serialize(payload).map(Self)
22    }
23
24    /// Deserialize the payload.
25    pub fn deserialize<T: serde::de::DeserializeOwned>(&self) -> Result<T, ApiExtensionRecvError> {
26        if let Some((id, error)) = self.parse_invalid_request() {
27            Err(ApiExtensionRecvError::InvalidRequest {
28                extension_id: id,
29                error: Txt::from_str(error),
30            })
31        } else if let Some(id) = self.parse_unknown_extension() {
32            Err(ApiExtensionRecvError::UnknownExtension { extension_id: id })
33        } else {
34            bincode::deserialize(&self.0).map_err(ApiExtensionRecvError::Deserialize)
35        }
36    }
37
38    /// Empty payload.
39    pub const fn empty() -> Self {
40        Self(vec![])
41    }
42
43    /// Value returned when an invalid extension is requested.
44    ///
45    /// Value is a string `"zng-view-api.unknown_extension;id={extension_id}"`.
46    pub fn unknown_extension(extension_id: ApiExtensionId) -> Self {
47        Self(format!("zng-view-api.unknown_extension;id={extension_id}").into_bytes())
48    }
49
50    /// Value returned when an invalid request is made for a valid extension key.
51    ///
52    /// Value is a string `"zng-view-api.invalid_request;id={extension_id};error={error}"`.
53    pub fn invalid_request(extension_id: ApiExtensionId, error: impl fmt::Display) -> Self {
54        Self(format!("zng-view-api.invalid_request;id={extension_id};error={error}").into_bytes())
55    }
56
57    /// If the payload is an [`unknown_extension`] error message, returns the key.
58    ///
59    /// if the payload starts with the invalid request header and the key cannot be retrieved the
60    /// [`ApiExtensionId::INVALID`] is returned as the key.
61    ///
62    /// [`unknown_extension`]: Self::unknown_extension
63    pub fn parse_unknown_extension(&self) -> Option<ApiExtensionId> {
64        let p = self.0.strip_prefix(b"zng-view-api.unknown_extension;")?;
65        if let Some(p) = p.strip_prefix(b"id=") {
66            if let Ok(id_str) = std::str::from_utf8(p) {
67                return match id_str.parse::<ApiExtensionId>() {
68                    Ok(id) => Some(id),
69                    Err(id) => Some(id),
70                };
71            }
72        }
73        Some(ApiExtensionId::INVALID)
74    }
75
76    /// If the payload is an [`invalid_request`] error message, returns the key and error.
77    ///
78    /// if the payload starts with the invalid request header and the key cannot be retrieved the
79    /// [`ApiExtensionId::INVALID`] is returned as the key and the error message will mention "corrupted payload".
80    ///
81    /// [`invalid_request`]: Self::invalid_request
82    pub fn parse_invalid_request(&self) -> Option<(ApiExtensionId, &str)> {
83        let p = self.0.strip_prefix(b"zng-view-api.invalid_request;")?;
84        if let Some(p) = p.strip_prefix(b"id=") {
85            if let Some(id_end) = p.iter().position(|&b| b == b';') {
86                if let Ok(id_str) = std::str::from_utf8(&p[..id_end]) {
87                    let id = match id_str.parse::<ApiExtensionId>() {
88                        Ok(id) => id,
89                        Err(id) => id,
90                    };
91                    if let Some(p) = p[id_end..].strip_prefix(b";error=") {
92                        if let Ok(err_str) = std::str::from_utf8(p) {
93                            return Some((id, err_str));
94                        }
95                    }
96                    return Some((id, "invalid request, corrupted payload, unknown error"));
97                }
98            }
99        }
100        Some((
101            ApiExtensionId::INVALID,
102            "invalid request, corrupted payload, unknown extension_id and error",
103        ))
104    }
105}
106impl fmt::Debug for ApiExtensionPayload {
107    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108        write!(f, "ExtensionPayload({} bytes)", self.0.len())
109    }
110}
111
112/// Identifies an API extension and version.
113///
114/// Note that the version is part of the name, usually in the pattern "crate-name.extension.v2",
115/// there are no minor versions, all different versions are considered breaking changes and
116/// must be announced and supported by exact match only. You can still communicate non-breaking changes
117/// by using the extension payload
118#[derive(Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
119pub struct ApiExtensionName {
120    name: Txt,
121}
122impl ApiExtensionName {
123    /// New from unique name.
124    ///
125    /// The name must contain at least 1 characters, and match the pattern `[a-zA-Z][a-zA-Z0-9-_.]`.
126    pub fn new(name: impl Into<Txt>) -> Result<Self, ApiExtensionNameError> {
127        let name = name.into();
128        Self::new_impl(name)
129    }
130    fn new_impl(name: Txt) -> Result<ApiExtensionName, ApiExtensionNameError> {
131        if name.is_empty() {
132            return Err(ApiExtensionNameError::NameCannotBeEmpty);
133        }
134        for (i, c) in name.char_indices() {
135            if i == 0 {
136                if !c.is_ascii_alphabetic() {
137                    return Err(ApiExtensionNameError::NameCannotStartWithChar(c));
138                }
139            } else if !c.is_ascii_alphanumeric() && c != '_' && c != '-' && c != '.' {
140                return Err(ApiExtensionNameError::NameInvalidChar(c));
141            }
142        }
143
144        Ok(Self { name })
145    }
146}
147impl fmt::Debug for ApiExtensionName {
148    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
149        fmt::Debug::fmt(&self.name, f)
150    }
151}
152impl fmt::Display for ApiExtensionName {
153    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
154        fmt::Display::fmt(&self.name, f)
155    }
156}
157impl ops::Deref for ApiExtensionName {
158    type Target = str;
159
160    fn deref(&self) -> &Self::Target {
161        self.name.as_str()
162    }
163}
164impl From<&'static str> for ApiExtensionName {
165    fn from(value: &'static str) -> Self {
166        Self::new(value).unwrap()
167    }
168}
169
170/// API extension invalid name.
171#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
172pub enum ApiExtensionNameError {
173    /// Name cannot empty `""`.
174    NameCannotBeEmpty,
175    /// Name can only start with ASCII alphabetic chars `[a-zA-Z]`.
176    NameCannotStartWithChar(char),
177    /// Name can only contains `[a-zA-Z0-9-_.]`.
178    NameInvalidChar(char),
179}
180impl fmt::Display for ApiExtensionNameError {
181    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
182        match self {
183            ApiExtensionNameError::NameCannotBeEmpty => write!(f, "API extension name cannot be empty"),
184            ApiExtensionNameError::NameCannotStartWithChar(c) => {
185                write!(f, "API cannot start with '{c}', name pattern `[a-zA-Z][a-zA-Z0-9-_.]`")
186            }
187            ApiExtensionNameError::NameInvalidChar(c) => write!(f, "API cannot contain '{c}', name pattern `[a-zA-Z][a-zA-Z0-9-_.]`"),
188        }
189    }
190}
191impl std::error::Error for ApiExtensionNameError {}
192
193/// List of available API extensions.
194#[derive(Default, Clone, Debug, serde::Serialize, serde::Deserialize)]
195pub struct ApiExtensions(Vec<ApiExtensionName>);
196impl ops::Deref for ApiExtensions {
197    type Target = [ApiExtensionName];
198
199    fn deref(&self) -> &Self::Target {
200        &self.0
201    }
202}
203impl ApiExtensions {
204    /// New Empty.
205    pub fn new() -> Self {
206        Self::default()
207    }
208
209    /// Gets the position of the `ext` in the list of available extensions. This index
210    /// identifies the API extension in the [`Api::app_extension`] and [`Api::render_extension`].
211    ///
212    /// The key can be cached only for the duration of the view process, each view re-instantiation
213    /// must query for the presence of the API extension again, and it may change position on the list.
214    ///
215    /// [`Api::app_extension`]: crate::Api::app_extension
216    /// [`Api::render_extension`]: crate::Api::render_extension
217    pub fn id(&self, ext: &ApiExtensionName) -> Option<ApiExtensionId> {
218        self.0.iter().position(|e| e == ext).map(ApiExtensionId::from_index)
219    }
220
221    /// Push the `ext` to the list, if it is not already inserted.
222    ///
223    /// Returns `Ok(key)` if inserted or `Err(key)` is was already in list.
224    pub fn insert(&mut self, ext: ApiExtensionName) -> Result<ApiExtensionId, ApiExtensionId> {
225        if let Some(key) = self.id(&ext) {
226            Err(key)
227        } else {
228            let key = self.0.len();
229            self.0.push(ext);
230            Ok(ApiExtensionId::from_index(key))
231        }
232    }
233}
234
235/// Identifies an [`ApiExtensionName`] in a list.
236#[derive(Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
237#[serde(transparent)]
238pub struct ApiExtensionId(u32);
239impl fmt::Debug for ApiExtensionId {
240    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
241        if *self == Self::INVALID {
242            if f.alternate() {
243                write!(f, "ApiExtensionId::")?;
244            }
245            write!(f, "INVALID")
246        } else {
247            write!(f, "ApiExtensionId({})", self.0 - 1)
248        }
249    }
250}
251impl fmt::Display for ApiExtensionId {
252    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
253        if *self == Self::INVALID {
254            write!(f, "invalid")
255        } else {
256            write!(f, "{}", self.0 - 1)
257        }
258    }
259}
260impl ApiExtensionId {
261    /// Dummy ID.
262    pub const INVALID: Self = Self(0);
263
264    /// Gets the ID as a list index.
265    ///
266    /// # Panics
267    ///
268    /// Panics if called in `INVALID`.
269    pub fn index(self) -> usize {
270        self.0.checked_sub(1).expect("invalid id") as _
271    }
272
273    /// New ID from the index of an [`ApiExtensionName`] in a list.
274    ///
275    /// # Panics
276    ///
277    /// Panics if `idx > u32::MAX - 1`.
278    pub fn from_index(idx: usize) -> Self {
279        if idx > (u32::MAX - 1) as _ {
280            panic!("index out-of-bounds")
281        }
282        Self(idx as u32 + 1)
283    }
284}
285impl std::str::FromStr for ApiExtensionId {
286    type Err = Self;
287
288    fn from_str(s: &str) -> Result<Self, Self::Err> {
289        match s.parse::<u32>() {
290            Ok(i) => {
291                let r = Self::from_index(i as _);
292                if r == Self::INVALID { Err(r) } else { Ok(r) }
293            }
294            Err(_) => Err(Self::INVALID),
295        }
296    }
297}
298
299/// Error in the response of an API extension call.
300#[derive(Debug)]
301pub enum ApiExtensionRecvError {
302    /// Requested extension was not in the list of extensions.
303    UnknownExtension {
304        /// Extension that was requested.
305        ///
306        /// Is `INVALID` only if error message is corrupted.
307        extension_id: ApiExtensionId,
308    },
309    /// Invalid request format.
310    InvalidRequest {
311        /// Extension that was requested.
312        ///
313        /// Is `INVALID` only if error message is corrupted.
314        extension_id: ApiExtensionId,
315        /// Message from the view-process.
316        error: Txt,
317    },
318    /// Failed to deserialize to the expected response type.
319    Deserialize(bincode::Error),
320}
321impl fmt::Display for ApiExtensionRecvError {
322    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
323        match self {
324            ApiExtensionRecvError::UnknownExtension { extension_id } => write!(f, "invalid API request for unknown id {extension_id:?}"),
325            ApiExtensionRecvError::InvalidRequest { extension_id, error } => {
326                write!(f, "invalid API request for extension id {extension_id:?}, {error}")
327            }
328            ApiExtensionRecvError::Deserialize(e) => write!(f, "API extension response failed to deserialize, {e}"),
329        }
330    }
331}
332impl std::error::Error for ApiExtensionRecvError {
333    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
334        if let Self::Deserialize(e) = self { Some(e) } else { None }
335    }
336}