Change $ref to be part of a SchemaObject.

This allows other keyworlds to be used alongside $ref, as allowed in Json Schema 2019-09
This commit is contained in:
Graham Esau 2019-10-13 18:30:05 +01:00
parent 7d162a8fb5
commit 5a82498e28
6 changed files with 115 additions and 137 deletions

View file

@ -4,9 +4,9 @@ use crate::{JsonSchemaError, Map, Result, Set};
impl Schema {
pub fn flatten(self, other: Self) -> Result {
if is_null_type(&self) {
return Ok(other)
return Ok(other);
} else if is_null_type(&other) {
return Ok(self)
return Ok(self);
}
let s1 = ensure_object_type(self)?;
let s2 = ensure_object_type(other)?;
@ -38,7 +38,7 @@ impl_merge!(SchemaObject {
merge: definitions extensions instance_type enum_values
number string array object,
or: schema id title description format const_value all_of any_of one_of not
if_schema then_schema else_schema,
if_schema then_schema else_schema reference,
});
impl_merge!(NumberValidation {

View file

@ -18,6 +18,16 @@ pub enum BoolSchemas {
impl Default for SchemaSettings {
fn default() -> SchemaSettings {
SchemaSettings::new()
}
}
impl SchemaSettings {
pub fn new() -> SchemaSettings {
Self::draft07()
}
pub fn draft07() -> SchemaSettings {
SchemaSettings {
option_nullable: false,
option_add_null_type: true,
@ -25,14 +35,7 @@ impl Default for SchemaSettings {
definitions_path: "#/definitions/".to_owned(),
}
}
}
impl SchemaSettings {
pub fn new() -> SchemaSettings {
SchemaSettings {
..Default::default()
}
}
pub fn openapi3() -> SchemaSettings {
SchemaSettings {
option_nullable: true,
@ -66,21 +69,20 @@ impl SchemaGenerator {
}
pub fn schema_for_any(&self) -> Schema {
let schema: Schema = true.into();
if self.settings().bool_schemas == BoolSchemas::Enable {
true.into()
schema
} else {
Schema::Object(Default::default())
Schema::Object(schema.into())
}
}
pub fn schema_for_none(&self) -> Schema {
let schema: Schema = false.into();
if self.settings().bool_schemas == BoolSchemas::Enable {
false.into()
schema
} else {
Schema::Object(SchemaObject {
not: Some(Schema::Object(Default::default()).into()),
..Default::default()
})
Schema::Object(schema.into())
}
}
@ -94,7 +96,7 @@ impl SchemaGenerator {
if !self.definitions.contains_key(&name) {
self.insert_new_subschema_for::<T>(name)?;
}
Ok(Ref { reference }.into())
Ok(Schema::new_ref(reference))
}
fn insert_new_subschema_for<T: ?Sized + JsonSchema>(&mut self, name: String) -> Result<()> {
@ -148,49 +150,41 @@ impl SchemaGenerator {
})
}
pub fn get_schema_object(&self, mut schema: Schema) -> Result<SchemaObject> {
pub fn dereference_once(&self, schema: Schema) -> Result<Schema> {
match schema {
Schema::Object(SchemaObject {
reference: Some(ref schema_ref),
..
}) => {
let definitions_path = &self.settings().definitions_path;
if !schema_ref.starts_with(definitions_path) {
return Err(JsonSchemaError::new(
"Could not extract referenced schema name.",
schema,
));
}
let name = &schema_ref[definitions_path.len()..];
self.definitions.get(name).cloned().ok_or_else(|| {
JsonSchemaError::new("Could not find referenced schema.", schema)
})
}
s => Ok(s),
}
}
pub fn dereference(&self, mut schema: Schema) -> Result<Schema> {
if !schema.is_ref() {
return Ok(schema);
}
for _ in 0..100 {
match schema {
Schema::Object(obj) => return Ok(obj),
Schema::Bool(true) => return Ok(Default::default()),
Schema::Bool(false) => {
return Ok(SchemaObject {
not: Some(Schema::Bool(true).into()),
..Default::default()
})
}
Schema::Ref(schema_ref) => {
let definitions_path = &self.settings().definitions_path;
if !schema_ref.reference.starts_with(definitions_path) {
return Err(JsonSchemaError::new(
"Could not extract referenced schema name.",
Schema::Ref(schema_ref),
));
}
let name = &schema_ref.reference[definitions_path.len()..];
schema = self
.definitions
.get(name)
.ok_or_else(|| {
JsonSchemaError::new(
"Could not find referenced schema.",
Schema::Ref(schema_ref.clone()),
)
})?
.clone();
if schema == Schema::Ref(schema_ref) {
return Err(JsonSchemaError::new(
"Schema is referencing itself.",
schema,
));
}
}
schema = self.dereference_once(schema)?;
if !schema.is_ref() {
return Ok(schema);
}
}
Err(JsonSchemaError::new(
"Failed to dereference schema after 100 iterations - reference may be cyclic.",
"Failed to dereference schema after 100 iterations - references may be cyclic.",
schema,
))
}

View file

@ -34,7 +34,7 @@ impl<T: JsonSchema> JsonSchema for Option<T> {
}
}
if gen.settings().option_nullable {
let mut deref = gen.get_schema_object(schema)?;
let mut deref: SchemaObject = gen.dereference(schema)?.into();
deref.extensions.insert("nullable".to_owned(), json!(true));
schema = Schema::Object(deref);
};
@ -110,12 +110,7 @@ mod tests {
assert_eq!(schema.any_of.is_some(), true);
let any_of = schema.any_of.unwrap();
assert_eq!(any_of.len(), 2);
assert_eq!(
any_of[0],
Schema::Ref(Ref {
reference: "#/definitions/Foo".to_string()
})
);
assert_eq!(any_of[0], Schema::new_ref("#/definitions/Foo".to_string()));
assert_eq!(any_of[1], schema_for::<()>());
}

View file

@ -7,10 +7,22 @@ use serde_json::Value;
#[serde(untagged)]
pub enum Schema {
Bool(bool),
Ref(Ref),
Object(SchemaObject),
}
impl Schema {
pub fn new_ref(reference: String) -> Self {
SchemaObject::new_ref(reference).into()
}
pub fn is_ref(&self) -> bool {
match self {
Schema::Object(o) => o.is_ref(),
_ => false,
}
}
}
impl From<SchemaObject> for Schema {
fn from(o: SchemaObject) -> Self {
Schema::Object(o)
@ -23,18 +35,6 @@ impl From<bool> for Schema {
}
}
impl From<Ref> for Schema {
fn from(r: Ref) -> Self {
Schema::Ref(r)
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema)]
pub struct Ref {
#[serde(rename = "$ref")]
pub reference: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema)]
#[serde(rename_all = "camelCase", default)]
pub struct SchemaObject {
@ -68,7 +68,7 @@ pub struct SchemaObject {
pub then_schema: Option<Box<Schema>>,
#[serde(rename = "else", skip_serializing_if = "Option::is_none")]
pub else_schema: Option<Box<Schema>>,
#[serde(skip_serializing_if = "Map::is_empty")]
#[serde(alias = "$defs", skip_serializing_if = "Map::is_empty")]
pub definitions: Map<String, Schema>,
#[serde(flatten)]
pub number: NumberValidation,
@ -78,10 +78,45 @@ pub struct SchemaObject {
pub array: ArrayValidation,
#[serde(flatten)]
pub object: ObjectValidation,
#[serde(rename = "$ref", skip_serializing_if = "Option::is_none")]
pub reference: Option<String>,
#[serde(flatten)]
pub extensions: Map<String, Value>,
}
impl SchemaObject {
pub fn new_ref(reference: String) -> Self {
SchemaObject {
reference: Some(reference),
..Default::default()
}
}
pub fn is_ref(&self) -> bool {
if self.reference.is_none() {
return false;
}
let only_ref = SchemaObject {
reference: self.reference.clone(),
..Default::default()
};
*self == only_ref
}
}
impl From<Schema> for SchemaObject {
fn from(schema: Schema) -> Self {
match schema {
Schema::Object(o) => o,
Schema::Bool(true) => SchemaObject::default(),
Schema::Bool(false) => SchemaObject {
not: Some(Schema::Object(Default::default()).into()),
..Default::default()
},
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema)]
#[serde(rename_all = "camelCase", default)]
pub struct NumberValidation {
@ -144,7 +179,9 @@ pub struct ObjectValidation {
pub property_names: Option<Box<Schema>>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, JsonSchema)]
#[derive(
Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, JsonSchema,
)]
#[serde(rename_all = "camelCase")]
pub enum InstanceType {
Null,

View file

@ -5,9 +5,6 @@
{
"type": "boolean"
},
{
"$ref": "#/components/schemas/Ref"
},
{
"$ref": "#/components/schemas/SchemaObject"
}
@ -24,25 +21,11 @@
"integer"
]
},
"Ref": {
"type": "object",
"required": [
"$ref"
],
"properties": {
"$ref": {
"type": "string"
}
}
},
"Schema": {
"anyOf": [
{
"type": "boolean"
},
{
"$ref": "#/components/schemas/Ref"
},
{
"$ref": "#/components/schemas/SchemaObject"
}
@ -55,6 +38,10 @@
"type": "string",
"nullable": true
},
"$ref": {
"type": "string",
"nullable": true
},
"$schema": {
"type": "string",
"nullable": true
@ -64,9 +51,6 @@
{
"type": "boolean"
},
{
"$ref": "#/components/schemas/Ref"
},
{
"$ref": "#/components/schemas/SchemaObject"
}
@ -78,9 +62,6 @@
{
"type": "boolean"
},
{
"$ref": "#/components/schemas/Ref"
},
{
"$ref": "#/components/schemas/SchemaObject"
}
@ -109,9 +90,6 @@
{
"type": "boolean"
},
{
"$ref": "#/components/schemas/Ref"
},
{
"$ref": "#/components/schemas/SchemaObject"
}
@ -133,9 +111,6 @@
{
"type": "boolean"
},
{
"$ref": "#/components/schemas/Ref"
},
{
"$ref": "#/components/schemas/SchemaObject"
}
@ -166,9 +141,6 @@
{
"type": "boolean"
},
{
"$ref": "#/components/schemas/Ref"
},
{
"$ref": "#/components/schemas/SchemaObject"
}
@ -239,9 +211,6 @@
{
"type": "boolean"
},
{
"$ref": "#/components/schemas/Ref"
},
{
"$ref": "#/components/schemas/SchemaObject"
}
@ -276,9 +245,6 @@
{
"type": "boolean"
},
{
"$ref": "#/components/schemas/Ref"
},
{
"$ref": "#/components/schemas/SchemaObject"
}
@ -296,9 +262,6 @@
{
"type": "boolean"
},
{
"$ref": "#/components/schemas/Ref"
},
{
"$ref": "#/components/schemas/SchemaObject"
}

View file

@ -5,9 +5,6 @@
{
"type": "boolean"
},
{
"$ref": "#/definitions/Ref"
},
{
"$ref": "#/definitions/SchemaObject"
}
@ -24,25 +21,11 @@
"integer"
]
},
"Ref": {
"type": "object",
"required": [
"$ref"
],
"properties": {
"$ref": {
"type": "string"
}
}
},
"Schema": {
"anyOf": [
{
"type": "boolean"
},
{
"$ref": "#/definitions/Ref"
},
{
"$ref": "#/definitions/SchemaObject"
}
@ -57,6 +40,12 @@
"null"
]
},
"$ref": {
"type": [
"string",
"null"
]
},
"$schema": {
"type": [
"string",