diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d37634e..7c72306 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,7 @@ jobs: - nightly include: - rust: 1.60.0 - test_features: "--features impl_json_schema" + test_features: "" allow_failure: false - rust: stable test_features: "--all-features" diff --git a/CHANGELOG.md b/CHANGELOG.md index 19d073a..a99e310 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,24 @@ # Changelog +## [1.0.0-alpha.3] - 2024-08-10 + +### Added + +- `#[schemars(transform = some::transform)]` for applying arbitrary modifications to generated schemas. `some::transform` must be an expression of type `schemars::transform::Transform` - note that this can be a function with the signature `fn(&mut Schema) -> ()`. +- `SchemaSettings` and `SchemaGenerator` are both now `Send` + +### Changed (_⚠️ breaking changes ⚠️_) + +- `visit` module and `Visitor` trait have been replace with `transform` and `Transform` respectively. Accordingly, these items have been renamed: + - `SchemaSettings::visitors` -> `SchemaSettings::transforms` + - `SchemaSettings::with_visitor` -> `SchemaSettings::with_transform` + - `SchemaGenerator::visitors_mut` -> `SchemaGenerator::transforms_mut` + - `GenVisitor` -> `GenTransform` + - `Visitor::visit_schema` -> `Transform::transform` + - `visit::visit_schema` -> `transform::transform_subschemas` +- `GenTransform` must also impl `Send`, but no longer needs to impl `Debug` +- Doc comments no longer have newlines collapsed when generating the `description` property (https://github.com/GREsau/schemars/pull/310) + ## [1.0.0-alpha.2] - 2024-06-05 ### Added diff --git a/Cargo.lock b/Cargo.lock index 92ed468..e8be803 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,12 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "arrayvec" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" - [[package]] name = "arrayvec" version = "0.7.4" @@ -29,17 +23,6 @@ dependencies = [ "serde", ] -[[package]] -name = "bigdecimal" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6773ddc0eafc0e509fb60e48dff7f450f8e674a0686ae8605e8d9901bd5eefa" -dependencies = [ - "num-bigint", - "num-integer", - "num-traits", -] - [[package]] name = "bigdecimal" version = "0.4.2" @@ -168,12 +151,6 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - [[package]] name = "hashbrown" version = "0.14.2" @@ -196,17 +173,6 @@ dependencies = [ "unicode-normalization", ] -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", - "serde", -] - [[package]] name = "indexmap" version = "2.0.2" @@ -214,8 +180,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897" dependencies = [ "equivalent", - "hashbrown 0.14.2", - "serde", + "hashbrown", ] [[package]] @@ -284,29 +249,49 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.69" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.33" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] +[[package]] +name = "ref-cast" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4846d4c50d1721b1a3bef8af76924eef20d5e723647333798c1b519b3a9473f" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fddb4f8d99b0a2ebafc65a87a69a7b9875e4b1ae1f00db265d300ef7f28bccc" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "rust_decimal" version = "1.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4c4216490d5a413bc6d10fa4742bd7d4955941d062c0ef873141d6b0e7b30fd" dependencies = [ - "arrayvec 0.7.4", + "arrayvec", "num-traits", ] @@ -318,20 +303,18 @@ checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "schemars" -version = "0.8.21" +version = "1.0.0-alpha.3" dependencies = [ - "arrayvec 0.5.2", - "arrayvec 0.7.4", - "bigdecimal 0.3.1", - "bigdecimal 0.4.2", + "arrayvec", + "bigdecimal", "bytes", "chrono", "dyn-clone", "either", "enumset", - "indexmap 1.9.3", - "indexmap 2.0.2", + "indexmap", "pretty_assertions", + "ref-cast", "rust_decimal", "schemars_derive", "semver", @@ -341,13 +324,12 @@ dependencies = [ "smol_str", "trybuild", "url", - "uuid 0.8.2", - "uuid 1.5.0", + "uuid", ] [[package]] name = "schemars_derive" -version = "0.8.21" +version = "1.0.0-alpha.3" dependencies = [ "pretty_assertions", "proc-macro2", @@ -361,9 +343,6 @@ name = "semver" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" -dependencies = [ - "serde", -] [[package]] name = "serde" @@ -402,6 +381,7 @@ version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" dependencies = [ + "indexmap", "itoa", "ryu", "serde", @@ -415,18 +395,15 @@ checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" [[package]] name = "smol_str" -version = "0.1.24" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fad6c857cbab2627dcf01ec85a623ca4e7dcb5691cbaa3d7fb7653671f0d09c9" -dependencies = [ - "serde", -] +checksum = "e6845563ada680337a52d43bb0b29f396f2d911616f6573012645b9e3d048a49" [[package]] name = "syn" -version = "2.0.38" +version = "2.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" +checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" dependencies = [ "proc-macro2", "quote", @@ -504,12 +481,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "uuid" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" - [[package]] name = "uuid" version = "1.5.0" diff --git a/README.md b/README.md index 9322417..18c6d4b 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,13 @@ # Schemars > [!NOTE] -> Schemars 1.0 is in development on [the v1 branch](https://github.com/GREsau/schemars/tree/v1), see [draft PR 290](https://github.com/GREsau/schemars/pull/290) for updates +> This branch is for the current v1 alpha version of Schemars which is still under development. +> For the current stable release of Schemars (v0.8.x), see the [v0 branch](https://github.com/GREsau/schemars/tree/v0). [](https://github.com/GREsau/schemars/actions) [](https://crates.io/crates/schemars) -[](https://docs.rs/schemars) -[](https://blog.rust-lang.org/2022/04/07/Rust-1.60.0.html) +[](https://docs.rs/schemars) +[](https://blog.rust-lang.org/2022/04/07/Rust-1.60.0.html) Generate JSON Schema documents from Rust code @@ -39,10 +40,9 @@ println!("{}", serde_json::to_string_pretty(&schema).unwrap()); ```json { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "MyStruct", "type": "object", - "required": ["my_bool", "my_int"], "properties": { "my_bool": { "type": "boolean" @@ -54,7 +54,7 @@ println!("{}", serde_json::to_string_pretty(&schema).unwrap()); "my_nullable_enum": { "anyOf": [ { - "$ref": "#/definitions/MyEnum" + "$ref": "#/$defs/MyEnum" }, { "type": "null" @@ -62,26 +62,25 @@ println!("{}", serde_json::to_string_pretty(&schema).unwrap()); ] } }, - "definitions": { + "required": ["my_int", "my_bool"], + "$defs": { "MyEnum": { - "anyOf": [ + "oneOf": [ { "type": "object", - "required": ["StringNewType"], "properties": { "StringNewType": { "type": "string" } }, - "additionalProperties": false + "additionalProperties": false, + "required": ["StringNewType"] }, { "type": "object", - "required": ["StructVariant"], "properties": { "StructVariant": { "type": "object", - "required": ["floats"], "properties": { "floats": { "type": "array", @@ -90,10 +89,12 @@ println!("{}", serde_json::to_string_pretty(&schema).unwrap()); "format": "float" } } - } + }, + "required": ["floats"] } }, - "additionalProperties": false + "additionalProperties": false, + "required": ["StructVariant"] } ] } @@ -137,24 +138,23 @@ println!("{}", serde_json::to_string_pretty(&schema).unwrap()); ```json { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "MyStruct", "type": "object", - "required": ["myBool", "myNumber"], "properties": { "myBool": { "type": "boolean" }, "myNullableEnum": { - "default": null, "anyOf": [ { - "$ref": "#/definitions/MyEnum" + "$ref": "#/$defs/MyEnum" }, { "type": "null" } - ] + ], + "default": null }, "myNumber": { "type": "integer", @@ -162,7 +162,8 @@ println!("{}", serde_json::to_string_pretty(&schema).unwrap()); } }, "additionalProperties": false, - "definitions": { + "required": ["myNumber", "myBool"], + "$defs": { "MyEnum": { "anyOf": [ { @@ -170,7 +171,6 @@ println!("{}", serde_json::to_string_pretty(&schema).unwrap()); }, { "type": "object", - "required": ["floats"], "properties": { "floats": { "type": "array", @@ -179,7 +179,8 @@ println!("{}", serde_json::to_string_pretty(&schema).unwrap()); "format": "float" } } - } + }, + "required": ["floats"] } ] } @@ -254,33 +255,28 @@ println!("{}", serde_json::to_string_pretty(&schema).unwrap()); ## Feature Flags - `derive` (enabled by default) - provides `#[derive(JsonSchema)]` macro -- `impl_json_schema` - implements `JsonSchema` for Schemars types themselves -- `preserve_order` - keep the order of struct fields in `Schema` and `SchemaObject` +- `preserve_order` - keep the order of struct fields in `Schema` properties - `raw_value` - implements `JsonSchema` for `serde_json::value::RawValue` (enables the serde_json `raw_value` feature) Schemars can implement `JsonSchema` on types from several popular crates, enabled via feature flags (dependency versions are shown in brackets): -- `chrono` - [chrono](https://crates.io/crates/chrono) (^0.4) -- `indexmap1` - [indexmap](https://crates.io/crates/indexmap) (^1.2) -- `indexmap2` - [indexmap](https://crates.io/crates/indexmap) (^2.0) -- `either` - [either](https://crates.io/crates/either) (^1.3) -- `uuid08` - [uuid](https://crates.io/crates/uuid) (^0.8) -- `uuid1` - [uuid](https://crates.io/crates/uuid) (^1.0) -- `smallvec` - [smallvec](https://crates.io/crates/smallvec) (^1.0) -- `arrayvec05` - [arrayvec](https://crates.io/crates/arrayvec) (^0.5) - `arrayvec07` - [arrayvec](https://crates.io/crates/arrayvec) (^0.7) -- `url` - [url](https://crates.io/crates/url) (^2.0) -- `bytes` - [bytes](https://crates.io/crates/bytes) (^1.0) -- `enumset` - [enumset](https://crates.io/crates/enumset) (^1.0) -- `rust_decimal` - [rust_decimal](https://crates.io/crates/rust_decimal) (^1.0) -- `bigdecimal03` - [bigdecimal](https://crates.io/crates/bigdecimal) (^0.3) - `bigdecimal04` - [bigdecimal](https://crates.io/crates/bigdecimal) (^0.4) -- `smol_str` - [smol_str](https://crates.io/crates/smol_str) (^0.1.17) -- `semver` - [semver](https://crates.io/crates/semver) (^1.0.9) +- `bytes1` - [bytes](https://crates.io/crates/bytes) (^1.0) +- `chrono04` - [chrono](https://crates.io/crates/chrono) (^0.4) +- `either1` - [either](https://crates.io/crates/either) (^1.3) +- `enumset1` - [enumset](https://crates.io/crates/enumset) (^1.0) +- `indexmap2` - [indexmap](https://crates.io/crates/indexmap) (^2.0) +- `rust_decimal1` - [rust_decimal](https://crates.io/crates/rust_decimal) (^1.0) +- `semver1` - [semver](https://crates.io/crates/semver) (^1.0.9) +- `smallvec1` - [smallvec](https://crates.io/crates/smallvec) (^1.0) +- `smol_str02` - [smol_str](https://crates.io/crates/smol_str) (^0.2.1) +- `url2` - [url](https://crates.io/crates/url) (^2.0) +- `uuid1` - [uuid](https://crates.io/crates/uuid) (^1.0) For example, to implement `JsonSchema` on types from `chrono`, enable it as a feature in the `schemars` dependency in your `Cargo.toml` like so: ```toml [dependencies] -schemars = { version = "0.8", features = ["chrono"] } +schemars = { version = "1.0.0-alpha.3", features = ["chrono04"] } ``` diff --git a/docs/0-migrating.md b/docs/0-migrating.md new file mode 100644 index 0000000..4ee936a --- /dev/null +++ b/docs/0-migrating.md @@ -0,0 +1,175 @@ +--- +title: Migrating from 0.8 +nav_order: 2 +has_children: true +has_toc: false +permalink: /migrating/ +layout: default +--- + +# Migrating from 0.8 to 1.0 + +
++ +## Optional dependencies + +All optional dependencies are now suffixed by their version: + +- `chrono` is now `chrono04` +- `either` is now `either1` +- `smallvec` is now `smallvec1` +- `url` is now `url2` +- `bytes` is now `bytes1` +- `rust_decimal` is now `rust_decimal1` +- `enumset` is now `enumset1` +- `smol_str` is now `smol_str02` +- `semver` is now `semver1` +- `indexmap`, `uuid08`, `arrayvec05` and `bigdecimal03` have been removed +- `indexmap2`, `arrayvec07` and `bigdecimal04` are unchanged + +## `Schema` is now a wrapper around `serde_json::Value` + +`Schema` is now defined as a wrapper around a `serde_json::Value` (which must be a `Value::Bool` or `Value::Object`), rather than a struct with a field for each JSON schema keyword (with some intermediary types). `Schema` is now available as `schemars::Schema` instead of `schemars::schema::Schema`, and all other types that were in the `schemars::schema` module have now been removed. Functions that previously returned a `RootSchema` now just return a `Schema`. + +A new macro `json_schema!(...)` is available to easily create new instances of `Schema`, which functions similarly to the [`serde_json::json!(...)` macro](https://docs.rs/serde_json/latest/serde_json/macro.json.html). + +Here's how you might create and modify a `Schema` in schemars v0.8: + +```rust +use schemars::schema::{InstanceType, ObjectValidation, Schema, SchemaObject}; +use schemars::Map; + +// Create a Schema for an object with property `foo` +let schema_object = SchemaObject { + instance_type: Some(InstanceType::Object.into()), + object: Some(Box::new(ObjectValidation { + properties: Map::from_iter([("foo".to_owned(), true.into())]), + ..Default::default() + })), + ..Default::default() +}; +let schema: Schema = schema_object.into(); + +// Make the `foo` property required +let mut schema_object = schema.into_object(); +let obj = schema_object.object(); +obj.required.insert("foo".to_owned()); +``` + +And the same thing in v1.0: + +```rust +use schemars::{json_schema, Schema}; + +// Create a Schema for an object with property `foo` +let mut schema: Schema = json_schema!({ + "type": "object", + "properties": { + "foo": true + } +}); + +// Make the `foo` property required +schema + .ensure_object() + .entry("required") + .or_insert(serde_json::Value::Array(Vec::new())) + .as_array_mut() + .expect("`required` should be an array") + .push("foo".into()); +``` + +## `visit::Visitor` replaced with `transform::Transform` + +The `visit` module and `Visitor` trait have been replace with `transform` and `Transform` respectively. Accordingly, these items have been renamed: + +- `SchemaSettings::visitors` -> `SchemaSettings::transforms` +- `SchemaSettings::with_visitor` -> `SchemaSettings::with_transform` +- `SchemaGenerator::visitors_mut` -> `SchemaGenerator::transforms_mut` +- `GenVisitor` -> `GenTransform` +- `Visitor::visit_schema` -> `Transform::transform` + - `visit_schema_object` and `visit_root_schema` methods have been removed +- `visit::visit_schema` -> `transform::transform_subschemas` + - `visit_schema_object` and `visit_root_schema` functions have been removed + +So if you had defined this `Visitor` in schemars 0.8: + +```rust +use schemars::schema::SchemaObject; +use schemars::visit::{visit_schema_object, Visitor}; + +pub struct MyVisitor; + +impl Visitor for MyVisitor { + fn visit_schema_object(&mut self, schema: &mut SchemaObject) { + // First, make our change to this schema + schema + .extensions + .insert("my_property".to_string(), serde_json::json!("hello world")); + + // Then delegate to default implementation to visit any subschemas + visit_schema_object(self, schema); + } +} + +let mut schema = schemars::schema_for!(str); +MyVisitor.visit_root_schema(&mut schema); +``` + +Then the equivalent `Transform` in schemars 1.0 would be: + +```rust +use schemars::transform::{transform_subschemas, Transform}; +use schemars::Schema; + +pub struct MyTransform; + +impl Transform for MyTransform { + fn transform(&mut self, schema: &mut Schema) { + // First, make our change to this schema + if let Some(obj) = schema.as_object_mut() { + obj.insert("my_property".to_string(), serde_json::json!("hello world")); + } + + // Then apply the transform to any subschemas + transform_subschemas(self, schema); + } +} + +let mut schema = schemars::schema_for!(str); +MyTransform.transform(&mut schema); +``` + +Also, since `Transform` is now implemented for functions that take a single `&mut Schema` argument, you could also define it as a function instead of a struct: + +```rust +fn my_transform(schema: &mut Schema) { + // First, make our change to this schema + if let Some(obj) = schema.as_object_mut() { + obj.insert("my_property".to_string(), serde_json::json!("hello world")); + } + + // Then apply the transform to any subschemas + transform_subschemas(&mut my_transform, schema); +} + +let mut schema = schemars::schema_for!(str); +my_transform(&mut schema); +// Or equivalently: +// my_transform.transform(&mut schema); +``` + +Finally, you can also use the `RecursiveTransform` newtype to convert a non-recursive `Transform` (i.e. one that does not transform subschemas) into a recursive one, like so: + +```rust +fn my_transform2(schema: &mut Schema) { + if let Some(obj) = schema.as_object_mut() { + obj.insert("my_property".to_string(), serde_json::json!("hello world")); + } +} + +let mut schema = schemars::schema_for!(str); +RecursiveTransform(my_transform2).transform(&mut schema); +``` diff --git a/docs/1-deriving.md b/docs/1-deriving.md index 6e37fc8..0bb4420 100644 --- a/docs/1-deriving.md +++ b/docs/1-deriving.md @@ -1,7 +1,6 @@ --- -layout: default title: Deriving JsonSchema -nav_order: 2 +nav_order: 3 has_children: true has_toc: false permalink: /deriving/ @@ -12,6 +11,7 @@ permalink: /deriving/ The most important trait in Schemars is `JsonSchema`, and the most important function of that trait is `json_schema(...)` which returns a JSON schema describing the type. Implementing this manually on many types would be slow and error-prone, so Schemars includes a derive macro which can implement that trait for you. Any derived implementation of `JsonSchema` should create a schema that describes the JSON representation of the type if it were to be serialized by serde_json. Usually, all you need to do to use it is to add a `#[derive(JsonSchema)]` attribute to your type: + ```rust use schemars::{JsonSchema, schema_for}; @@ -28,7 +28,8 @@ fn main() { println!("{}", serialized); } ``` - diff --git a/docs/_v0/1.1-attributes.md b/docs/_v0/1.1-attributes.md new file mode 100644 index 0000000..b668d7f --- /dev/null +++ b/docs/_v0/1.1-attributes.md @@ -0,0 +1,323 @@ +--- +title: Attributes +parent: Deriving JsonSchema +nav_order: 1 +permalink: /v0/deriving/attributes/ +--- + + + +# Attributes + +You can add attributes to your types to customize Schemars's derived `JsonSchema` implementation. + +[Serde](https://serde.rs/) allows setting `#[serde(...)]` attributes which change how types are serialized, and Schemars will generally respect these attributes to ensure that generated schemas will match how the type is serialized by serde_json. `#[serde(...)]` attributes can be overriden using `#[schemars(...)]` attributes, which behave identically (e.g. `#[schemars(rename_all = "camelCase")]`). You may find this useful if you want to change the generated schema without affecting Serde's behaviour, or if you're just not using Serde. + +[Validator](https://github.com/Keats/validator) allows setting `#[validate(...)]` attributes to restrict valid values of particular fields, many of which will be used by Schemars to generate more accurate schemas. These can also be overridden by `#[schemars(...)]` attributes. + +Schemars 1.0 is still under development, and further changes may be introduced. +