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:
parent
7d162a8fb5
commit
5a82498e28
6 changed files with 115 additions and 137 deletions
|
@ -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 {
|
||||
|
|
|
@ -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> {
|
||||
for _ in 0..100 {
|
||||
pub fn dereference_once(&self, schema: Schema) -> Result<Schema> {
|
||||
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) => {
|
||||
Schema::Object(SchemaObject {
|
||||
reference: Some(ref schema_ref),
|
||||
..
|
||||
}) => {
|
||||
let definitions_path = &self.settings().definitions_path;
|
||||
if !schema_ref.reference.starts_with(definitions_path) {
|
||||
if !schema_ref.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,
|
||||
));
|
||||
}
|
||||
|
||||
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 {
|
||||
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,
|
||||
))
|
||||
}
|
||||
|
|
|
@ -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::<()>());
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue