diff --git a/schemafy_lib/src/generator.rs b/schemafy_lib/src/generator.rs index 74f1f64..8adbc80 100644 --- a/schemafy_lib/src/generator.rs +++ b/schemafy_lib/src/generator.rs @@ -1,9 +1,14 @@ -use crate::Expander; +use crate::{Expander, ExpanderOptions}; use std::{ io, path::{Path, PathBuf}, }; +#[derive(Debug, PartialEq)] +pub struct GeneratorOptions { + pub deny_unknown_fields: bool, +} + /// A configurable builder for generating Rust types from a JSON /// schema. /// @@ -23,6 +28,8 @@ pub struct Generator<'a, 'b> { pub schemafy_path: &'a str, /// The JSON schema file to read pub input_file: &'b Path, + + pub generator_options: &'a GeneratorOptions, } impl<'a, 'b> Generator<'a, 'b> { @@ -50,7 +57,15 @@ impl<'a, 'b> Generator<'a, 'b> { err ) }); - let mut expander = Expander::new(self.root_name.as_deref(), self.schemafy_path, &schema); + let expander_options = ExpanderOptions { + deny_unknown_fields: self.generator_options.deny_unknown_fields, + }; + let mut expander = Expander::new( + self.root_name.as_deref(), + self.schemafy_path, + &schema, + &expander_options, + ); expander.expand(&schema) } @@ -79,6 +94,9 @@ impl<'a, 'b> Default for GeneratorBuilder<'a, 'b> { root_name: None, schemafy_path: "::schemafy_core::", input_file: Path::new("schema.json"), + generator_options: &GeneratorOptions { + deny_unknown_fields: false, + }, }, } } @@ -101,6 +119,10 @@ impl<'a, 'b> GeneratorBuilder<'a, 'b> { self.inner.schemafy_path = schemafy_path; self } + pub fn with_options(mut self, generator_options: &'a GeneratorOptions) -> Self { + self.inner.generator_options = generator_options; + self + } pub fn build(self) -> Generator<'a, 'b> { self.inner } diff --git a/schemafy_lib/src/lib.rs b/schemafy_lib/src/lib.rs index 0343b0f..a0519db 100644 --- a/schemafy_lib/src/lib.rs +++ b/schemafy_lib/src/lib.rs @@ -69,7 +69,7 @@ use uriparse::{Fragment, URI}; pub use schema::{Schema, SimpleTypes}; -pub use generator::{Generator, GeneratorBuilder}; +pub use generator::{Generator, GeneratorBuilder, GeneratorOptions}; use proc_macro2::{Span, TokenStream}; @@ -310,10 +310,23 @@ impl<'a, 'r> FieldExpander<'a, 'r> { } } +pub struct ExpanderOptions { + deny_unknown_fields: bool, +} + +impl Default for ExpanderOptions { + fn default() -> Self { + ExpanderOptions { + deny_unknown_fields: true, + } + } +} + pub struct Expander<'r> { root_name: Option<&'r str>, schemafy_path: &'r str, root: &'r Schema, + options: &'r ExpanderOptions, current_type: String, current_field: String, types: Vec<(String, TokenStream)>, @@ -343,11 +356,13 @@ impl<'r> Expander<'r> { root_name: Option<&'r str>, schemafy_path: &'r str, root: &'r Schema, + options: &'r ExpanderOptions, ) -> Expander<'r> { Expander { root_name, root, schemafy_path, + options, current_field: "".into(), current_type: "".into(), types: Vec::new(), @@ -610,6 +625,7 @@ impl<'r> Expander<'r> { let type_decl = if is_struct { let serde_deny_unknown = if schema.additional_properties == Some(Value::Bool(false)) && schema.pattern_properties.is_empty() + && self.options.deny_unknown_fields { Some(quote! { #[serde(deny_unknown_fields)] }) } else { @@ -796,7 +812,13 @@ mod tests { fn test_expander_type_ref() { let json = std::fs::read_to_string("src/schema.json").expect("Read schema JSON file"); let schema = serde_json::from_str(&json).unwrap_or_else(|err| panic!("{}", err)); - let expander = Expander::new(Some("SchemaName"), "::schemafy_core::", &schema); + let expander_options = ExpanderOptions::default(); + let expander = Expander::new( + Some("SchemaName"), + "::schemafy_core::", + &schema, + &expander_options, + ); assert_eq!(expander.type_ref("normalField"), "NormalField"); assert_eq!(expander.type_ref("#"), "SchemaName"); @@ -837,7 +859,8 @@ mod tests { let json = std::fs::read_to_string("tests/multiple-property-types.json") .expect("Read schema JSON file"); let schema = serde_json::from_str(&json).unwrap(); - let mut expander = Expander::new(Some("Root"), "UNUSED", &schema); + let expander_options = ExpanderOptions::default(); + let mut expander = Expander::new(Some("Root"), "UNUSED", &schema, &expander_options); expander.expand(&schema); // check that the type names for embedded objects only include their diff --git a/src/main.rs b/src/main.rs index 386ad62..a178a22 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,7 @@ use std::path::Path; use std::process::{Command, Stdio}; use anyhow::{anyhow, bail, Context, Result}; -use schemafy_lib::Generator; +use schemafy_lib::{Generator, GeneratorOptions}; use structopt::StructOpt; use tempfile::NamedTempFile; @@ -18,16 +18,22 @@ struct Opts { output: Option, /// JSON schema file schema_path: String, + /// Flag to allow new fields from the server. + #[structopt(long)] + allow_unknown_fields: bool, } pub fn main() -> Result<()> { let opts = Opts::from_args(); - + let generator_options = GeneratorOptions { + deny_unknown_fields: !opts.allow_unknown_fields, + }; // generate the Rust code let mut generated_file = NamedTempFile::new()?; Generator::builder() .with_root_name_str(&opts.root) .with_input_file(&opts.schema_path) + .with_options(&generator_options) .build() .generate_to_file( &generated_file