-
-
Notifications
You must be signed in to change notification settings - Fork 234
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Not required over null #344
Comments
I have exactly the same issue. Right now you can't actually set that toggle via the JsonSchema implementation for that type. One has to use the #[derive(Debug, Default, Clone, Eq, PartialEq, EnumIs)]
pub enum Maybe<T> {
#[default]
Missing,
Null,
Value(T),
}
impl<T> JsonSchema for Maybe<T>
where
T: JsonSchema,
{
fn schema_name() -> String {
format!("Maybe_{}", T::schema_name())
}
fn schema_id() -> Cow<'static, str> {
Cow::Owned(format!("Maybe<{}>", T::schema_id()))
}
fn json_schema(gen: &mut SchemaGenerator) -> Schema {
let mut schema = gen.subschema_for::<T>();
if gen.settings().option_add_null_type {
schema = match schema {
Schema::Bool(true) => Schema::Bool(true),
Schema::Bool(false) => <()>::json_schema(gen),
Schema::Object(SchemaObject {
instance_type: Some(ref mut instance_type),
..
}) => {
add_null_type(instance_type);
schema
}
schema => SchemaObject {
// TODO technically the schema already accepts null, so this may be unnecessary
subschemas: Some(Box::new(SubschemaValidation {
any_of: Some(vec![schema, <()>::json_schema(gen)]),
..Default::default()
})),
..Default::default()
}
.into(),
}
}
if gen.settings().option_nullable {
let mut schema_obj = schema.into_object();
schema_obj
.extensions
.insert("nullable".to_owned(), json!(true));
schema = Schema::Object(schema_obj);
};
schema
}
}
#[cfg(test)]
mod tests {
use super::*;
use schemars::schema_for;
#[derive(Debug, Serialize, Deserialize, Default, JsonSchema)]
#[serde(default)]
struct TestStruct {
check: bool,
a: Maybe<String>,
b: Maybe<u32>,
#[schemars(skip_serializing_if = "Maybe::is_missing")]
c: Maybe<u32>,
}
#[test]
fn it_generates_the_correct_jsonschema() {
let schema = schema_for!(TestStruct);
let serialized = serde_json::to_string_pretty(&schema).unwrap();
println!("{}", serialized);
}
} Which results in this schema: {
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "TestStruct",
"type": "object",
"properties": {
"a": {
"default": null,
"allOf": [
{
"$ref": "#/definitions/Maybe_String"
}
]
},
"b": {
"default": null,
"allOf": [
{
"$ref": "#/definitions/Maybe_uint32"
}
]
},
"c": {
"$ref": "#/definitions/Maybe_uint32"
},
"check": {
"default": false,
"type": "boolean"
}
},
"definitions": {
"Maybe_String": {
"type": [
"string",
"null"
]
},
"Maybe_uint32": {
"type": [
"integer",
"null"
],
"format": "uint32",
"minimum": 0.0
}
}
} Note The This is the only way I have found, that allows me to make a field in a struct truly optional. There's no way I can set the default value in my JsonSchema trait implementation. JsonSchema derive result for the TestStruct
// Recursive expansion of JsonSchema macro
// ========================================
const _: () = {
#[automatically_derived]
#[allow(unused_braces)]
impl schemars::JsonSchema for TestStruct {
fn schema_name() -> std::string::String {
"TestStruct".to_owned()
}
fn schema_id() -> std::borrow::Cow<'static, str> {
std::borrow::Cow::Borrowed(std::concat!(std::module_path!(), "::", "TestStruct"))
}
fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
{
let container_default = Self::default();
let mut schema_object = schemars::schema::SchemaObject {
instance_type: Some(schemars::schema::InstanceType::Object.into()),
..Default::default()
};
let object_validation = schema_object.object();
{
schemars::_private::insert_object_property::<bool>(
object_validation,
"check",
true,
false,
schemars::_private::metadata::add_default(
gen.subschema_for::<bool>(),
Some(container_default.check)
.and_then(|d| schemars::_schemars_maybe_to_value!(d)),
),
);
}
{
schemars::_private::insert_object_property::<Maybe<String>>(
object_validation,
"a",
true,
false,
schemars::_private::metadata::add_default(
gen.subschema_for::<Maybe<String>>(),
Some(container_default.a)
.and_then(|d| schemars::_schemars_maybe_to_value!(d)),
),
);
}
{
schemars::_private::insert_object_property::<Maybe<u32>>(
object_validation,
"b",
true,
false,
schemars::_private::metadata::add_default(
gen.subschema_for::<Maybe<u32>>(),
Some(container_default.b)
.and_then(|d| schemars::_schemars_maybe_to_value!(d)),
),
);
}
{
schemars::_private::insert_object_property::<Maybe<u32>>(
object_validation,
"c",
true,
false,
schemars::_private::metadata::add_default(
gen.subschema_for::<Maybe<u32>>(),
{
let default = container_default.c;
if Maybe::is_missing(&default) {
None
} else {
Some(default)
}
}
.and_then(|d| schemars::_schemars_maybe_to_value!(d)),
),
);
}
schemars::schema::Schema::Object(schema_object)
}
}
};
}; @GREsau Do you have any recommendations or plans on allowing for more control in the JsonSchema implementation for a type, such that we can override the default value? As of now, It seems that it is only possible to modify the subschema for that type. Looking into the code at https://github.com/GREsau/schemars/blob/v0/schemars/src/_private.rs it seems trivial to change the Hope this explains the issue well. I'd be happy to help out here @GREsau as I know you're also working on the v1.0 of the library, and maybe now may still be a good point in time to introduce a breaking change such as this. Retrofitting this kind of change to the v0.8 is probably not possible, right? Full reproduction
use schemars::JsonSchema;
use schemars::gen::SchemaGenerator;
use schemars::schema::{InstanceType, Schema, SchemaObject, SingleOrVec, SubschemaValidation};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_json::json;
use std::borrow::Cow;
#[derive(Debug, Default, Clone, Eq, PartialEq)]
pub enum Maybe<T> {
#[default]
Missing,
Null,
Value(T),
}
impl<T> Maybe<T> {
pub fn is_missing(&self) -> bool {
matches!(self, Maybe::Missing)
}
}
impl<T> From<Option<T>> for Maybe<T> {
#[inline]
fn from(opt: Option<T>) -> Maybe<T> {
match opt {
Some(v) => Maybe::Value(v),
None => Maybe::Null,
}
}
}
impl<'de, T> Deserialize<'de> for Maybe<T>
where
T: Deserialize<'de>,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Option::deserialize(deserializer).map(Into::into)
}
}
impl<T: Serialize> Serialize for Maybe<T> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
Maybe::Value(v) => v.serialize(serializer),
Maybe::Null | &Maybe::Missing => serializer.serialize_none(),
}
}
}
impl<T> JsonSchema for Maybe<T>
where
T: JsonSchema,
{
fn schema_name() -> String {
format!("Maybe_{}", T::schema_name())
}
fn schema_id() -> Cow<'static, str> {
Cow::Owned(format!("Maybe<{}>", T::schema_id()))
}
fn json_schema(gen: &mut SchemaGenerator) -> Schema {
let mut schema = gen.subschema_for::<T>();
if gen.settings().option_add_null_type {
schema = match schema {
Schema::Bool(true) => Schema::Bool(true),
Schema::Bool(false) => <()>::json_schema(gen),
Schema::Object(SchemaObject {
instance_type: Some(ref mut instance_type),
..
}) => {
add_null_type(instance_type);
schema
}
schema => SchemaObject {
// TODO technically the schema already accepts null, so this may be unnecessary
subschemas: Some(Box::new(SubschemaValidation {
any_of: Some(vec![schema, <()>::json_schema(gen)]),
..Default::default()
})),
..Default::default()
}
.into(),
}
}
if gen.settings().option_nullable {
let mut schema_obj = schema.into_object();
schema_obj
.extensions
.insert("nullable".to_owned(), json!(true));
schema = Schema::Object(schema_obj);
};
schema
}
}
fn add_null_type(instance_type: &mut SingleOrVec<InstanceType>) {
match instance_type {
SingleOrVec::Single(ty) if **ty != InstanceType::Null => {
*instance_type = vec![**ty, InstanceType::Null].into()
}
SingleOrVec::Vec(ty) if !ty.contains(&InstanceType::Null) => ty.push(InstanceType::Null),
_ => {}
};
}
#[cfg(test)]
mod tests {
use super::*;
use schemars::{JsonSchema, schema_for};
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Default, JsonSchema)]
#[serde(default)]
struct TestStruct {
check: bool,
a: Maybe<String>,
b: Maybe<u32>,
#[schemars(skip_serializing_if = "Maybe::is_missing")]
c: Maybe<u32>,
}
#[test]
fn it_generates_the_correct_jsonschema() {
let schema = schema_for!(TestStruct);
let serialized = serde_json::to_string_pretty(&schema).unwrap();
println!("{}", serialized);
}
} |
Is it possible to set a value as not required? Using an optional sets the value as nullable with a validation of "allOf". I just want the value to be optional not nullable. I would expect this to be the case when option_nullable is set and option_add_null_type is not set as in the openapi3 function. This does not seem to be the case.
The text was updated successfully, but these errors were encountered: