From 51ed13218c24c83b7b10f8cbfbcaa5ceea908a18 Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Tue, 6 Aug 2019 20:56:04 +0100 Subject: [PATCH] Allow making a schema to fail by returning an Err --- schemars/src/error.rs | 26 ++++++++++ schemars/src/gen.rs | 101 ++++++++++++++++++++++-------------- schemars/src/lib.rs | 3 ++ schemars/src/main.rs | 9 ++-- schemars/src/make_schema.rs | 80 ++++++++++++++-------------- schemars/tests/flatten.rs | 6 ++- schemars/tests/test.rs | 9 ++-- schemars_derive/src/lib.rs | 14 ++--- 8 files changed, 149 insertions(+), 99 deletions(-) create mode 100644 schemars/src/error.rs diff --git a/schemars/src/error.rs b/schemars/src/error.rs new file mode 100644 index 0000000..410deac --- /dev/null +++ b/schemars/src/error.rs @@ -0,0 +1,26 @@ +use std::fmt; +use std::error::Error; +use crate::schema::Schema; + +pub type Result = std::result::Result; + +#[derive(Debug, Clone)] +pub struct MakeSchemaError { + msg: &'static str, + schema: Schema +} + +impl MakeSchemaError { + pub fn new(msg: &'static str, schema: Schema) -> MakeSchemaError { + MakeSchemaError { msg, schema } + } +} + +impl fmt::Display for MakeSchemaError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{} Schema: {:?}", self.msg, self.schema) + } +} + +impl Error for MakeSchemaError { +} diff --git a/schemars/src/gen.rs b/schemars/src/gen.rs index 7370d70..38c8e31 100644 --- a/schemars/src/gen.rs +++ b/schemars/src/gen.rs @@ -1,5 +1,6 @@ use crate::make_schema::{MakeSchema, SchemaTypeId}; use crate::schema::*; +use crate::{MakeSchemaError, Result}; use std::collections::BTreeMap as Map; use std::collections::BTreeSet as Set; use std::iter::FromIterator; @@ -88,7 +89,7 @@ impl SchemaGenerator { } } - pub fn subschema_for(&mut self) -> Schema { + pub fn subschema_for(&mut self) -> Result { if !T::is_referenceable() { return T::make_schema(self); } @@ -97,30 +98,40 @@ impl SchemaGenerator { let name = self .definitions .get(&type_id) - .map(|(n, _)| n.clone()) + .map(|(n, _)| Ok(n.clone())) .unwrap_or_else(|| { let name = self.make_unique_name::(); - self.insert_new_subschema_for::(type_id, name.clone()); - name - }); + self.insert_new_subschema_for::(type_id, name.clone())?; + Ok(name) + })?; let reference = format!("{}{}", self.settings().definitions_path, name); - SchemaRef { reference }.into() + Ok(SchemaRef { reference }.into()) } fn insert_new_subschema_for( &mut self, type_id: SchemaTypeId, name: String, - ) { + ) -> Result { self.names.insert(name.clone()); let dummy = Schema::Bool(false); // insert into definitions BEFORE calling make_schema to avoid infinite recursion - self.definitions.insert(type_id.clone(), (name, dummy)); - - let schema = T::make_schema(self); self.definitions - .entry(type_id) - .and_modify(|(_, s)| *s = schema); + .insert(type_id.clone(), (name.clone(), dummy)); + + match T::make_schema(self) { + Ok(schema) => { + self.definitions + .entry(type_id) + .and_modify(|(_, s)| *s = schema); + Ok(name) + } + Err(e) => { + self.names.remove(&name); + self.definitions.remove(&type_id); + Err(e) + } + } } pub fn definitions(&self) -> Map { @@ -131,52 +142,64 @@ impl SchemaGenerator { Map::from_iter(self.definitions.into_iter().map(|(_, v)| v)) } - pub fn root_schema_for(&mut self) -> Schema { - let schema = T::make_schema(self); - if let Schema::Object(mut o) = schema { - o.schema = Some("http://json-schema.org/draft-07/schema#".to_owned()); - o.title = Some(T::schema_name()); - o.definitions.extend(self.definitions()); - return Schema::Object(o); - } - schema + pub fn root_schema_for(&mut self) -> Result { + let schema = T::make_schema(self)?; + Ok(match schema { + Schema::Object(mut o) => { + o.schema = Some("http://json-schema.org/draft-07/schema#".to_owned()); + o.title = Some(T::schema_name()); + o.definitions.extend(self.definitions()); + Schema::Object(o) + } + schema => schema, + }) } - pub fn into_root_schema_for(mut self) -> Schema { - let schema = T::make_schema(&mut self); - if let Schema::Object(mut o) = schema { - o.schema = Some("http://json-schema.org/draft-07/schema#".to_owned()); - o.title = Some(T::schema_name()); - o.definitions.extend(self.into_definitions()); - return Schema::Object(o); - } - schema + pub fn into_root_schema_for(mut self) -> Result { + let schema = T::make_schema(&mut self)?; + Ok(match schema { + Schema::Object(mut o) => { + o.schema = Some("http://json-schema.org/draft-07/schema#".to_owned()); + o.title = Some(T::schema_name()); + o.definitions.extend(self.into_definitions()); + Schema::Object(o) + } + schema => schema, + }) } - pub(crate) fn try_get_schema_object<'a>( - &'a self, - mut schema: &'a Schema, - ) -> Option { + pub(crate) fn get_schema_object<'a>(&'a self, mut schema: &'a Schema) -> Result { loop { match schema { - Schema::Object(o) => return Some(o.clone()), - Schema::Bool(true) => return Some(Default::default()), + Schema::Object(o) => return Ok(o.clone()), + Schema::Bool(true) => return Ok(Default::default()), Schema::Bool(false) => { - return Some(SchemaObject { + return Ok(SchemaObject { not: Some(Schema::Bool(true).into()), ..Default::default() }) } Schema::Ref(r) => { let definitions_path_len = self.settings().definitions_path.len(); - let name = r.reference.get(definitions_path_len..)?; + let name = r.reference.get(definitions_path_len..).ok_or_else(|| { + MakeSchemaError::new( + "Could not extract referenced schema name.", + Schema::Ref(r.clone()), + ) + })?; // FIXME this is pretty inefficient schema = self .definitions .values() .filter(|(n, _)| n == name) .map(|(_, s)| s) - .next()?; + .next() + .ok_or_else(|| { + MakeSchemaError::new( + "Could not find referenced schema.", + Schema::Ref(r.clone()), + ) + })?; } } } diff --git a/schemars/src/lib.rs b/schemars/src/lib.rs index 99195cc..5b36794 100644 --- a/schemars/src/lib.rs +++ b/schemars/src/lib.rs @@ -2,9 +2,12 @@ pub mod gen; pub mod make_schema; pub mod schema; +mod error; #[macro_use] mod macros; +pub use error::*; + pub use make_schema::MakeSchema; pub use schemars_derive::*; diff --git a/schemars/src/main.rs b/schemars/src/main.rs index 079039d..13c91bb 100644 --- a/schemars/src/main.rs +++ b/schemars/src/main.rs @@ -1,9 +1,8 @@ -use schemars::schema_for; -use schemars::schema::Schema; -use serde_json::Result; +use schemars::{schema_for, schema::Schema}; +use std::error::Error; -fn main() -> Result<()> { - let schema = schema_for!(Schema); +fn main() -> Result<(), Box> { + let schema = schema_for!(Schema)?; let json = serde_json::to_string_pretty(&schema)?; println!("{}", json); diff --git a/schemars/src/make_schema.rs b/schemars/src/make_schema.rs index 5b5dc35..f6564a5 100644 --- a/schemars/src/make_schema.rs +++ b/schemars/src/make_schema.rs @@ -1,5 +1,6 @@ use crate::gen::{BoolSchemas, SchemaGenerator}; use crate::schema::*; +use crate::Result; use serde_json::json; use std::collections::BTreeMap as Map; @@ -24,7 +25,7 @@ pub trait MakeSchema { true } - fn make_schema(gen: &mut SchemaGenerator) -> Schema; + fn make_schema(gen: &mut SchemaGenerator) -> Result; } macro_rules! no_ref_schema { @@ -52,12 +53,12 @@ macro_rules! simple_impl { impl MakeSchema for $type { no_ref_schema!(); - fn make_schema(_: &mut SchemaGenerator) -> Schema { - SchemaObject { + fn make_schema(_: &mut SchemaGenerator) -> Result { + Ok(SchemaObject { instance_type: Some(InstanceType::$instance_type.into()), ..Default::default() } - .into() + .into()) } } }; @@ -85,16 +86,16 @@ simple_impl!(() => Null); impl MakeSchema for char { no_ref_schema!(); - fn make_schema(_: &mut SchemaGenerator) -> Schema { + fn make_schema(_: &mut SchemaGenerator) -> Result { let mut extensions = Map::new(); extensions.insert("minLength".to_owned(), json!(1)); extensions.insert("maxLength".to_owned(), json!(1)); - SchemaObject { + Ok(SchemaObject { instance_type: Some(InstanceType::String.into()), extensions, ..Default::default() } - .into() + .into()) } } @@ -104,15 +105,15 @@ impl MakeSchema for char { impl MakeSchema for [T; 0] { no_ref_schema!(); - fn make_schema(_: &mut SchemaGenerator) -> Schema { + fn make_schema(_: &mut SchemaGenerator) -> Result { let mut extensions = Map::new(); extensions.insert("maxItems".to_owned(), json!(0)); - SchemaObject { + Ok(SchemaObject { instance_type: Some(InstanceType::Array.into()), extensions, ..Default::default() } - .into() + .into()) } } @@ -122,16 +123,16 @@ macro_rules! array_impls { impl MakeSchema for [T; $len] { no_ref_schema!(); - fn make_schema(gen: &mut SchemaGenerator) -> Schema { + fn make_schema(gen: &mut SchemaGenerator) -> Result { let mut extensions = Map::new(); extensions.insert("minItems".to_owned(), json!($len)); extensions.insert("maxItems".to_owned(), json!($len)); - SchemaObject { + Ok(SchemaObject { instance_type: Some(InstanceType::Array.into()), - items: Some(gen.subschema_for::().into()), + items: Some(gen.subschema_for::()?.into()), extensions, ..Default::default() - }.into() + }.into()) } } )+ @@ -153,19 +154,19 @@ macro_rules! tuple_impls { impl<$($name: MakeSchema),+> MakeSchema for ($($name,)+) { no_ref_schema!(); - fn make_schema(gen: &mut SchemaGenerator) -> Schema { + fn make_schema(gen: &mut SchemaGenerator) -> Result { let mut extensions = Map::new(); extensions.insert("minItems".to_owned(), json!($len)); extensions.insert("maxItems".to_owned(), json!($len)); let items = vec![ - $(gen.subschema_for::<$name>()),+ + $(gen.subschema_for::<$name>()?),+ ]; - SchemaObject { + Ok(SchemaObject { instance_type: Some(InstanceType::Array.into()), items: Some(items.into()), extensions, ..Default::default() - }.into() + }.into()) } } )+ @@ -201,13 +202,12 @@ macro_rules! seq_impl { { no_ref_schema!(); - fn make_schema(gen: &mut SchemaGenerator) -> Schema - { - SchemaObject { + fn make_schema(gen: &mut SchemaGenerator) -> Result { + Ok(SchemaObject { instance_type: Some(InstanceType::Array.into()), - items: Some(gen.subschema_for::().into()), + items: Some(gen.subschema_for::()?.into()), ..Default::default() - }.into() + }.into()) } } }; @@ -231,9 +231,8 @@ macro_rules! map_impl { { no_ref_schema!(); - fn make_schema(gen: &mut SchemaGenerator) -> Schema - { - let subschema = gen.subschema_for::(); + fn make_schema(gen: &mut SchemaGenerator) -> Result { + let subschema = gen.subschema_for::()?; let make_schema_bool = gen.settings().bool_schemas == BoolSchemas::AdditionalPropertiesOnly && subschema == gen.schema_for_any(); let mut extensions = Map::new(); @@ -245,11 +244,11 @@ macro_rules! map_impl { json!(subschema) } ); - SchemaObject { + Ok(SchemaObject { instance_type: Some(InstanceType::Object.into()), extensions, ..Default::default() - }.into() + }.into()) } } }; @@ -267,28 +266,25 @@ impl MakeSchema for Option { T::is_referenceable() } - fn make_schema(gen: &mut SchemaGenerator) -> Schema { - let mut schema = gen.subschema_for::(); + fn make_schema(gen: &mut SchemaGenerator) -> Result { + let mut schema = gen.subschema_for::()?; if gen.settings().option_add_null_type { schema = match schema { Schema::Bool(true) => Schema::Bool(true), - Schema::Bool(false) => <()>::make_schema(gen), + Schema::Bool(false) => <()>::make_schema(gen)?, schema => SchemaObject { - any_of: Some(vec![schema, <()>::make_schema(gen)]), + any_of: Some(vec![schema, <()>::make_schema(gen)?]), ..Default::default() } .into(), } } if gen.settings().option_nullable { - let deref = gen.try_get_schema_object(&schema); - debug_assert!(deref.is_some(), "Could not get schema object: {:?}", schema); - if let Some(mut deref) = deref { - deref.extensions.insert("nullable".to_owned(), json!(true)); - schema = Schema::Object(deref); - } + let mut deref = gen.get_schema_object(&schema)?; + deref.extensions.insert("nullable".to_owned(), json!(true)); + schema = Schema::Object(deref); }; - schema + Ok(schema) } } @@ -302,7 +298,7 @@ macro_rules! deref_impl { { no_ref_schema!(); - fn make_schema(gen: &mut SchemaGenerator) -> Schema { + fn make_schema(gen: &mut SchemaGenerator) -> Result { gen.subschema_for::() } } @@ -321,7 +317,7 @@ deref_impl!(<'a, T: ToOwned> MakeSchema for std::borrow::Cow<'a, T>); impl MakeSchema for serde_json::Value { no_ref_schema!(); - fn make_schema(gen: &mut SchemaGenerator) -> Schema { - gen.schema_for_any() + fn make_schema(gen: &mut SchemaGenerator) -> Result { + Ok(gen.schema_for_any()) } } diff --git a/schemars/tests/flatten.rs b/schemars/tests/flatten.rs index 9ad3812..dbcc780 100644 --- a/schemars/tests/flatten.rs +++ b/schemars/tests/flatten.rs @@ -1,6 +1,7 @@ use pretty_assertions::assert_eq; use schemars::{schema_for, MakeSchema}; use serde::{Deserialize, Serialize}; +use std::error::Error; #[derive(Serialize, Deserialize, Debug, PartialEq, MakeSchema)] struct Flat { @@ -32,6 +33,7 @@ struct Deep3 { #[test] #[ignore = "flattening is not yet implemented"] -fn flatten_schema() { - assert_eq!(schema_for!(Flat), schema_for!(Deep1)); +fn flatten_schema() -> Result<(), Box> { + assert_eq!(schema_for!(Flat)?, schema_for!(Deep1)?); + Ok(()) } diff --git a/schemars/tests/test.rs b/schemars/tests/test.rs index b6074af..697b47f 100644 --- a/schemars/tests/test.rs +++ b/schemars/tests/test.rs @@ -3,13 +3,14 @@ use schemars::schema::*; use schemars::{gen, schema_for}; use serde_json::{from_str, to_string_pretty}; use std::fs; +use std::error::Error; #[test] -fn schema_matches_default_settings() -> Result<(), Box> { +fn schema_matches_default_settings() -> Result<(), Box> { let expected_json = fs::read_to_string("tests/schema.json")?; let expected: Schema = from_str(&expected_json)?; - let actual = schema_for!(Schema); + let actual = schema_for!(Schema)?; fs::write("tests/schema.actual.json", to_string_pretty(&actual)?)?; assert_eq!(actual, expected, "Generated schema did not match saved schema - generated schema has been written to \"tests/schema.actual.json\"."); @@ -17,13 +18,13 @@ fn schema_matches_default_settings() -> Result<(), Box> { } #[test] -fn schema_matches_openapi3() -> Result<(), Box> { +fn schema_matches_openapi3() -> Result<(), Box> { let expected_json = fs::read_to_string("tests/schema-openapi3.json")?; let expected: Schema = from_str(&expected_json)?; let actual = gen::SchemaSettings::openapi3() .into_generator() - .into_root_schema_for::(); + .into_root_schema_for::()?; fs::write( "tests/schema-openapi3.actual.json", to_string_pretty(&actual)?, diff --git a/schemars_derive/src/lib.rs b/schemars_derive/src/lib.rs index 753e199..273c732 100644 --- a/schemars_derive/src/lib.rs +++ b/schemars_derive/src/lib.rs @@ -35,7 +35,7 @@ pub fn derive_make_schema(input: proc_macro::TokenStream) -> proc_macro::TokenSt let impl_block = quote! { #[automatically_derived] impl #impl_generics schemars::MakeSchema for #name #ty_generics #where_clause { - fn make_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { + fn make_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::Result { #schema } }; @@ -53,11 +53,11 @@ fn add_trait_bounds(generics: &mut Generics) { fn wrap_schema_fields(schema_contents: TokenStream) -> TokenStream { quote! { - schemars::schema::SchemaObject { + Ok(schemars::schema::SchemaObject { #schema_contents ..Default::default() } - .into() + .into()) } } @@ -131,19 +131,19 @@ fn schema_for_untagged_enum(variants: &[Variant]) -> TokenStream { fn schema_for_untagged_enum_variant(variant: &Variant) -> TokenStream { match variant.style { Style::Unit => quote! { - gen.subschema_for::<()>() + gen.subschema_for::<()>()? }, Style::Newtype => { let f = &variant.fields[0]; let ty = f.ty; quote_spanned! {f.original.span()=> - gen.subschema_for::<#ty>() + gen.subschema_for::<#ty>()? } } Style::Tuple => { let types = variant.fields.iter().map(|f| f.ty); quote! { - gen.subschema_for::<(#(#types),*)>() + gen.subschema_for::<(#(#types),*)>()? } } Style::Struct => schema_for_struct(&variant.fields), @@ -155,7 +155,7 @@ fn schema_for_struct(fields: &[Field]) -> TokenStream { let name = f.attrs.name().deserialize_name(); let ty = f.ty; quote_spanned! {f.original.span()=> - props.insert(#name.to_owned(), gen.subschema_for::<#ty>()); + props.insert(#name.to_owned(), gen.subschema_for::<#ty>()?); } }); wrap_schema_fields(quote! {