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