fdb_gen/
lib.rs

1use std::fmt;
2use xml::attribute::OwnedAttribute;
3use xml::reader::{EventReader, XmlEvent};
4
5const TAB1: &str = "    ";
6const TAB2: &str = "        ";
7const TAB3: &str = "            ";
8const TAB4: &str = "                ";
9
10#[derive(Debug)]
11struct FdbScope {
12    name: String,
13    options: Vec<FdbOption>,
14}
15impl FdbScope {
16    fn gen_ty<W: fmt::Write>(&self, w: &mut W) -> fmt::Result {
17        let with_ty = self.with_ty();
18
19        match self.name.as_str() {
20            "NetworkOption" => {
21                writeln!(
22                    w,
23                    "/// A set of options that can be set globally for the [FDB API]."
24                )?;
25                writeln!(w, "///")?;
26                writeln!(w, "/// [FDB API]: crate")?;
27            }
28            "ClusterOption" => writeln!(w, "/// TODO")?,
29            "DatabaseOption" => {
30                writeln!(
31                    w,
32                    "/// A set of options that can be set on a [`FdbDatabase`]."
33                )?;
34                writeln!(w, "///")?;
35                writeln!(w, "/// [`FdbDatabase`]: crate::database::FdbDatabase")?;
36            }
37            "TransactionOption" => {
38                writeln!(
39                    w,
40                    "/// A set of options that can be set on a [`FdbTransaction`]."
41                )?;
42                writeln!(w, "///")?;
43                writeln!(
44                    w,
45                    "/// [`FdbTransaction`]: crate::transaction::FdbTransaction"
46                )?;
47            }
48            "StreamingMode" => {
49                writeln!(
50                    w,
51                    "/// Options that control the way the Rust binding performs range reads."
52                )?;
53                writeln!(w, "///")?;
54                writeln!(
55                    w,
56                    "/// These options can be passed to [`get_range`] method."
57                )?;
58                writeln!(w, "///")?;
59                writeln!(
60                    w,
61                    "/// [`get_range`]: crate::transaction::ReadTransaction::get_range"
62                )?;
63                writeln!(w, "#[derive(PartialEq)]")?;
64            }
65            "MutationType" => {
66                writeln!(
67                    w,
68                    "/// A set of operations that can be performed atomically on a database."
69                )?;
70                writeln!(w, "///")?;
71                writeln!(w, "/// These options can be passed to [`mutate`] method.")?;
72                writeln!(w, "///")?;
73                writeln!(w, "/// [`mutate`]: crate::transaction::Transaction::mutate")?;
74            }
75            "ConflictRangeType" => {
76                writeln!(
77                    w,
78                    "/// An enumeration of available conflict range types to be passed to `fdb_sys::fdb_transaction_add_conflict_range`."
79                )?;
80                writeln!(w, "///")?;
81                writeln!(
82                    w,
83                    "/// This API is not directly exposed to the user. See [`FDBConflictRangeType`] C API for details."
84                )?;
85                writeln!(w, "///")?;
86                writeln!(w, "/// [`FDBConflictRangeType`]: https://apple.github.io/foundationdb/api-c.html#c.FDBConflictRangeType")?;
87            }
88            "ErrorPredicate" => writeln!(w, "/// TODO")?,
89            ty => panic!("unknown Scope name: `{}`", ty),
90        }
91        if with_ty {
92            writeln!(w, "#[derive(Clone, Debug)]")?;
93        } else {
94            writeln!(w, "#[derive(Clone, Copy, Debug)]")?;
95        }
96        writeln!(w, "#[allow(dead_code, missing_docs)]")?;
97        writeln!(w, "#[non_exhaustive]")?;
98        if self.name == "ConflictRangeType" || self.name == "ErrorPredicate" {
99            writeln!(w, "pub(crate) enum {name} {{", name = self.name)?;
100        } else {
101            writeln!(w, "pub enum {name} {{", name = self.name)?;
102        }
103
104        let with_ty = self.with_ty();
105        for option in self.options.iter() {
106            option.gen_ty(w, with_ty)?;
107        }
108        writeln!(w, "}}")
109    }
110
111    fn gen_impl<W: fmt::Write>(&self, w: &mut W) -> fmt::Result {
112        writeln!(w, "impl {name} {{", name = self.name)?;
113        self.gen_code(w)?;
114        self.gen_apply(w)?;
115        writeln!(w, "}}")
116    }
117
118    fn gen_code<W: fmt::Write>(&self, w: &mut W) -> fmt::Result {
119        writeln!(
120            w,
121            "{t}#[allow(dead_code)]\n{t}pub(crate) fn code(&self) -> fdb_sys::FDB{name} {{",
122            t = TAB1,
123            name = self.name,
124        )?;
125        writeln!(w, "{t}match *self {{", t = TAB2)?;
126
127        let enum_prefix = self.c_enum_prefix();
128        let with_ty = self.with_ty();
129
130        for option in self.options.iter() {
131            writeln!(
132                w,
133                "{t}{scope}::{name}{param} => fdb_sys::{enum_prefix}{code},",
134                t = TAB3,
135                scope = self.name,
136                name = option.name,
137                param = if let (true, Some(..)) = (with_ty, option.get_ty()) {
138                    "(..)"
139                } else {
140                    ""
141                },
142                enum_prefix = enum_prefix,
143                code = option.c_name,
144            )?;
145        }
146
147        writeln!(w, "{t}}}", t = TAB2)?;
148        writeln!(w, "{t}}}", t = TAB1)
149    }
150
151    fn gen_apply<W: fmt::Write>(&self, w: &mut W) -> fmt::Result {
152        let fn_name = match self.apply_fn_name() {
153            Some(name) => name,
154            _ => return Ok(()),
155        };
156
157        let first_arg = match self.apply_arg_name() {
158            Some(name) => format!(", target: *mut fdb_sys::{}", name),
159            None => String::new(),
160        };
161
162        writeln!(
163            w,
164            "{t}#[allow(dead_code)]\n{t}#[allow(clippy::transmute_num_to_bytes)]\n{t}pub(crate) unsafe fn apply(&self{args}) -> FdbResult<()> {{",
165            t = TAB1,
166            args = first_arg
167        )?;
168        writeln!(w, "{t}let code = self.code();", t = TAB2)?;
169        writeln!(w, "{t}let err = match *self {{", t = TAB2)?;
170
171        let args = if first_arg.is_empty() {
172            "code"
173        } else {
174            "target, code"
175        };
176
177        for option in self.options.iter() {
178            write!(w, "{}{}::{}", TAB3, self.name, option.name)?;
179            match option.param_type {
180                FdbOptionTy::Empty => {
181                    writeln!(
182                        w,
183                        " => fdb_sys::{}({}, std::ptr::null(), 0),",
184                        fn_name, args
185                    )?;
186                }
187                FdbOptionTy::Int => {
188                    writeln!(w, "(v) => {{")?;
189                    writeln!(
190                        w,
191                        "{}let data: [u8;8] = std::mem::transmute(v as i64);",
192                        TAB4,
193                    )?;
194                    writeln!(
195                        w,
196                        "{}fdb_sys::{}({}, data.as_ptr() as *const u8, 8)",
197                        TAB4, fn_name, args
198                    )?;
199                    writeln!(w, "{t}}}", t = TAB3)?;
200                }
201                FdbOptionTy::Bytes => {
202                    writeln!(w, "(ref v) => {{")?;
203                    writeln!(
204                        w,
205                        "{}fdb_sys::{}({}, v.as_ptr() as *const u8, \
206                         i32::try_from(v.len()).expect(\"len to fit in i32\"))\n",
207                        TAB4, fn_name, args
208                    )?;
209                    writeln!(w, "{t}}}", t = TAB3)?;
210                }
211                FdbOptionTy::Str => {
212                    writeln!(w, "(ref v) => {{")?;
213                    writeln!(
214                        w,
215                        "{}fdb_sys::{}({}, v.as_ptr() as *const u8, \
216                         i32::try_from(v.len()).expect(\"len to fit in i32\"))\n",
217                        TAB4, fn_name, args
218                    )?;
219                    writeln!(w, "{t}}}", t = TAB3)?;
220                }
221            }
222        }
223
224        writeln!(w, "{t}}};", t = TAB2)?;
225        writeln!(
226            w,
227            "{t}if err != 0 {{ Err(FdbError::new(err)) }} else {{ Ok(()) }}",
228            t = TAB2,
229        )?;
230        writeln!(w, "{t}}}", t = TAB1)
231    }
232
233    fn with_ty(&self) -> bool {
234        self.apply_fn_name().is_some()
235    }
236
237    fn c_enum_prefix(&self) -> &'static str {
238        match self.name.as_str() {
239            "NetworkOption" => "FDBNetworkOption_FDB_NET_OPTION_",
240            "ClusterOption" => "FDBClusterOption_FDB_CLUSTER_OPTION_",
241            "DatabaseOption" => "FDBDatabaseOption_FDB_DB_OPTION_",
242            "TransactionOption" => "FDBTransactionOption_FDB_TR_OPTION_",
243            "StreamingMode" => "FDBStreamingMode_FDB_STREAMING_MODE_",
244            "MutationType" => "FDBMutationType_FDB_MUTATION_TYPE_",
245            "ConflictRangeType" => "FDBConflictRangeType_FDB_CONFLICT_RANGE_TYPE_",
246            "ErrorPredicate" => "FDBErrorPredicate_FDB_ERROR_PREDICATE_",
247            ty => panic!("unknown Scope name: `{}`", ty),
248        }
249    }
250
251    fn apply_arg_name(&self) -> Option<&'static str> {
252        let s = match self.name.as_str() {
253            "ClusterOption" => "FDBCluster",
254            "DatabaseOption" => "FDBDatabase",
255            "TransactionOption" => "FDBTransaction",
256            _ => return None,
257        };
258        Some(s)
259    }
260
261    fn apply_fn_name(&self) -> Option<&'static str> {
262        let s = match self.name.as_str() {
263            "NetworkOption" => "fdb_network_set_option",
264            "ClusterOption" => "fdb_cluster_set_option",
265            "DatabaseOption" => "fdb_database_set_option",
266            "TransactionOption" => "fdb_transaction_set_option",
267            _ => return None,
268        };
269        Some(s)
270    }
271}
272
273#[derive(Clone, Copy, Debug)]
274enum FdbOptionTy {
275    Empty,
276    Int,
277    Str,
278    Bytes,
279}
280impl std::default::Default for FdbOptionTy {
281    fn default() -> Self {
282        FdbOptionTy::Empty
283    }
284}
285
286#[derive(Default, Debug)]
287struct FdbOption {
288    name: String,
289    c_name: String,
290    code: i32,
291    param_type: FdbOptionTy,
292    param_description: String,
293    description: String,
294    hidden: bool,
295    default_for: Option<i32>,
296    persistent: bool,
297}
298
299impl FdbOption {
300    fn gen_ty<W: fmt::Write>(&self, w: &mut W, with_ty: bool) -> fmt::Result {
301        if !self.param_description.is_empty() {
302            writeln!(w, "{t}/// {desc}", t = TAB1, desc = self.param_description)?;
303            writeln!(w, "{t}///", t = TAB1)?;
304        }
305        if !self.description.is_empty() {
306            writeln!(w, "{t}/// {desc}", t = TAB1, desc = self.description)?;
307        }
308
309        if let (true, Some(ty)) = (with_ty, self.get_ty()) {
310            writeln!(w, "{t}{name}({ty}),", t = TAB1, name = self.name, ty = ty)?;
311        } else {
312            writeln!(w, "{t}{name},", t = TAB1, name = self.name)?;
313        }
314        Ok(())
315    }
316
317    fn get_ty(&self) -> Option<&'static str> {
318        match self.param_type {
319            FdbOptionTy::Int => Some("i32"),
320            FdbOptionTy::Str => Some("String"),
321            FdbOptionTy::Bytes => Some("Vec<u8>"),
322            FdbOptionTy::Empty => None,
323        }
324    }
325}
326
327fn to_rs_enum_name(v: &str) -> String {
328    let mut is_start_of_word = true;
329    v.chars()
330        .filter_map(|c| {
331            if c == '_' {
332                is_start_of_word = true;
333                None
334            } else if is_start_of_word {
335                is_start_of_word = false;
336                Some(c.to_ascii_uppercase())
337            } else {
338                Some(c)
339            }
340        })
341        .collect()
342}
343
344impl From<Vec<OwnedAttribute>> for FdbOption {
345    fn from(attrs: Vec<OwnedAttribute>) -> Self {
346        let mut opt = Self::default();
347        for attr in attrs {
348            let v = attr.value;
349            match attr.name.local_name.as_str() {
350                "name" => {
351                    opt.name = to_rs_enum_name(v.as_str());
352                    opt.c_name = v.to_uppercase();
353                }
354                "code" => {
355                    opt.code = v.parse().expect("code to be a i32");
356                }
357                "paramType" => {
358                    opt.param_type = match v.as_str() {
359                        "Int" => FdbOptionTy::Int,
360                        "String" => FdbOptionTy::Str,
361                        "Bytes" => FdbOptionTy::Bytes,
362                        "" => FdbOptionTy::Empty,
363                        ty => panic!("unexpected param_type: {}", ty),
364                    };
365                }
366                "paramDescription" => {
367                    opt.param_description = v;
368                }
369                "description" => {
370                    opt.description = v;
371                }
372                "hidden" => match v.as_str() {
373                    "true" => opt.hidden = true,
374                    "false" => opt.hidden = false,
375                    _ => panic!("unexpected boolean value in 'hidden': {}", v),
376                },
377                "defaultFor" => {
378                    opt.default_for = Some(v.parse().expect("defaultFor to be a i32"));
379                }
380                "persistent" => match v.as_str() {
381                    "true" => opt.persistent = true,
382                    "false" => opt.persistent = false,
383                    _ => panic!("unexpected boolean value in 'persistent': {}", v),
384                },
385                attr => {
386                    panic!("unexpected option attribute: {}", attr);
387                }
388            }
389        }
390        opt
391    }
392}
393
394fn on_scope<I>(parser: &mut I) -> Vec<FdbOption>
395where
396    I: Iterator<Item = xml::reader::Result<XmlEvent>>,
397{
398    let mut options = Vec::new();
399    for e in parser {
400        let e = e.unwrap();
401        match e {
402            XmlEvent::StartElement {
403                name, attributes, ..
404            } => {
405                assert_eq!(name.local_name, "Option", "unexpected token");
406
407                let option = FdbOption::from(attributes.clone());
408                if !option.hidden {
409                    options.push(option);
410                }
411            }
412            XmlEvent::EndElement { name, .. } => {
413                if name.local_name == "Scope" {
414                    return options;
415                }
416            }
417            _ => {}
418        }
419    }
420
421    panic!("unexpected end of token");
422}
423
424#[cfg(not(any(feature = "fdb-6_3", feature = "fdb-7_1")))]
425const OPTIONS_DATA: &[u8] = include_bytes!("Please specify fdb-<major>_<minor> feature");
426
427#[cfg(feature = "fdb-6_3")]
428const OPTIONS_DATA: &[u8] = include_bytes!("../include/630/fdb.options");
429
430#[cfg(feature = "fdb-7_1")]
431const OPTIONS_DATA: &[u8] = include_bytes!("../include/710/fdb.options");
432
433pub fn emit(w: &mut impl fmt::Write) -> fmt::Result {
434    let mut reader = OPTIONS_DATA;
435    let parser = EventReader::new(&mut reader);
436    let mut iter = parser.into_iter();
437    let mut scopes = Vec::new();
438
439    while let Some(e) = iter.next() {
440        match e.unwrap() {
441            XmlEvent::StartElement {
442                name, attributes, ..
443            } => {
444                if name.local_name == "Scope" {
445                    let scope_name = attributes
446                        .into_iter()
447                        .find(|attr| attr.name.local_name == "name")
448                        .unwrap();
449
450                    let options = on_scope(&mut iter);
451                    scopes.push(FdbScope {
452                        name: scope_name.value,
453                        options,
454                    });
455                }
456            }
457            XmlEvent::EndElement { .. } => {
458                //
459            }
460            _ => {}
461        }
462    }
463
464    writeln!(w, "use std::convert::TryFrom;")?;
465    writeln!(w, "use crate::error::{{FdbError, FdbResult}};")?;
466    for scope in scopes.iter() {
467        scope.gen_ty(w)?;
468        scope.gen_impl(w)?;
469    }
470
471    Ok(())
472}