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