I've just noticed that this crate is using the default Deserialization contract of schemars for OperationOutputs such as for axum::Json.
This is incorrect but has probably gone unnoticed until now, because the Deserialization contract of schemars generates a superset jsonschema of the one that would've been generated by the Serialization contract.
Let me highlight an example:
#[derive(Debug, Serialize, JsonSchema)]
pub struct Response {
pub rating: Option<i64>,
}
which would currently result in a response schema as such:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Response",
"type": "object",
"properties": {
"rating": {
"type": [
"integer",
"null"
],
"format": "int64"
}
}
}
But that is incorrect in the context of a serialization operation, where the rating field should be defined as required, as it will always exist in a response, but be either a null or i64.
The correct jsonschema would look like this:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Response",
"type": "object",
"properties": {
"rating": {
"type": [
"integer",
"null"
],
"format": "int64"
}
},
"required": [
"rating"
]
}
Note the required field. At the moment aide always utilizies the Deserialization contract, even for responses.
We'll probably have to add two schema generators to the gen context and use the serialization generator for all OperationOuput implementations.
Although this change may initially seem like a breaking change, all it really does is narrow down the possible states of fields within responses.
What it used to generate: "Field A can be an i64, null, or not exist"
What it would now generate: "Field A can be an i64 or null"
So we're really just removing the case of "not exists" from the field schema.
Here's a complete snippet to reproduce it yourself:
#[cfg(test)]
mod tests {
use schemars::generate::SchemaSettings;
use schemars::{JsonSchema, SchemaGenerator};
use serde::Serialize;
#[derive(Debug, Serialize, JsonSchema)]
pub struct Response {
pub rating: Option<i64>,
}
#[test]
fn schema_for_serialize() {
let generate = SchemaGenerator::new(SchemaSettings::default().for_serialize()); // <- Uses the Serialize schemars contract
let schema = generate.into_root_schema_for::<Response>();
println!("{}", serde_json::to_string_pretty(&schema).unwrap());
}
#[test]
fn schema_for_deserialize() {
let generate = SchemaGenerator::new(SchemaSettings::default().for_deserialize()); // <- Uses the Deserialize schemars contract
let schema = generate.into_root_schema_for::<Response>();
println!("{}", serde_json::to_string_pretty(&schema).unwrap());
}
}
I've just noticed that this crate is using the default
Deserializationcontract of schemars forOperationOutputssuch as foraxum::Json.This is incorrect but has probably gone unnoticed until now, because the
Deserializationcontract of schemars generates a superset jsonschema of the one that would've been generated by theSerializationcontract.Let me highlight an example:
which would currently result in a response schema as such:
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Response", "type": "object", "properties": { "rating": { "type": [ "integer", "null" ], "format": "int64" } } }But that is incorrect in the context of a serialization operation, where the
ratingfield should be defined as required, as it will always exist in a response, but be either anullori64.The correct jsonschema would look like this:
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Response", "type": "object", "properties": { "rating": { "type": [ "integer", "null" ], "format": "int64" } }, "required": [ "rating" ] }Note the
requiredfield. At the moment aide always utilizies theDeserializationcontract, even for responses.We'll probably have to add two schema generators to the gen context and use the serialization generator for all
OperationOuputimplementations.Although this change may initially seem like a breaking change, all it really does is narrow down the possible states of fields within responses.
What it used to generate: "Field A can be an i64, null, or not exist"
What it would now generate: "Field A can be an i64 or null"
So we're really just removing the case of "not exists" from the field schema.
Here's a complete snippet to reproduce it yourself: