foundationdb_gen/
lib.rs

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