schemars/
lib.rs

1#![forbid(unsafe_code)]
2/*!
3Generate JSON Schema documents from Rust code
4
5## Basic Usage
6
7If you don't really care about the specifics, the easiest way to generate a JSON schema for your types is to `#[derive(JsonSchema)]` and use the `schema_for!` macro. All fields of the type must also implement `JsonSchema` - Schemars implements this for many standard library types.
8
9```rust
10use schemars::{schema_for, JsonSchema};
11
12#[derive(JsonSchema)]
13pub struct MyStruct {
14    pub my_int: i32,
15    pub my_bool: bool,
16    pub my_nullable_enum: Option<MyEnum>,
17}
18
19#[derive(JsonSchema)]
20pub enum MyEnum {
21    StringNewType(String),
22    StructVariant { floats: Vec<f32> },
23}
24
25let schema = schema_for!(MyStruct);
26println!("{}", serde_json::to_string_pretty(&schema).unwrap());
27```
28
29<details>
30<summary>Click to see the output JSON schema...</summary>
31
32```json
33{
34    "$schema": "http://json-schema.org/draft-07/schema#",
35    "title": "MyStruct",
36    "type": "object",
37    "required": [
38        "my_bool",
39        "my_int"
40    ],
41    "properties": {
42        "my_bool": {
43            "type": "boolean"
44        },
45        "my_int": {
46            "type": "integer",
47            "format": "int32"
48        },
49        "my_nullable_enum": {
50            "anyOf": [
51                {
52                    "$ref": "#/definitions/MyEnum"
53                },
54                {
55                    "type": "null"
56                }
57            ]
58        }
59    },
60    "definitions": {
61        "MyEnum": {
62            "anyOf": [
63                {
64                    "type": "object",
65                    "required": [
66                        "StringNewType"
67                    ],
68                    "properties": {
69                        "StringNewType": {
70                            "type": "string"
71                        }
72                    },
73                    "additionalProperties": false
74                },
75                {
76                    "type": "object",
77                    "required": [
78                        "StructVariant"
79                    ],
80                    "properties": {
81                        "StructVariant": {
82                            "type": "object",
83                            "required": [
84                                "floats"
85                            ],
86                            "properties": {
87                                "floats": {
88                                    "type": "array",
89                                    "items": {
90                                        "type": "number",
91                                        "format": "float"
92                                    }
93                                }
94                            }
95                        }
96                    },
97                    "additionalProperties": false
98                }
99            ]
100        }
101    }
102}
103```
104</details>
105
106### Serde Compatibility
107
108One of the main aims of this library is compatibility with [Serde](https://github.com/serde-rs/serde). Any generated schema *should* match how [serde_json](https://github.com/serde-rs/json) would serialize/deserialize to/from JSON. To support this, Schemars will check for any `#[serde(...)]` attributes on types that derive `JsonSchema`, and adjust the generated schema accordingly.
109
110```rust
111use schemars::{schema_for, JsonSchema};
112use serde::{Deserialize, Serialize};
113
114#[derive(Deserialize, Serialize, JsonSchema)]
115#[serde(rename_all = "camelCase", deny_unknown_fields)]
116pub struct MyStruct {
117    #[serde(rename = "myNumber")]
118    pub my_int: i32,
119    pub my_bool: bool,
120    #[serde(default)]
121    pub my_nullable_enum: Option<MyEnum>,
122}
123
124#[derive(Deserialize, Serialize, JsonSchema)]
125#[serde(untagged)]
126pub enum MyEnum {
127    StringNewType(String),
128    StructVariant { floats: Vec<f32> },
129}
130
131let schema = schema_for!(MyStruct);
132println!("{}", serde_json::to_string_pretty(&schema).unwrap());
133```
134
135<details>
136<summary>Click to see the output JSON schema...</summary>
137
138```json
139{
140    "$schema": "http://json-schema.org/draft-07/schema#",
141    "title": "MyStruct",
142    "type": "object",
143    "required": [
144        "myBool",
145        "myNumber"
146    ],
147    "properties": {
148        "myBool": {
149            "type": "boolean"
150        },
151        "myNullableEnum": {
152            "default": null,
153            "anyOf": [
154                {
155                    "$ref": "#/definitions/MyEnum"
156                },
157                {
158                    "type": "null"
159                }
160            ]
161        },
162        "myNumber": {
163            "type": "integer",
164            "format": "int32"
165        }
166    },
167    "additionalProperties": false,
168    "definitions": {
169        "MyEnum": {
170            "anyOf": [
171                {
172                    "type": "string"
173                },
174                {
175                    "type": "object",
176                    "required": [
177                        "floats"
178                    ],
179                    "properties": {
180                        "floats": {
181                            "type": "array",
182                            "items": {
183                                "type": "number",
184                                "format": "float"
185                            }
186                        }
187                    }
188                }
189            ]
190        }
191    }
192}
193```
194</details>
195
196`#[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.
197
198### Schema from Example Value
199
200If you want a schema for a type that can't/doesn't implement `JsonSchema`, but does implement `serde::Serialize`, then you can generate a JSON schema from a value of that type. However, this schema will generally be less precise than if the type implemented `JsonSchema` - particularly when it involves enums, since schemars will not make any assumptions about the structure of an enum based on a single variant.
201
202```rust
203use schemars::schema_for_value;
204use serde::Serialize;
205
206#[derive(Serialize)]
207pub struct MyStruct {
208    pub my_int: i32,
209    pub my_bool: bool,
210    pub my_nullable_enum: Option<MyEnum>,
211}
212
213#[derive(Serialize)]
214pub enum MyEnum {
215    StringNewType(String),
216    StructVariant { floats: Vec<f32> },
217}
218
219let schema = schema_for_value!(MyStruct {
220    my_int: 123,
221    my_bool: true,
222    my_nullable_enum: Some(MyEnum::StringNewType("foo".to_string()))
223});
224println!("{}", serde_json::to_string_pretty(&schema).unwrap());
225```
226
227<details>
228<summary>Click to see the output JSON schema...</summary>
229
230```json
231{
232    "$schema": "http://json-schema.org/draft-07/schema#",
233    "title": "MyStruct",
234    "examples": [
235        {
236            "my_bool": true,
237            "my_int": 123,
238            "my_nullable_enum": {
239                "StringNewType": "foo"
240            }
241        }
242    ],
243    "type": "object",
244    "properties": {
245        "my_bool": {
246            "type": "boolean"
247        },
248        "my_int": {
249            "type": "integer"
250        },
251        "my_nullable_enum": true
252    }
253}
254```
255</details>
256
257## Feature Flags
258- `derive` (enabled by default) - provides `#[derive(JsonSchema)]` macro
259- `impl_json_schema` - implements `JsonSchema` for Schemars types themselves
260- `preserve_order` - keep the order of struct fields in `Schema` and `SchemaObject`
261
262Schemars can implement `JsonSchema` on types from several popular crates, enabled via feature flags (dependency versions are shown in brackets):
263- `chrono` - [chrono](https://crates.io/crates/chrono) (^0.4)
264- `indexmap1` - [indexmap](https://crates.io/crates/indexmap) (^1.2)
265- `either` - [either](https://crates.io/crates/either) (^1.3)
266- `uuid08` - [uuid](https://crates.io/crates/uuid) (^0.8)
267- `uuid1` - [uuid](https://crates.io/crates/uuid) (^1.0)
268- `smallvec` - [smallvec](https://crates.io/crates/smallvec) (^1.0)
269- `arrayvec05` - [arrayvec](https://crates.io/crates/arrayvec) (^0.5)
270- `arrayvec07` - [arrayvec](https://crates.io/crates/arrayvec) (^0.7)
271- `url` - [url](https://crates.io/crates/url) (^2.0)
272- `bytes` - [bytes](https://crates.io/crates/bytes) (^1.0)
273- `enumset` - [enumset](https://crates.io/crates/enumset) (^1.0)
274- `rust_decimal` - [rust_decimal](https://crates.io/crates/rust_decimal) (^1.0)
275- `bigdecimal` - [bigdecimal](https://crates.io/crates/bigdecimal) (^0.3)
276
277For example, to implement `JsonSchema` on types from `chrono`, enable it as a feature in the `schemars` dependency in your `Cargo.toml` like so:
278
279```toml
280[dependencies]
281schemars = { version = "0.8", features = ["chrono"] }
282```
283
284```
285*/
286
287/// The map type used by schemars types.
288///
289/// Currently a `BTreeMap` or `IndexMap` can be used, but this may change to a different implementation
290/// with a similar interface in a future version of schemars.
291/// The `IndexMap` will be used when the `preserve_order` feature flag is set.
292#[cfg(not(feature = "preserve_order"))]
293pub type Map<K, V> = std::collections::BTreeMap<K, V>;
294#[cfg(feature = "preserve_order")]
295pub type Map<K, V> = indexmap::IndexMap<K, V>;
296/// The set type used by schemars types.
297///
298/// Currently a `BTreeSet`, but this may change to a different implementation
299/// with a similar interface in a future version of schemars.
300pub type Set<T> = std::collections::BTreeSet<T>;
301
302/// A view into a single entry in a map, which may either be vacant or occupied.
303//
304/// This is constructed from the `entry` method on `BTreeMap` or `IndexMap`,
305/// depending on whether the `preserve_order` feature flag is set.
306#[cfg(not(feature = "preserve_order"))]
307pub type MapEntry<'a, K, V> = std::collections::btree_map::Entry<'a, K, V>;
308#[cfg(feature = "preserve_order")]
309pub type MapEntry<'a, K, V> = indexmap::map::Entry<'a, K, V>;
310
311mod flatten;
312mod json_schema_impls;
313mod ser;
314#[macro_use]
315mod macros;
316
317/// This module is only public for use by `schemars_derive`. It should not need to be used by code
318/// outside of `schemars`, and should not be considered part of the public API.
319#[doc(hidden)]
320pub mod _private;
321pub mod gen;
322pub mod schema;
323pub mod visit;
324
325#[cfg(feature = "schemars_derive")]
326extern crate schemars_derive;
327#[cfg(feature = "schemars_derive")]
328pub use schemars_derive::*;
329
330// Export serde_json so schemars_derive can use it
331#[doc(hidden)]
332pub use serde_json as _serde_json;
333
334use schema::Schema;
335
336/// A type which can be described as a JSON Schema document.
337///
338/// This is implemented for many Rust primitive and standard library types.
339///
340/// This can also be automatically derived on most custom types with `#[derive(JsonSchema)]`.
341///
342/// # Example
343/// ```
344/// use schemars::{schema_for, JsonSchema};
345///
346/// #[derive(JsonSchema)]
347/// struct MyStruct {
348///     foo: i32,
349/// }
350///
351/// let my_schema = schema_for!(MyStruct);
352/// ```
353pub trait JsonSchema {
354    /// Whether JSON Schemas generated for this type should be re-used where possible using the `$ref` keyword.
355    ///
356    /// For trivial types (such as primitives), this should return `false`. For more complex types, it should return `true`.
357    /// For recursive types, this **must** return `true` to prevent infinite cycles when generating schemas.
358    ///
359    /// By default, this returns `true`.
360    fn is_referenceable() -> bool {
361        true
362    }
363
364    /// The name of the generated JSON Schema.
365    ///
366    /// This is used as the title for root schemas, and the key within the root's `definitions` property for subschemas.
367    fn schema_name() -> String;
368
369    /// Generates a JSON Schema for this type.
370    ///
371    /// If the returned schema depends on any [referenceable](JsonSchema::is_referenceable) schemas, then this method will
372    /// add them to the [`SchemaGenerator`](gen::SchemaGenerator)'s schema definitions.
373    ///
374    /// This should not return a `$ref` schema.
375    fn json_schema(gen: &mut gen::SchemaGenerator) -> Schema;
376
377    // TODO document and bring into public API?
378    #[doc(hidden)]
379    fn _schemars_private_non_optional_json_schema(gen: &mut gen::SchemaGenerator) -> Schema {
380        Self::json_schema(gen)
381    }
382
383    // TODO document and bring into public API?
384    #[doc(hidden)]
385    fn _schemars_private_is_option() -> bool {
386        false
387    }
388}
389
390#[cfg(test)]
391pub mod tests {
392    use super::*;
393
394    pub fn schema_object_for<T: JsonSchema>() -> schema::SchemaObject {
395        schema_object(schema_for::<T>())
396    }
397
398    pub fn schema_for<T: JsonSchema>() -> schema::Schema {
399        let mut gen = gen::SchemaGenerator::default();
400        T::json_schema(&mut gen)
401    }
402
403    pub fn schema_object(schema: schema::Schema) -> schema::SchemaObject {
404        match schema {
405            schema::Schema::Object(o) => o,
406            s => panic!("Schema was not an object: {:?}", s),
407        }
408    }
409}