simploxide_bindgen/types/
mod.rs1pub 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 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 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
251pub 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 "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
311pub 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
371pub(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}