Skip to content

Commit 731b00b

Browse files
authored
feat: automatic schemars support for Base64 (#949)
Hey @jonasbb, here is a follow-up PR that adds `schemars` support for `#[serde_as(as = "Base64")]`. Without it, devs have to annotate it every time: ```rust #[serde_as] #[derive(Serialize, Deserialize, JsonSchema)] struct B64 { #[serde_as(as = "Base64")] #[schemars(with = "String")] // <- not needed anymore bytes: Vec<u8>, } ``` Will be happy to address any comments and hope it can get included in next release as well 🤞🏻
2 parents 93d6d9d + 84f2e40 commit 731b00b

10 files changed

Lines changed: 143 additions & 9 deletions

File tree

serde_with/Cargo.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ macros = ["dep:serde_with_macros"]
135135
##
136136
## This pulls in [`schemars` v0.8](::schemars_0_8) as a dependency. It will also implicitly enable
137137
## the `std` feature as `schemars` is not `#[no_std]`.
138-
schemars_0_8 = ["dep:schemars_0_8", "std", "serde_with_macros?/schemars_0_8"]
138+
schemars_0_8 = ["dep:schemars_0_8", "std", "serde_with_macros?/schemars_0_8", "dep:serde_json"]
139139
## This feature enables integration with `schemars` v0.9
140140
## This makes `#[derive(JsonSchema)]` pick up the correct schema for the type
141141
## used within `#[serde_as(as = ...)]`.
@@ -291,17 +291,17 @@ required-features = ["smallvec_1", "macros"]
291291
[[test]]
292292
name = "schemars_0_8"
293293
path = "tests/schemars_0_8/main.rs"
294-
required-features = ["schemars_0_8", "base58", "hex"]
294+
required-features = ["schemars_0_8", "base58", "base64", "hex"]
295295

296296
[[test]]
297297
name = "schemars_0_9"
298298
path = "tests/schemars_0_9/main.rs"
299-
required-features = ["schemars_0_9", "std", "base58", "hex"]
299+
required-features = ["schemars_0_9", "std", "base58", "base64", "hex"]
300300

301301
[[test]]
302302
name = "schemars_1"
303303
path = "tests/schemars_1/main.rs"
304-
required-features = ["schemars_1", "std", "base58", "hex"]
304+
required-features = ["schemars_1", "std", "base58", "base64", "hex"]
305305

306306
[package.metadata.docs.rs]
307307
all-features = true

serde_with/src/schemars_0_8.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,31 @@ impl<T, A: base58::Alphabet> JsonSchemaAs<T> for base58::Base58<A> {
428428
}
429429
}
430430

431+
#[cfg(feature = "base64")]
432+
impl<T, A: base64::Alphabet, F: formats::Format> JsonSchemaAs<T> for base64::Base64<A, F> {
433+
fn schema_name() -> String {
434+
"Base64<A, F>".into()
435+
}
436+
437+
fn schema_id() -> Cow<'static, str> {
438+
"serde_with::base64::Base64<A, F>".into()
439+
}
440+
441+
fn json_schema(_: &mut SchemaGenerator) -> Schema {
442+
SchemaObject {
443+
instance_type: Some(InstanceType::String.into()),
444+
// See <https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-00#rfc.section.8.3>
445+
extensions: [("contentEncoding".to_string(), serde_json::json!("base64"))].into(),
446+
..Default::default()
447+
}
448+
.into()
449+
}
450+
451+
fn is_referenceable() -> bool {
452+
false
453+
}
454+
}
455+
431456
#[cfg(feature = "hex")]
432457
impl<T, F: formats::Format> JsonSchemaAs<T> for hex::Hex<F> {
433458
fn schema_name() -> String {

serde_with/src/schemars_0_9.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,29 @@ impl<T, A: base58::Alphabet> JsonSchemaAs<T> for base58::Base58<A> {
428428
}
429429
}
430430

431+
#[cfg(feature = "base64")]
432+
impl<T, A: base64::Alphabet, F: formats::Format> JsonSchemaAs<T> for base64::Base64<A, F> {
433+
fn schema_name() -> Cow<'static, str> {
434+
"Base64<A, F>".into()
435+
}
436+
437+
fn schema_id() -> Cow<'static, str> {
438+
"serde_with::base64::Base64<A, F>".into()
439+
}
440+
441+
fn json_schema(_: &mut SchemaGenerator) -> Schema {
442+
json_schema!({
443+
"type": "string",
444+
// See <https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-00#rfc.section.8.3>
445+
"contentEncoding": "base64",
446+
})
447+
}
448+
449+
fn inline_schema() -> bool {
450+
true
451+
}
452+
}
453+
431454
#[cfg(feature = "hex")]
432455
impl<T, F: formats::Format> JsonSchemaAs<T> for hex::Hex<F> {
433456
fn schema_name() -> Cow<'static, str> {

serde_with/src/schemars_1.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,29 @@ impl<T, A: base58::Alphabet> JsonSchemaAs<T> for base58::Base58<A> {
430430
}
431431
}
432432

