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 }
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}