Skip to content

Commit da943a0

Browse files
authored
Add support for the x-rust-type extension (#584)
1 parent 336a042 commit da943a0

File tree

18 files changed

+812
-48
lines changed

18 files changed

+812
-48
lines changed

Cargo.lock

Lines changed: 8 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 172 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,29 +31,30 @@ appropriate built-in type based on type attributes. For example, a JSON Schema
3131
might specify a maximum and/or minimum that indicates the appropriate integral
3232
type to use.
3333

34-
String schemas that include a `format` are represented with the appropriate Rust
35-
type. For example `{ "type": "string", "format": "uuid" }` is represented as a
36-
`uuid::Uuid` (which requires the `uuid` crate be included as a dependency).
34+
String schemas that include a known `format` are represented with the
35+
appropriate Rust type. For example `{ "type": "string", "format": "uuid" }` is
36+
represented as a `uuid::Uuid` (which requires the `uuid` crate be included as a
37+
dependency).
3738

3839
### Arrays
3940

4041
JSON Schema arrays can turn into one of three Rust types `Vec<T>`, `HashSet<T>`,
4142
and tuples depending on the schema properties. An array may have a fixed length
4243
that matches a fixed list of item types; this is well represented by a Rust
43-
tuples. The distinction between `Vec<T>` and `HashSet<T>` is only if the
44+
tuple. The distinction between `Vec<T>` and `HashSet<T>` is only if the
4445
schema's `uniqueItems` field is `false` or `true` respectively.
4546

4647
### Objects
4748

48-
In general, objects turn in to Rust structs. If, however, the schema defines no
49+
In general, objects turn into Rust structs. If, however, the schema defines no
4950
properties, Typify emits a `HashMap<String, T>` if the `additionalProperties`
5051
schema specifies `T` or a `HashMap<String, serde_json::Value>` otherwise.
5152

52-
Properties that are not in the `required` set are typically represented as an
53-
`Option<T>` with the `#[serde(default)]` attribute applied. Non-required
54-
properties with types that already have a default value (such as a `Vec<T>`)
55-
simply get the `#[serde(default)]` attribute (so you won't see e.g.
56-
`Option<Vec<T>>`).
53+
Properties of generated `struct` that are not in the `required` set are
54+
typically represented as an `Option<T>` with the `#[serde(default)]` attribute
55+
applied. Non-required properties with types that already have a default value
56+
(such as a `Vec<T>`) simply get the `#[serde(default)]` attribute (so you won't
57+
see e.g. `Option<Vec<T>>`).
5758

5859
### OneOf
5960

@@ -76,6 +77,166 @@ flattened members, this is one of the weaker areas of code generation.
7677

7778
Issues describing example schemas and desired output are welcome and helpful.
7879

80+
## Rust -> Schema -> Rust
81+
82+
Schemas derived from Rust types may include an extension that provides
83+
information about the original type:
84+
85+
```json
86+
{
87+
"type": "object",
88+
"properties": { .. },
89+
"x-rust-type": {
90+
"crate": "crate-o-types",
91+
"version": "1.0.0",
92+
"path": "crate_o_types::some_mod::SomeType"
93+
}
94+
}
95+
```
96+
97+
The extension includes the name of the crate, a Cargo-style version
98+
requirements spec, and the full path (that must start with ident-converted name
99+
of the crate).
100+
101+
Each of the modes of using typify allow for a list of crates and versions to be
102+
specified. In this case, if the user specifies "[email protected]" for
103+
example, then typify would use its `SomeType` type rather than generating one
104+
according to the schema.
105+
106+
### Using types from other crates
107+
108+
Each mode of using typify has a method for controlling the use of types with
109+
`x-rust-type` annotations. The default is to ignore them. The recommended
110+
method is to specify each crate and version you intend to use. You can
111+
additionally supply the `*` version for crates (which may result in
112+
incompatibilities) or you can define a policy to allow the use of all "unknown"
113+
crates (which may require that addition of dependencies for those crates).
114+
115+
For the CLI:
116+
```console
117+
$ cargo typify --unknown-crates allow --crate [email protected] ...
118+
```
119+
120+
For the builder:
121+
```rust
122+
let mut settings = typify::TypeSpaceSettings::default();
123+
settings.with_unknown_crates(typify::UnknownPolicy::Allow)
124+
.with_crate("oxnet", typify::CrateVers::Version("1.0.0".parse().unwrap()));
125+
```
126+
127+
For the macro:
128+
```rust
129+
typify::import_types!(
130+
schema = "schema.json",
131+
unknown_types = Allow,
132+
crates {
133+
"oxnet" = "1.0.0"
134+
}
135+
)
136+
```
137+
138+
### Version requirements
139+
140+
The `version` field within the `x-rust-type` extension follows the Cargo
141+
version requirements specification. If the extension specifies `0.1.0` of a
142+
crate and the user states that they're using `0.1.1`, then the type is used;
143+
conversely, if the extension specifies `0.2.2` and the user is only using
144+
`0.2.0` the type is not used.
145+
146+
Crate authors may choose to adhere to greater stability than otherwise provided
147+
by semver. If the extension version is `>=0.1.0, <1.0.0` then the crate author
148+
is committing to the schema compatibility of the given type on all releases
149+
until `1.0.0`. It is important that crate authors populate the `version` field
150+
in a way that upholds type availability. For example, while `*` is a valid
151+
value, it is only conceivably valid if the type in question were available in
152+
the first ever version of a crate published and never changed incompatibly in
153+
any subsequent version.
154+
155+
### Type parameters
156+
157+
The `x-rust-type` extension may also specify type parameters:
158+
159+
```json
160+
{
161+
"$defs": {
162+
"Sprocket": {
163+
"type": "object",
164+
"properties": { .. },
165+
"x-rust-type": {
166+
"crate": "util",
167+
"version": "0.1.0",
168+
"path": "util::Sprocket",
169+
"parameters": [
170+
{
171+
"$ref": "#/$defs/Gizmo"
172+
}
173+
]
174+
}
175+
},
176+
"Gizmo": {
177+
"type": "object",
178+
"properties": { .. },
179+
"x-rust-type": {
180+
"crate": "util",
181+
"version": "0.1.0",
182+
"path": "util::Gizmo"
183+
}
184+
}
185+
}
186+
}
187+
```
188+
189+
With the `[email protected]` crate specified during type generation, schemas
190+
referencing `#/$defs/Sprocket` would use the (non-generated) type
191+
`util::Sprocket<util::Gizmo>`.
192+
193+
The `parameters` field is an array of schemas. They may be inline schemas or
194+
referenced schemas.
195+
196+
### Including `x-rust-type` in your library
197+
198+
The schema for the expected value is as follows:
199+
200+
```json
201+
{
202+
"description": "schema for the x-rust-type extension",
203+
"type": "object",
204+
"properties": {
205+
"crate": {
206+
"type": "string",
207+
"pattern": "^[a-zA-Z0-9_-]+$"
208+
},
209+
"version": {
210+
"description": "semver requirements per a Cargo.toml dependencies entry",
211+
"type": "string"
212+
},
213+
"path": {
214+
"type": "string",
215+
"pattern": "^[a-zA-Z0-9_]+(::[a-zA-Z0-9+]+)*$"
216+
},
217+
"parameters": {
218+
"type": "array",
219+
"items": {
220+
"$ref": "#/definitions/Schema"
221+
}
222+
}
223+
},
224+
"required": [
225+
"crate",
226+
"path",
227+
"version"
228+
]
229+
}
230+
```
231+
232+
The `version` field expresses the stability of your type. For example, if
233+
`0.1.0` indicates that `0.1.1` users would be fine whereas `0.2.0` users would
234+
not use the type (instead generating it). You can communicate a future
235+
commitment beyond what semver implies by using the [Cargo version requirement
236+
syntax](https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#version-requirement-syntax).
237+
For example `>=0.1.0, <1.0.0` says that the type will remain structurally
238+
compatible from version `0.1.0` until `1.0.0`.
239+
79240
## Formatting
80241

81242
You can format generated code using crates such as
@@ -90,7 +251,7 @@ The examples below show different ways to convert a `TypeSpace` to a string
90251
### `rustfmt`
91252

92253
Best for generation of code that might be checked in alongside hand-written
93-
code such as in the case of an `xtask` or stand-alone code generator (list
254+
code such as in the case of an `xtask` or stand-alone code generator (such as
94255
`cargo-typify`).
95256

96257
```rust

cargo-typify/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ clap = { version = "4.5.4", features = ["derive"] }
1818
color-eyre = "0.6"
1919
env_logger = "0.10"
2020
rustfmt-wrapper = "0.2.1"
21+
semver = "1.0.22"
2122
serde_json = "1.0.116"
2223
schemars = "0.8.17"
2324

0 commit comments

Comments
 (0)