Skip to content

Commit 7e9772f

Browse files
authored
Implement JsonSchemaAs for OneOrMany (#719)
2 parents c5c35db + 5e36083 commit 7e9772f

File tree

3 files changed

+182
-1
lines changed

3 files changed

+182
-1
lines changed

serde_with/src/schemars_0_8.rs

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
//! see [`JsonSchemaAs`].
77
88
use crate::{
9-
formats::{Flexible, Format, Separator, Strict},
9+
formats::{Flexible, Format, PreferMany, PreferOne, Separator, Strict},
1010
prelude::{Schema as WrapSchema, *},
1111
};
1212
use ::schemars_0_8::{
@@ -794,6 +794,99 @@ map_first_last_wins_schema!(=> S indexmap_1::IndexMap<K, V, S>);
794794
#[cfg(feature = "indexmap_2")]
795795
map_first_last_wins_schema!(=> S indexmap_2::IndexMap<K, V, S>);
796796

797+
impl<T, TA> JsonSchema for WrapSchema<Vec<T>, OneOrMany<TA, PreferOne>>
798+
where
799+
WrapSchema<T, TA>: JsonSchema,
800+
{
801+
fn schema_name() -> String {
802+
std::format!(
803+
"OneOrMany<{}, PreferOne>",
804+
<WrapSchema<T, TA>>::schema_name()
805+
)
806+
}
807+
808+
fn schema_id() -> Cow<'static, str> {
809+
std::format!(
810+
"serde_with::OneOrMany<{}, PreferOne>",
811+
<WrapSchema<T, TA>>::schema_id()
812+
)
813+
.into()
814+
}
815+
816+
fn json_schema(gen: &mut SchemaGenerator) -> Schema {
817+
let single = gen.subschema_for::<WrapSchema<T, TA>>();
818+
let array = SchemaObject {
819+
instance_type: Some(InstanceType::Array.into()),
820+
array: Some(Box::new(ArrayValidation {
821+
items: Some(single.clone().into()),
822+
..Default::default()
823+
})),
824+
..Default::default()
825+
};
826+
827+
SchemaObject {
828+
subschemas: Some(Box::new(SubschemaValidation {
829+
any_of: Some(std::vec![single, array.into()]),
830+
..Default::default()
831+
})),
832+
..Default::default()
833+
}
834+
.into()
835+
}
836+
}
837+
838+
impl<T, TA> JsonSchema for WrapSchema<Vec<T>, OneOrMany<TA, PreferMany>>
839+
where
840+
WrapSchema<T, TA>: JsonSchema,
841+
{
842+
fn schema_name() -> String {
843+
std::format!(
844+
"OneOrMany<{}, PreferMany>",
845+
<WrapSchema<T, TA>>::schema_name()
846+
)
847+
}
848+
849+
fn schema_id() -> Cow<'static, str> {
850+
std::format!(
851+
"serde_with::OneOrMany<{}, PreferMany>",
852+
<WrapSchema<T, TA>>::schema_id()
853+
)
854+
.into()
855+
}
856+
857+
fn json_schema(gen: &mut SchemaGenerator) -> Schema {
858+
let inner = gen.subschema_for::<WrapSchema<T, TA>>();
859+
let single = SchemaObject {
860+
metadata: Some(Box::new(Metadata {
861+
write_only: true,
862+
..Default::default()
863+
})),
864+
subschemas: Some(Box::new(SubschemaValidation {
865+
all_of: Some(std::vec![inner.clone()]),
866+
..Default::default()
867+
})),
868+
..Default::default()
869+
};
870+
let array = SchemaObject {
871+
instance_type: Some(InstanceType::Array.into()),
872+
array: Some(Box::new(ArrayValidation {
873+
items: Some(Schema::from(single.clone()).into()),
874+
..Default::default()
875+
})),
876+
..Default::default()
877+
};
878+
879+
SchemaObject {
880+
subschemas: Some(Box::new(SubschemaValidation {
881+
any_of: Some(std::vec![single.into(), array.into()]),
882+
..Default::default()
883+
})),
884+
..Default::default()
885+
}
886+
.into()
887+
}
888+
}
889+
797890
impl<T, TA> JsonSchemaAs<T> for SetLastValueWins<TA>
798891
where
799892
TA: JsonSchemaAs<T>,

serde_with/tests/schemars_0_8.rs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,14 @@ mod snapshots {
352352
data: Vec<KvMapFlatten>,
353353
}
354354
}
355+
356+
one_or_many_prefer_one {
357+
#[serde(transparent)]
358+
struct Test {
359+
#[serde_as(as = "OneOrMany<_, PreferOne>")]
360+
data: Vec<i32>,
361+
}
362+
}
355363
}
356364
}
357365

@@ -870,3 +878,66 @@ mod key_value_map {
870878
check_valid_json_schema(&value);
871879
}
872880
}
881+
882+
mod one_or_many {
883+
use super::*;
884+
use serde_with::formats::{PreferMany, PreferOne};
885+
886+
#[serde_as]
887+
#[derive(Clone, Debug, JsonSchema, Serialize)]
888+
#[serde(transparent)]
889+
struct WithPreferOne(#[serde_as(as = "OneOrMany<_, PreferOne>")] Vec<i32>);
890+
891+
#[serde_as]
892+
#[derive(Clone, Debug, JsonSchema, Serialize)]
893+
#[serde(transparent)]
894+
struct WithPreferMany(#[serde_as(as = "OneOrMany<_, PreferMany>")] Vec<i32>);
895+
896+
#[test]
897+
fn test_prefer_one() {
898+
let single = WithPreferOne(vec![7]);
899+
let multiple = WithPreferOne(vec![1, 2, 3]);
900+
901+
check_valid_json_schema(&single);
902+
check_valid_json_schema(&multiple);
903+
}
904+
905+
#[test]
906+
fn test_prefer_one_matches() {
907+
check_matches_schema::<WithPreferOne>(&json!(7));
908+
check_matches_schema::<WithPreferOne>(&json!([1, 2, 3]));
909+
}
910+
911+
#[test]
912+
#[should_panic]
913+
fn test_prefer_one_no_invalid_type_one() {
914+
check_matches_schema::<WithPreferOne>(&json!("test"));
915+
}
916+
917+
#[test]
918+
#[should_panic]
919+
fn test_prefer_one_no_invalid_type_many() {
920+
check_matches_schema::<WithPreferOne>(&json!(["test", 1]));
921+
}
922+
923+
#[test]
924+
fn test_prefer_many() {
925+
let single = WithPreferMany(vec![7]);
926+
let multiple = WithPreferMany(vec![1, 2, 3]);
927+
928+
check_valid_json_schema(&single);
929+
check_valid_json_schema(&multiple);
930+
}
931+
932+
#[test]
933+
#[should_panic]
934+
fn test_prefer_many_no_invalid_type_one() {
935+
check_matches_schema::<WithPreferMany>(&json!("test"));
936+
}
937+
938+
#[test]
939+
#[should_panic]
940+
fn test_prefer_many_no_invalid_type_many() {
941+
check_matches_schema::<WithPreferMany>(&json!(["test", 1]));
942+
}
943+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-07/schema#",
3+
"title": "OneOrMany<int32, PreferOne>",
4+
"anyOf": [
5+
{
6+
"type": "integer",
7+
"format": "int32"
8+
},
9+
{
10+
"type": "array",
11+
"items": {
12+
"type": "integer",
13+
"format": "int32"
14+
}
15+
}
16+
]
17+
}

0 commit comments

Comments
 (0)