433+
#[cfg(feature = "base64")]
434+
impl<T, A: base64::Alphabet, F: formats::Format> JsonSchemaAs<T> for base64::Base64<A, F> {
435+
fn schema_name() -> Cow<'static, str> {
436+
"Base64<A, F>".into()
437+
}
438+
439+
fn schema_id() -> Cow<'static, str> {
440+
"serde_with::base64::Base64<A, F>".into()
441+
}
442+
443+
fn json_schema(_: &mut SchemaGenerator) -> Schema {
444+
json_schema!({
445+
"type": "string",
446+
// See <https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-00#rfc.section.8.3>
447+
"contentEncoding": "base64",
448+
})
449+
}
450+
451+
fn inline_schema() -> bool {
452+
true
453+
}
454+
}
455+
433456
#[cfg(feature = "hex")]
434457
impl<T, F: formats::Format> JsonSchemaAs<T> for hex::Hex<F> {
435458
fn schema_name() -> Cow<'static, str> {

serde_with/tests/schemars_0_8/main.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use expect_test::expect_file;
55
use schemars::JsonSchema;
66
use serde::Serialize;
77
use serde_json::json;
8-
use serde_with::{base58::*, hex::*, *};
8+
use serde_with::{base58::*, base64::*, hex::*, *};
99
use std::collections::{BTreeMap, BTreeSet};
1010

1111
// This avoids us having to add `#[schemars(crate = "::schemars_0_8")]` all
@@ -107,6 +107,15 @@ fn schemars_basic() {
107107
/// charset.
108108
#[serde_as(as = "Base58<Flickr>")]
109109
base58_flickr: Vec<u8>,
110+
111+
/// A vector of bytes that's serialized as a base64 string.
112+
#[serde_as(as = "Base64")]
113+
base64: Vec<u8>,
114+
115+
/// A vector of bytes that's serialized as a base64 string in URL-safe
116+
/// charset.
117+
#[serde_as(as = "Base64<UrlSafe>")]
118+
base64_urlsafe: Vec<u8>,
110119
}
111120

112121
let schema = schemars::schema_for!(Basic);

serde_with/tests/schemars_0_8/schemars_basic.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
"bare_field",
77
"base58",
88
"base58_flickr",
9+
"base64",
10+
"base64_urlsafe",
911
"box_same",
1012
"display_from_str",
1113
"lowercase_hex",
@@ -28,6 +30,16 @@
2830
"description": "A vector of bytes that's serialized as a base58 string in `Flickr` charset.",
2931
"type": "string"
3032
},
33+
"base64": {
34+
"description": "A vector of bytes that's serialized as a base64 string.",
35+
"type": "string",
36+
"contentEncoding": "base64"
37+
},
38+
"base64_urlsafe": {
39+
"description": "A vector of bytes that's serialized as a base64 string in URL-safe charset.",
40+
"type": "string",
41+
"contentEncoding": "base64"
42+
},
3143
"box_same": {
3244
"description": "This checks that Same still works when wrapped in a box.",
3345
"type": "integer",

serde_with/tests/schemars_0_9/main.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use expect_test::expect_file;
66
use schemars::JsonSchema;
77
use serde::Serialize;
88
use serde_json::json;
9-
use serde_with::{base58::*, hex::*, *};
9+
use serde_with::{base58::*, base64::*, hex::*, *};
1010
use std::collections::{BTreeMap, BTreeSet};
1111

1212
// This avoids us having to add `#[schemars(crate = "::schemars_0_9")]` all
@@ -108,6 +108,15 @@ fn schemars_basic() {
108108
/// charset.
109109
#[serde_as(as = "Base58<Flickr>")]
110110
base58_flickr: Vec<u8>,
111+
112+
/// A vector of bytes that's serialized as a base64 string.
113+
#[serde_as(as = "Base64")]
114+
base64: Vec<u8>,
115+
116+
/// A vector of bytes that's serialized as a base64 string in URL-safe
117+
/// charset.
118+
#[serde_as(as = "Base64<UrlSafe>")]
119+
base64_urlsafe: Vec<u8>,
111120
}
112121

113122
let schema = schemars::schema_for!(Basic);

serde_with/tests/schemars_0_9/schemars_basic.json

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,16 @@
5151
"base58_flickr": {
5252
"description": "A vector of bytes that's serialized as a base58 string in `Flickr`\n charset.",
5353
"type": "string"
54+
},
55+
"base64": {
56+
"description": "A vector of bytes that's serialized as a base64 string.",
57+
"type": "string",
58+
"contentEncoding": "base64"
59+
},
60+
"base64_urlsafe": {
61+
"description": "A vector of bytes that's serialized as a base64 string in URL-safe\n charset.",
62+
"type": "string",
63+
"contentEncoding": "base64"
5464
}
5565
},
5666
"required": [
@@ -62,6 +72,8 @@
6272
"lowercase_hex",
6373
"uppercase_hex",
6474
"base58",
65-
"base58_flickr"
75+
"base58_flickr",
76+
"base64",
77+
"base64_urlsafe"
6678
]
6779
}

