simploxide_bindgen/types/
mod.rs

1//! Defines both data types that represent different typekinds in the SimpleX API docs as well as
2//! the parser that turns TYPES.md into an iterator which yields [`crate::types::ApiType`].
3//!
4//! The `std::fmt::Display` implementations render types as the Rust code tailored for
5//! `simploxide-api-types` crate, but you can easily override them with a newtype like
6//! `CustomFmt<'a>(&'a ApiType);`.
7
8pub mod discriminated_union_type;
9pub mod enum_type;
10pub mod record_type;
11
12pub use discriminated_union_type::{
13    DiscriminatedUnionType, DisjointedDiscriminatedUnion, DisjointedDisriminatedUnionVariant,
14};
15pub use enum_type::EnumType;
16pub use record_type::RecordType;
17
18use convert_case::{Case, Casing as _};
19use std::str::FromStr;
20
21use crate::parse_utils;
22
23pub fn parse(types_md: &str) -> impl Iterator<Item = Result<ApiType, String>> {
24    types_md.split("---").skip(1).map(ApiType::from_str)
25}
26
27pub enum ApiType {
28    Record(RecordType),
29    DiscriminatedUnion(DiscriminatedUnionType),
30    Enum(EnumType),
31}
32
33impl ApiType {
34    /// True if type represents an error type.
35    pub fn is_error(&self) -> bool {
36        self.name().contains("Error")
37    }
38
39    pub fn name(&self) -> &str {
40        match self {
41            Self::Record(r) => r.name.as_str(),
42            Self::Enum(e) => e.name.as_str(),
43            Self::DiscriminatedUnion(du) => du.name.as_str(),
44        }
45    }
46}
47
48impl std::str::FromStr for ApiType {
49    type Err = String;
50
51    fn from_str(md_block: &str) -> Result<Self, Self::Err> {
52        fn parser<'a>(mut lines: impl Iterator<Item = &'a str>) -> Result<ApiType, String> {
53            const TYPENAME_PAT: &str = parse_utils::H2;
54            const TYPEKIND_PAT: &str = parse_utils::BOLD;
55
56            let typename = parse_utils::skip_empty(&mut lines)
57                .and_then(|s| s.strip_prefix(TYPENAME_PAT))
58                .ok_or_else(|| format!("Failed to find a type name by pattern {TYPENAME_PAT:?}"))?;
59
60            let mut doc_comments = Vec::new();
61
62            let typekind = parse_utils::parse_doc_lines(&mut lines, &mut doc_comments, |s| {
63                s.starts_with(TYPEKIND_PAT)
64            })
65            .map(|s| s.strip_prefix(TYPEKIND_PAT).unwrap())
66            .ok_or_else(|| format!("Failed to find a type kind by pattern {TYPEKIND_PAT:?}"))?;
67
68            let mut syntax = String::new();
69            let breaker = |s: &str| s.starts_with("**Syntax");
70
71            if typekind.starts_with("Record") {
72                let mut fields = Vec::new();
73
74                let syntax_block =
75                    parse_utils::parse_record_fields(&mut lines, &mut fields, breaker)?;
76
77                if syntax_block.is_some() {
78                    parse_utils::parse_syntax(&mut lines, &mut syntax)?;
79                }
80
81                Ok(ApiType::Record(RecordType {
82                    name: typename.to_owned(),
83                    fields,
84                    doc_comments,
85                    syntax,
86                }))
87            } else if typekind.starts_with("Enum") {
88                let mut variants = Vec::new();
89
90                let syntax_block =
91                    parse_utils::parse_enum_variants(&mut lines, &mut variants, breaker)?;
92
93                if syntax_block.is_some() {
94                    parse_utils::parse_syntax(&mut lines, &mut syntax)?;
95                }
96
97                Ok(ApiType::Enum(EnumType {
98                    name: typename.to_owned(),
99                    variants,
100                    doc_comments,
101                    syntax,
102                }))
103            } else if typekind.starts_with("Discriminated") {
104                let mut variants = Vec::new();
105
106                let syntax_block = parse_utils::parse_discriminated_union_variants(
107                    &mut lines,
108                    &mut variants,
109                    breaker,
110                )?;
111
112                if syntax_block.is_some() {
113                    parse_utils::parse_syntax(&mut lines, &mut syntax)?;
114                }
115
116                Ok(ApiType::DiscriminatedUnion(DiscriminatedUnionType {
117                    name: typename.to_owned(),
118                    variants,
119                    doc_comments,
120                    syntax,
121                }))
122            } else {
123                Err(format!("Unknown type kind: {typekind:?}"))
124            }
125        }
126
127        parser(md_block.lines().map(str::trim))
128            .map_err(|e| format!("{e} in md block\n```\n{md_block}\n```"))
129    }
130}
131
132impl std::fmt::Display for ApiType {
133    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
134        match self {
135            Self::Record(r) => r.fmt(f),
136            Self::Enum(e) => e.fmt(f),
137            Self::DiscriminatedUnion(du) => du.fmt(f),
138        }
139    }
140}
141
142#[derive(Debug, Clone, PartialEq)]
143pub struct Field {
144    pub api_name: String,
145    pub rust_name: String,
146    pub typ: String,
147}
148
149impl Field {
150    pub fn from_api_name(api_name: String, typ: String) -> Self {
151        Self {
152            api_name: api_name.clone(),
153            rust_name: api_name.to_case(Case::Snake),
154            typ,
155        }
156    }
157    pub fn is_optional(&self) -> bool {
158        is_optional_type(self.typ.as_str())
159    }
160
161    pub fn is_vec(&self) -> bool {
162        is_vec_type(self.typ.as_str())
163    }
164
165    pub fn is_map(&self) -> bool {
166        is_map_type(self.typ.as_str())
167    }
168
169    pub fn is_numeric(&self) -> bool {
170        is_numeric_type(self.typ.as_str())
171    }
172
173    pub fn is_bool(&self) -> bool {
174        is_bool_type(self.typ.as_str())
175    }
176
177    pub fn is_string(&self) -> bool {
178        is_string_type(self.typ.as_str())
179    }
180
181    pub fn is_compound(&self) -> bool {
182        is_compound_type(self.typ.as_str())
183    }
184
185    /// Retrieves the inner type of Option<_> or Vec<_>
186    /// Returns None if the field type is not Option or Vec.
187    pub fn inner_type(&self) -> Option<&str> {
188        inner_type(self.typ.as_str())
189    }
190}
191
192impl FromStr for Field {
193    type Err = String;
194
195    fn from_str(line: &str) -> Result<Self, Self::Err> {
196        let (name, typ) = line
197            .trim()
198            .split_once(':')
199            .ok_or_else(|| format!("Failed to parse field at line: '{line}'"))?;
200
201        let api_name = name.trim().to_owned();
202        let rust_name = api_name.to_case(Case::Snake);
203        let typ = resolve_type(typ.trim())?;
204
205        Ok(Field {
206            api_name,
207            rust_name,
208            typ,
209        })
210    }
211}
212
213pub fn is_optional_type(typ: &str) -> bool {
214    typ.starts_with("Option<")
215}
216
217pub fn is_vec_type(typ: &str) -> bool {
218    typ.starts_with("Vec<")
219}
220
221pub fn is_map_type(typ: &str) -> bool {
222    typ.starts_with("HashMap<")
223}
224
225pub fn is_numeric_type(typ: &str) -> bool {
226    typ.starts_with("i")
227        || typ.starts_with("f")
228        || typ.starts_with("u")
229        || typ.starts_with("Option<i")
230        || typ.starts_with("Option<f")
231        || typ.starts_with("Option<u")
232}
233
234pub fn is_bool_type(typ: &str) -> bool {
235    typ == "bool"
236}
237
238pub fn is_string_type(typ: &str) -> bool {
239    typ == "String" || typ == "UtcTime"
240}
241
242pub fn is_compound_type(typ: &str) -> bool {
243    !is_optional_type(typ)
244        && !is_vec_type(typ)
245        && !is_map_type(typ)
246        && !is_numeric_type(typ)
247        && !is_bool_type(typ)
248        && !is_string_type(typ)
249}
250
251/// Retrieves the inner type of Option<_> or Vec<_>
252/// Returns None if the field type is not Option or Vec.
253pub fn inner_type(typ: &str) -> Option<&str> {
254    if let Some(opt) = typ.strip_prefix("Option<") {
255        let end = opt.rfind('>').unwrap();
256        Some(&opt[..end])
257    } else if let Some(vec) = typ.strip_prefix("Vec<") {
258        let end = vec.rfind('>').unwrap();
259        Some(&vec[..end])
260    } else {
261        None
262    }
263}
264
265fn resolve_type(t: &str) -> Result<String, String> {
266    if let Some(t) = t.strip_suffix('}') {
267        let t = t.strip_prefix('{').unwrap().trim();
268        let (lhs, rhs) = t.split_once(':').unwrap();
269
270        let key = resolve_type(lhs.trim())?;
271        let val = resolve_type(rhs.trim())?;
272
273        return Ok(format!("HashMap<{key}, {val}>"));
274    }
275
276    if let Some(t) = t.strip_suffix(']') {
277        let resolved = resolve_type(t.strip_prefix('[').unwrap())?;
278        return Ok(format!("Vec<{resolved}>"));
279    }
280
281    if let Some(t) = t.strip_suffix('?') {
282        let resolved = resolve_type(t)?;
283        return Ok(format!("Option<{resolved}>"));
284    }
285
286    let resolved = match t {
287        "bool" => "bool".to_owned(),
288        "int" => "i32".to_owned(),
289        "int64" => "i64".to_owned(),
290        "word32" => "u32".to_owned(),
291        "double" => "f64".to_owned(),
292        "string" => "String".to_owned(),
293        // These types map into themselves to preserve semantics.
294        // The generated module MUST have the typedefs like these:
295        //  - type UtcTime = String;
296        //  - type JsonObject = serde_json::Value;
297        "UTCTime" => "UtcTime".to_owned(),
298        "JSONObject" => "JsonObject".to_owned(),
299
300        compound if compound.starts_with('[') => {
301            let end = compound.find(']').unwrap();
302            compound['['.len_utf8()..end].to_owned()
303        }
304
305        _ => return Err(format!("Failed to resolve type: `{t}`")),
306    };
307
308    Ok(resolved)
309}
310
311/// A helper that allows to configure how the field should be rendred.
312pub struct FieldFmt<'a> {
313    field: &'a Field,
314    offset: usize,
315    is_pub: bool,
316}
317
318impl<'a> FieldFmt<'a> {
319    pub fn new(field: &'a Field) -> Self {
320        Self::with_offset(field, 0)
321    }
322
323    pub fn with_offset(field: &'a Field, offset: usize) -> Self {
324        Self {
325            field,
326            offset,
327            is_pub: false,
328        }
329    }
330
331    pub fn set_pub(&mut self, new: bool) {
332        self.is_pub = new;
333    }
334}
335
336impl<'a> std::fmt::Display for FieldFmt<'a> {
337    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
338        let offset = " ".repeat(self.offset);
339        let pub_ = if self.is_pub { "pub " } else { "" };
340        let is_numeric = self.field.is_numeric();
341        let is_optional = self.field.is_optional();
342
343        write!(f, "{offset}#[serde(rename = \"{}\"", self.field.api_name)?;
344
345        if is_optional {
346            write!(f, ", skip_serializing_if = \"Option::is_none\"")?;
347        }
348
349        if is_numeric {
350            if is_optional {
351                write!(
352                    f,
353                    ", deserialize_with=\"deserialize_option_number_from_string\", default"
354                )?;
355            } else {
356                write!(f, ", deserialize_with=\"deserialize_number_from_string\"")?;
357            }
358        }
359
360        writeln!(f, ")]")?;
361        writeln!(
362            f,
363            "{offset}{pub_}{}: {},",
364            self.field.rust_name, self.field.typ
365        )?;
366
367        Ok(())
368    }
369}
370
371/// A common impl for outer docs rendering shared by all type kinds.
372pub(crate) trait TopLevelDocs {
373    fn doc_lines(&self) -> &Vec<String>;
374
375    fn syntax(&self) -> &str;
376
377    fn write_docs_fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
378        for line in self.doc_lines() {
379            writeln!(f, "/// {line}")?;
380        }
381
382        if !self.syntax().is_empty() {
383            if !self.doc_lines().is_empty() {
384                writeln!(f, "///")?;
385            }
386
387            writeln!(f, "/// *Syntax:*")?;
388            writeln!(f, "///")?;
389            writeln!(f, "/// ```")?;
390            writeln!(f, "/// {}", self.syntax())?;
391            writeln!(f, "/// ```")?;
392        }
393
394        Ok(())
395    }
396}