serde_with/tests/schemars_1/main.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use expect_test::expect_file;
66
use schemars::JsonSchema;
77
use serde::Serialize;
88
use serde_json::json;
9-
use serde_with::{base58::*, hex::*, *};
9+
use serde_with::{base58::*, base64::*, hex::*, *};
1010
use std::collections::{BTreeMap, BTreeSet};
1111

1212
// This avoids us having to add `#[schemars(crate = "::schemars_1")]` all
@@ -108,6 +108,15 @@ fn schemars_basic() {
108108
/// charset.
109109
#[serde_as(as = "Base58<Flickr>")]
110110
base58_flickr: Vec<u8>,
111+
112+
/// A vector of bytes that's serialized as a base64 string.
113+
#[serde_as(as = "Base64")]
114+
base64: Vec<u8>,
115+
116+
/// A vector of bytes that's serialized as a base64 string in URL-safe
117+
/// charset.
118+
#[serde_as(as = "Base64<UrlSafe>")]
119+
base64_urlsafe: Vec<u8>,
111120
}
112121

113122
let schema = schemars::schema_for!(Basic);

serde_with/tests/schemars_1/schemars_basic.json

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,16 @@
5151
"base58_flickr": {
5252
"description": "A vector of bytes that's serialized as a base58 string in `Flickr`\ncharset.",
5353
"type": "string"
54+
},
55+
"base64": {
56+
"description": "A vector of bytes that's serialized as a base64 string.",
57+
"type": "string",
58+
"contentEncoding": "base64"
59+
},
60+
"base64_urlsafe": {
61+
"description": "A vector of bytes that's serialized as a base64 string in URL-safe\ncharset.",
62+
"type": "string",
63+
"contentEncoding": "base64"
5464
}
5565
},
5666
"required": [
@@ -62,6 +72,8 @@
6272
"lowercase_hex",
6373
"uppercase_hex",
6474
"base58",
65-
"base58_flickr"
75+
"base58_flickr",
76+
"base64",
77+
"base64_urlsafe"
6678
]
6779
}

0 commit comments

Comments
 (0)