Repository: oxidecomputer/typify
Pull request #586 (2024-05-13 13:48:35)

Bump trybuild from 1.0.93 to 1.0.95
Bumps [trybuild](https://github.com/dtolnay/trybuild) from 1.0.93 to 1.0.95.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/dtolnay/trybuild/releases">trybuild's releases</a>.</em></p>
<blockquote>
<h2>1.0.95</h2>
<ul>
<li>Keep long type names in diagnostics so that test output does not vary depending on the length of the absolute filepath of the crate (<a href="https://redirect.github.com/dtolnay/trybuild/issues/269">#269</a>)</li>
</ul>
<h2>1.0.94</h2>
<ul>
<li>Resolve unexpected_cfgs warning (<a href="https://redirect.github.com/dtolnay/trybuild/issues/268">#268</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/dtolnay/trybuild/commit/02e6117084d81540ff533da81a3bdd74e00cdf27"><code>02e6117</code></a> Release 1.0.95</li>
<li><a href="https://github.com/dtolnay/trybuild/commit/619bfcd491cbdcc443e54f66aa329dc207cc6034"><code>619bfcd</code></a> Merge pull request <a href="https://redirect.github.com/dtolnay/trybuild/issues/270">#270</a> from dtolnay/typename</li>
<li><a href="https://github.com/dtolnay/trybuild/commit/db3c9b4f545d8e9250e2defeb724d6f46c4f22cc"><code>db3c9b4</code></a> Disable long typename shortening in diagnostics</li>
<li><a href="https://github.com/dtolnay/trybuild/commit/9ed763b56f204ecc2624657016c12aeae3d26920"><code>9ed763b</code></a> Release 1.0.94</li>
<li><a href="https://github.com/dtolnay/trybuild/commit/b3c948124251872bc3111d2cf2938ae044e9e9d1"><code>b3c9481</code></a> Merge pull request <a href="https://redirect.github.com/dtolnay/trybuild/issues/268">#268</a> from dtolnay/checkcfg</li>
<li><a href="https://github.com/dtolnay/trybuild/commit/7bb6f2fb22f39744771fa229740d8e023c67e424"><code>7bb6f2f</code></a> Resolve unexpected_cfgs warning</li>
<li>See full diff in <a href="https://github.com/dtolnay/trybuild/compare/1.0.93...1.0.95">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=trybuild&package-manager=cargo&previous-version=1.0.93&new-version=1.0.95)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index c869fea..aa66142 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -184,6 +184,7 @@ dependencies = [
  "newline-converter",
  "rustfmt-wrapper",
  "schemars",
+ "semver",
  "serde_json",
  "tempdir",
  "typify",
@@ -895,9 +896,12 @@ dependencies = [
 
 [[package]]
 name = "semver"
-version = "1.0.20"
+version = "1.0.22"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090"
+checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca"
+dependencies = [
+ "serde",
+]
 
 [[package]]
 name = "serde"
@@ -1152,9 +1156,9 @@ dependencies = [
 
 [[package]]
 name = "trybuild"
-version = "1.0.93"
+version = "1.0.95"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2a0e5d82932dfbf36df38de5df0cfe846d13430b3ae3fdc48b2e91ed692c8df7"
+checksum = "4ddb747392ea12569d501a5bbca08852e4c8cd88b92566074b2243b8846f09e6"
 dependencies = [
  "glob",
  "serde",
@@ -1199,6 +1203,7 @@ dependencies = [
  "rustfmt-wrapper",
  "schema",
  "schemars",
+ "semver",
  "serde",
  "serde_json",
  "syn",
@@ -1214,6 +1219,7 @@ dependencies = [
  "proc-macro2",
  "quote",
  "schemars",
+ "semver",
  "serde",
  "serde_json",
  "serde_tokenstream",
diff --git a/README.md b/README.md
index 3b14ad1..8b63f2b 100644
--- a/README.md
+++ b/README.md
@@ -31,29 +31,30 @@ appropriate built-in type based on type attributes. For example, a JSON Schema
 might specify a maximum and/or minimum that indicates the appropriate integral
 type to use.
 
-String schemas that include a `format` are represented with the appropriate Rust
-type. For example `{ "type": "string", "format": "uuid" }` is represented as a
-`uuid::Uuid` (which requires the `uuid` crate be included as a dependency).
+String schemas that include a known `format` are represented with the
+appropriate Rust type. For example `{ "type": "string", "format": "uuid" }` is
+represented as a `uuid::Uuid` (which requires the `uuid` crate be included as a
+dependency).
 
 ### Arrays
 
 JSON Schema arrays can turn into one of three Rust types `Vec<T>`, `HashSet<T>`,
 and tuples depending on the schema properties. An array may have a fixed length
 that matches a fixed list of item types; this is well represented by a Rust
-tuples. The distinction between `Vec<T>` and `HashSet<T>` is only if the
+tuple. The distinction between `Vec<T>` and `HashSet<T>` is only if the
 schema's `uniqueItems` field is `false` or `true` respectively.
 
 ### Objects
 
-In general, objects turn in to Rust structs. If, however, the schema defines no
+In general, objects turn into Rust structs. If, however, the schema defines no
 properties, Typify emits a `HashMap<String, T>` if the `additionalProperties`
 schema specifies `T` or a `HashMap<String, serde_json::Value>` otherwise.
 
-Properties that are not in the `required` set are typically represented as an
-`Option<T>` with the `#[serde(default)]` attribute applied. Non-required
-properties with types that already have a default value (such as a `Vec<T>`)
-simply get the `#[serde(default)]` attribute (so you won't see e.g.
-`Option<Vec<T>>`).
+Properties of generated `struct` that are not in the `required` set are
+typically represented as an `Option<T>` with the `#[serde(default)]` attribute
+applied. Non-required properties with types that already have a default value
+(such as a `Vec<T>`) simply get the `#[serde(default)]` attribute (so you won't
+see e.g. `Option<Vec<T>>`).
 
 ### OneOf
 
@@ -76,6 +77,166 @@ flattened members, this is one of the weaker areas of code generation.
 
 Issues describing example schemas and desired output are welcome and helpful.
 
+## Rust -> Schema -> Rust
+
+Schemas derived from Rust types may include an extension that provides
+information about the original type:
+
+```json
+{
+  "type": "object",
+  "properties": { .. },
+  "x-rust-type": {
+    "crate": "crate-o-types",
+    "version": "1.0.0",
+    "path": "crate_o_types::some_mod::SomeType"
+  }
+}
+```
+
+The extension includes the name of the crate, a Cargo-style version
+requirements spec, and the full path (that must start with ident-converted name
+of the crate).
+
+Each of the modes of using typify allow for a list of crates and versions to be
+specified. In this case, if the user specifies "crate-o-types@1.0.1" for
+example, then typify would use its `SomeType` type rather than generating one
+according to the schema.
+
+### Using types from other crates
+
+Each mode of using typify has a method for controlling the use of types with
+`x-rust-type` annotations. The default is to ignore them. The recommended
+method is to specify each crate and version you intend to use. You can
+additionally supply the `*` version for crates (which may result in
+incompatibilities) or you can define a policy to allow the use of all "unknown"
+crates (which may require that addition of dependencies for those crates).
+
+For the CLI:
+```console
+$ cargo typify --unknown-crates allow --crate oxnet@1.0.0 ...
+```
+
+For the builder:
+```rust
+let mut settings = typify::TypeSpaceSettings::default();
+settings.with_unknown_crates(typify::UnknownPolicy::Allow)
+    .with_crate("oxnet", typify::CrateVers::Version("1.0.0".parse().unwrap()));
+```
+
+For the macro:
+```rust
+typify::import_types!(
+  schema = "schema.json",
+  unknown_types = Allow,
+  crates {
+    "oxnet" = "1.0.0"
+  }
+)
+```
+
+### Version requirements
+
+The `version` field within the `x-rust-type` extension follows the Cargo
+version requirements specification. If the extension specifies `0.1.0` of a
+crate and the user states that they're using `0.1.1`, then the type is used;
+conversely, if the extension specifies `0.2.2` and the user is only using
+`0.2.0` the type is not used.
+
+Crate authors may choose to adhere to greater stability than otherwise provided
+by semver. If the extension version is `>=0.1.0, <1.0.0` then the crate author
+is committing to the schema compatibility of the given type on all releases
+until `1.0.0`. It is important that crate authors populate the `version` field
+in a way that upholds type availability. For example, while `*` is a valid
+value, it is only conceivably valid if the type in question were available in
+the first ever version of a crate published and never changed incompatibly in
+any subsequent version.
+
+### Type parameters
+
+The `x-rust-type` extension may also specify type parameters:
+
+```json
+{
+  "$defs": {
+    "Sprocket": {
+      "type": "object",
+      "properties": { .. },
+      "x-rust-type": {
+        "crate": "util",
+        "version": "0.1.0",
+        "path": "util::Sprocket",
+        "parameters": [
+          {
+            "$ref": "#/$defs/Gizmo"
+          }
+        ]
+      }
+    },
+    "Gizmo": {
+      "type": "object",
+      "properties": { .. },
+      "x-rust-type": {
+        "crate": "util",
+        "version": "0.1.0",
+        "path": "util::Gizmo"
+      }
+    }
+  }
+}
+```
+
+With the `util@0.1.0` crate specified during type generation, schemas
+referencing `#/$defs/Sprocket` would use the (non-generated) type
+`util::Sprocket<util::Gizmo>`.
+
+The `parameters` field is an array of schemas. They may be inline schemas or
+referenced schemas.
+
+### Including `x-rust-type` in your library
+
+The schema for the expected value is as follows:
+
+```json
+{
+  "description": "schema for the x-rust-type extension",
+  "type": "object",
+  "properties": {
+    "crate": {
+      "type": "string",
+      "pattern": "^[a-zA-Z0-9_-]+$"
+    },
+    "version": {
+      "description": "semver requirements per a Cargo.toml dependencies entry",
+      "type": "string"
+    },
+    "path": {
+      "type": "string",
+      "pattern": "^[a-zA-Z0-9_]+(::[a-zA-Z0-9+]+)*$"
+    },
+    "parameters": {
+      "type": "array",
+      "items": {
+        "$ref": "#/definitions/Schema"
+      }
+    }
+  },
+  "required": [
+    "crate",
+    "path",
+    "version"
+  ]
+}
+```
+
+The `version` field expresses the stability of your type. For example, if
+`0.1.0` indicates that `0.1.1` users would be fine whereas `0.2.0` users would
+not use the type (instead generating it). You can communicate a future
+commitment beyond what semver implies by using the [Cargo version requirement
+syntax](https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#version-requirement-syntax).
+For example `>=0.1.0, <1.0.0` says that the type will remain structurally
+compatible from version `0.1.0` until `1.0.0`.
+
 ## Formatting
 
 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
 ### `rustfmt`
 
 Best for generation of code that might be checked in alongside hand-written
-code such as in the case of an `xtask` or stand-alone code generator (list
+code such as in the case of an `xtask` or stand-alone code generator (such as
 `cargo-typify`).
 
 ```rust
diff --git a/cargo-typify/Cargo.toml b/cargo-typify/Cargo.toml
index 2e8fcba..e4a319b 100644
--- a/cargo-typify/Cargo.toml
+++ b/cargo-typify/Cargo.toml
@@ -18,6 +18,7 @@ clap = { version = "4.5.4", features = ["derive"] }
 color-eyre = "0.6"
 env_logger = "0.10"
 rustfmt-wrapper = "0.2.1"
+semver = "1.0.22"
 serde_json = "1.0.116"
 schemars = "0.8.17"
 
diff --git a/cargo-typify/src/lib.rs b/cargo-typify/src/lib.rs
index eb74a0d..328b57b 100644
--- a/cargo-typify/src/lib.rs
+++ b/cargo-typify/src/lib.rs
@@ -1,4 +1,4 @@
-// Copyright 2023 Oxide Computer Company
+// Copyright 2024 Oxide Computer Company
 
 //! cargo command to generate Rust code from a JSON Schema.
 
@@ -8,7 +8,7 @@ use std::path::PathBuf;
 
 use clap::{ArgGroup, Args};
 use color_eyre::eyre::{Context, Result};
-use typify::{TypeSpace, TypeSpaceSettings};
+use typify::{CrateVers, TypeSpace, TypeSpaceSettings, UnknownPolicy};
 
 /// A CLI for the `typify` crate that converts JSON Schema files to Rust code.
 #[derive(Args)]
@@ -25,7 +25,8 @@ pub struct CliArgs {
     #[arg(short, long, default_value = "false", group = "build")]
     pub builder: bool,
 
-    /// Inverse of `--builder`. When set the builder-style interface will not be included.
+    /// Inverse of `--builder`. When set the builder-style interface will not
+    /// be included.
     #[arg(short = 'B', long, default_value = "false", group = "build")]
     pub no_builder: bool,
 
@@ -33,12 +34,25 @@ pub struct CliArgs {
     #[arg(short, long = "additional-derive", value_name = "derive")]
     pub additional_derives: Vec<String>,
 
-    /// The output file to write to. If not specified, the input file name will be used with a
-    /// `.rs` extension.
+    /// The output file to write to. If not specified, the input file name will
+    /// be used with a `.rs` extension.
     ///
     /// If `-` is specified, the output will be written to stdout.
     #[arg(short, long)]
     pub output: Option<PathBuf>,
+
+    /// Specify each crate@version that can be assumed to be in use for types
+    /// found in the schema with the x-rust-type extension.
+    #[arg(long = "crate")]
+    crates: Vec<CrateSpec>,
+
+    /// Specify the policy unknown crates found in schemas with the
+    /// x-rust-type extension.
+    #[arg(
+        long = "unknown-crates",
+        value_parser = ["generate", "allow", "deny"]
+    )]
+    unknown_crates: Option<String>,
 }
 
 impl CliArgs {
@@ -66,6 +80,53 @@ impl CliArgs {
     }
 }
 
+#[derive(Debug, Clone)]
+struct CrateSpec {
+    name: String,
+    version: CrateVers,
+    rename: Option<String>,
+}
+
+impl std::str::FromStr for CrateSpec {
+    type Err = &'static str;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        fn is_crate(s: &str) -> bool {
+            !s.contains(|cc: char| !cc.is_alphabetic() && cc != '-' && cc != '_')
+        }
+
+        fn convert(s: &str) -> Option<CrateSpec> {
+            let (rename, s) = if let Some(ii) = s.find('=') {
+                let rename = &s[..ii];
+                let rest = &s[ii + 1..];
+                if !is_crate(rename) {
+                    return None;
+                }
+                (Some(rename.to_string()), rest)
+            } else {
+                (None, s)
+            };
+
+            let ii = s.find('@')?;
+            let crate_str = &s[..ii];
+            let vers_str = &s[ii + 1..];
+
+            if !is_crate(crate_str) {
+                return None;
+            }
+            let version = CrateVers::parse(vers_str)?;
+
+            Some(CrateSpec {
+                name: crate_str.to_string(),
+                version,
+                rename,
+            })
+        }
+
+        convert(s).ok_or("crate specifier must be of the form 'cratename@version'")
+    }
+}
+
 /// Generate Rust code for the selected JSON Schema.
 pub fn convert(args: &CliArgs) -> Result<String> {
     let content = std::fs::read_to_string(&args.input)
@@ -74,14 +135,33 @@ pub fn convert(args: &CliArgs) -> Result<String> {
     let schema = serde_json::from_str::<schemars::schema::RootSchema>(&content)
         .wrap_err("Failed to parse input file as JSON Schema")?;
 
-    let mut settings = &mut TypeSpaceSettings::default();
-    settings = settings.with_struct_builder(args.use_builder());
+    let mut settings = TypeSpaceSettings::default();
+    settings.with_struct_builder(args.use_builder());
 
     for derive in &args.additional_derives {
-        settings = settings.with_derive(derive.clone());
+        settings.with_derive(derive.clone());
+    }
+
+    for CrateSpec {
+        name,
+        version,
+        rename,
+    } in &args.crates
+    {
+        settings.with_crate(name, version.clone(), rename.as_ref());
+    }
+
+    if let Some(unknown_crates) = &args.unknown_crates {
+        let unknown_crates = match unknown_crates.as_str() {
+            "generate" => UnknownPolicy::Generate,
+            "allow" => UnknownPolicy::Allow,
+            "deny" => UnknownPolicy::Deny,
+            _ => unreachable!(),
+        };
+        settings.with_unknown_crates(unknown_crates);
     }
 
-    let mut type_space = TypeSpace::new(settings);
+    let mut type_space = TypeSpace::new(&settings);
     type_space
         .add_root_schema(schema)
         .wrap_err("Schema conversion failed")?;
@@ -113,6 +193,8 @@ mod tests {
             additional_derives: vec![],
             output: Some(PathBuf::from("-")),
             no_builder: false,
+            crates: vec![],
+            unknown_crates: Default::default(),
         };
 
         assert_eq!(args.output_path(), None);
@@ -126,6 +208,8 @@ mod tests {
             additional_derives: vec![],
             output: Some(PathBuf::from("some_file.rs")),
             no_builder: false,
+            crates: vec![],
+            unknown_crates: Default::default(),
         };
 
         assert_eq!(args.output_path(), Some(PathBuf::from("some_file.rs")));
@@ -139,6 +223,8 @@ mod tests {
             additional_derives: vec![],
             output: None,
             no_builder: false,
+            crates: vec![],
+            unknown_crates: Default::default(),
         };
 
         assert_eq!(args.output_path(), Some(PathBuf::from("input.rs")));
@@ -152,6 +238,8 @@ mod tests {
             additional_derives: vec![],
             output: None,
             no_builder: false,
+            crates: vec![],
+            unknown_crates: Default::default(),
         };
 
         assert!(args.use_builder());
@@ -165,6 +253,8 @@ mod tests {
             additional_derives: vec![],
             output: None,
             no_builder: true,
+            crates: vec![],
+            unknown_crates: Default::default(),
         };
 
         assert!(!args.use_builder());
@@ -178,6 +268,8 @@ mod tests {
             additional_derives: vec![],
             output: None,
             no_builder: false,
+            crates: vec![],
+            unknown_crates: Default::default(),
         };
 
         assert!(args.use_builder());
diff --git a/extension-schema.json b/extension-schema.json
new file mode 100644
index 0000000..73e5822
--- /dev/null
+++ b/extension-schema.json
@@ -0,0 +1,29 @@
+{
+  "description": "schema for the x-rust-type extension",
+  "type": "object",
+  "properties": {
+    "crate": {
+      "type": "string",
+      "pattern": "^[a-zA-Z0-9_-]+$"
+    },
+    "version": {
+      "description": "semver requirements per a Cargo.toml dependencies entry",
+      "type": "string"
+    },
+    "path": {
+      "type": "string",
+      "pattern": "^[a-zA-Z0-9_]+(::[a-zA-Z0-9+]+)*$"
+    },
+    "parameters": {
+      "type": "array",
+      "items": {
+        "$ref": "#/definitions/Schema"
+      }
+    }
+  },
+  "required": [
+    "crate",
+    "path",
+    "version"
+  ]
+}
\ No newline at end of file
diff --git a/typify-impl/Cargo.toml b/typify-impl/Cargo.toml
index 470e1b3..60d04a3 100644
--- a/typify-impl/Cargo.toml
+++ b/typify-impl/Cargo.toml
@@ -14,6 +14,8 @@ proc-macro2 = "1.0.81"
 quote = "1.0.36"
 regress = "0.9.1"
 schemars = "0.8.17"
+semver = "1.0.22"
+serde = "1.0.200"
 serde_json = "1.0.116"
 syn = { version = "2.0.60", features = ["full"] }
 thiserror = "1.0.59"
@@ -26,7 +28,6 @@ expectorate = "1.1.0"
 paste = "1.0.14"
 rustfmt-wrapper = "0.2.1"
 schema = "0.1.0"
-schemars = { version = "0.8.17", features = ["uuid1"] }
-serde = "1.0.200"
+schemars = { version = "0.8.17", features = ["uuid1", "impl_json_schema"] }
 syn = { version = "2.0.60", features = ["full", "extra-traits"] }
 uuid = "1.8.0"
diff --git a/typify-impl/src/convert.rs b/typify-impl/src/convert.rs
index 1ad1666..ac8a7a0 100644
--- a/typify-impl/src/convert.rs
+++ b/typify-impl/src/convert.rs
@@ -49,6 +49,10 @@ impl TypeSpace {
         original_schema: &'a Schema,
         schema: &'a SchemaObject,
     ) -> Result<(TypeEntry, &'a Option<Box<Metadata>>)> {
+        if let Some(type_entry) = self.convert_rust_extension(schema) {
+            return Ok((type_entry, &schema.metadata));
+        }
+
         match schema {
             // If we have a schema that has an instance type array that's
             // exactly two elements and one of them is Null, we have the
diff --git a/typify-impl/src/lib.rs b/typify-impl/src/lib.rs
index 2090489..906f458 100644
--- a/typify-impl/src/lib.rs
+++ b/typify-impl/src/lib.rs
@@ -30,6 +30,7 @@ mod defaults;
 mod enums;
 mod merge;
 mod output;
+mod rust_extension;
 mod structs;
 mod type_entry;
 mod util;
@@ -242,11 +243,66 @@ pub struct TypeSpaceSettings {
     extra_derives: Vec<String>,
     struct_builder: bool,
 
+    unknown_crates: UnknownPolicy,
+    crates: BTreeMap<String, CrateSpec>,
+
     patch: BTreeMap<String, TypeSpacePatch>,
     replace: BTreeMap<String, TypeSpaceReplace>,
     convert: Vec<TypeSpaceConversion>,
 }
 
+#[derive(Debug, Clone)]
+struct CrateSpec {
+    version: CrateVers,
+    rename: Option<String>,
+}
+
+/// Policy to apply to external types described by schema extensions whose
+/// crates are not explicitly specified.
+#[derive(Default, Debug, Clone, Copy, Eq, PartialEq, serde::Deserialize)]
+pub enum UnknownPolicy {
+    /// Generate the type rather according to the schema.
+    #[default]
+    Generate,
+    /// Use the specified type by path (this will result in a compile error if
+    /// one of the crates is not an existing dependency). Note that this
+    /// ignores compatibility requirements specified by the schema extension
+    /// and may result in subtle failures if the crate used is incompatible
+    /// with the version that produced the schema.
+    Allow,
+    /// If an unknown crate is encountered, generate a compiler warning
+    /// indicating the crate that must be specified to proceed along with
+    /// version constraints. This affords users an opportunity to specify the
+    /// specific crate version to use (or the user may explicitly deny use of
+    /// that crate).
+    Deny,
+}
+
+/// Specify the version for a named crate to consider for type use (rather than
+/// generating types) in the presense of a schema extension.
+#[derive(Debug, Clone)]
+pub enum CrateVers {
+    /// An explicit version.
+    Version(semver::Version),
+    /// Any version.
+    Any,
+    /// Never use the given crate.
+    Never,
+}
+
+impl CrateVers {
+    /// Parse from a string
+    pub fn parse(s: &str) -> Option<Self> {
+        if s == "!" {
+            Some(Self::Never)
+        } else if s == "*" {
+            Some(Self::Any)
+        } else {
+            Some(Self::Version(semver::Version::parse(s).ok()?))
+        }
+    }
+}
+
 /// Contains a set of modifications that may be applied to an existing type.
 #[derive(Debug, Default, Clone)]
 pub struct TypeSpacePatch {
@@ -365,6 +421,38 @@ impl TypeSpaceSettings {
         });
         self
     }
+
+    /// Type schemas may contain an extension (`x-rust-type`) that indicates
+    /// the corresponding Rust type within a particular crate. This function
+    /// changes the disposition regarding crates not otherwise specified via
+    /// [`Self::with_crate`]. The default value is `false`.
+    pub fn with_unknown_crates(&mut self, policy: UnknownPolicy) -> &mut Self {
+        self.unknown_crates = policy;
+        self
+    }
+
+    /// Type schemas may contain an extension (`x-rust-type`) that indicates
+    /// the corresponding Rust type within a particular crate. This extension
+    /// indicates the crate, version compatibility, type path, and type
+    /// parameters. This function modifies settings to use (rather than
+    /// generate) types from the given crate and version. The version should
+    /// precisely match the version of the crate that you expect as a
+    /// dependency.
+    pub fn with_crate<S1: ToString>(
+        &mut self,
+        crate_name: S1,
+        version: CrateVers,
+        rename: Option<&String>,
+    ) -> &mut Self {
+        self.crates.insert(
+            crate_name.to_string(),
+            CrateSpec {
+                version,
+                rename: rename.cloned(),
+            },
+        );
+        self
+    }
 }
 
 impl TypeSpacePatch {
@@ -535,6 +623,8 @@ impl TypeSpace {
                 schema.clone(),
             ),
 
+            TypeEntryDetails::Native(native) if native.name_match(&type_name) => type_entry,
+
             // For types that don't have names, this is effectively a type
             // alias which we treat as a newtype.
             _ => {
@@ -554,8 +644,10 @@ impl TypeSpace {
                 )
             }
         };
-        let entry_name = type_entry.name().unwrap().clone();
-        self.name_to_id.insert(entry_name, type_id.clone());
+        // TODO need a type alias?
+        if let Some(entry_name) = type_entry.name() {
+            self.name_to_id.insert(entry_name.clone(), type_id.clone());
+        }
         self.id_to_entry.insert(type_id, type_entry);
         Ok(())
     }
diff --git a/typify-impl/src/rust_extension.rs b/typify-impl/src/rust_extension.rs
new file mode 100644
index 0000000..e7a6cf5
--- /dev/null
+++ b/typify-impl/src/rust_extension.rs
@@ -0,0 +1,104 @@
+// Copyright 2024 Oxide Computer Company
+
+use log::warn;
+use schemars::schema::{Schema, SchemaObject};
+use serde::Deserialize;
+
+use crate::{type_entry::TypeEntry, CrateVers, Name, Result, TypeSpace};
+
+const RUST_TYPE_EXTENSION: &str = "x-rust-type";
+
+/// Definition of the value of the x-rust-type extension. This structure
+/// must not change incompatibly (and probably shouldn't change at all).
+#[derive(Deserialize)]
+struct RustExtension {
+    #[serde(rename = "crate")]
+    crate_name: String,
+    version: String,
+    path: String,
+    #[serde(default)]
+    parameters: Vec<Schema>,
+}
+
+impl TypeSpace {
+    pub(crate) fn convert_rust_extension(&mut self, schema: &SchemaObject) -> Option<TypeEntry> {
+        let x_rust = schema.extensions.get(RUST_TYPE_EXTENSION)?;
+
+        let Ok(RustExtension {
+            crate_name,
+            version,
+            path,
+            parameters,
+        }) = serde_json::from_value(x_rust.clone())
+        else {
+            warn!(
+                "{} contains an invalid value for {}",
+                serde_json::to_string_pretty(&schema).unwrap(),
+                RUST_TYPE_EXTENSION,
+            );
+            return None;
+        };
+
+        let Ok(req) = semver::VersionReq::parse(&version) else {
+            warn!(
+                "{} contains an invalid version",
+                serde_json::to_string_pretty(&schema).unwrap(),
+            );
+            return None;
+        };
+
+        let crate_ident = crate_name.replace('-', "_");
+        let path_sep = path.find("::")?;
+        if crate_ident != path[..path_sep] {
+            warn!(
+                "{} path doesn't start with crate name",
+                serde_json::to_string_pretty(&schema).unwrap(),
+            );
+            return None;
+        }
+
+        let path = {
+            if let Some(crate_spec) = self.settings.crates.get(crate_name.as_str()) {
+                // The version must be non-Never and match the requirements
+                // from the extension.
+                match &crate_spec.version {
+                    CrateVers::Any => (),
+                    CrateVers::Version(version) if req.matches(version) => (),
+                    _ => return None,
+                }
+
+                // Replace the initial path component with the new crate name.
+                if let Some(new_crate) = &crate_spec.rename {
+                    format!("{}{}", new_crate.replace('-', "_"), &path[path_sep..])
+                } else {
+                    path
+                }
+            } else {
+                match self.settings.unknown_crates {
+                    crate::UnknownPolicy::Generate => return None,
+                    crate::UnknownPolicy::Allow => path,
+
+                    // TODO need to bubble up a coherent compiler error via the
+                    // generated code.
+                    crate::UnknownPolicy::Deny => return None,
+                }
+            }
+        };
+
+        // Convert and collect type parameters.
+        let param_ids = parameters
+            .iter()
+            .map(|p_schema| {
+                // TODO could we have some reasonable type name? Do we need to?
+                let (param_id, _) = self.id_for_schema(Name::Unknown, p_schema)?;
+                Ok(param_id)
+            })
+            .collect::<Result<Vec<_>>>()
+            .ok()?;
+
+        Some(TypeEntry::new_native_params(
+            format!("::{path}"),
+            &param_ids,
+        ))
+    }
+}
diff --git a/typify-impl/src/type_entry.rs b/typify-impl/src/type_entry.rs
index cc85120..d45d5c8 100644
--- a/typify-impl/src/type_entry.rs
+++ b/typify-impl/src/type_entry.rs
@@ -20,16 +20,16 @@ pub(crate) struct SchemaWrapper(Schema);
 
 impl Eq for SchemaWrapper {}
 
-impl PartialOrd for SchemaWrapper {
-    fn partial_cmp(&self, _other: &Self) -> Option<std::cmp::Ordering> {
-        Some(std::cmp::Ordering::Equal)
-    }
-}
 impl Ord for SchemaWrapper {
     fn cmp(&self, _other: &Self) -> std::cmp::Ordering {
         std::cmp::Ordering::Equal
     }
 }
+impl PartialOrd for SchemaWrapper {
+    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+        Some(self.cmp(other))
+    }
+}
 
 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
 pub(crate) struct TypeEntryEnum {
@@ -89,6 +89,17 @@ pub(crate) enum TypeEntryNewtypeConstraints {
 pub(crate) struct TypeEntryNative {
     pub type_name: String,
     impls: Vec<TypeSpaceImpl>,
+    // TODO to support const generics, this can be some sort of TypeOrValue,
+    // but note that we may some day need to disambiguate char and &'static str
+    // since schemars represents a char as a string of length 1.
+    pub parameters: Vec<TypeId>,
+}
+impl TypeEntryNative {
+    pub(crate) fn name_match(&self, type_name: &Name) -> bool {
+        let native_name = self.type_name.rsplit("::").next().unwrap();
+        !self.parameters.is_empty()
+            || matches!(type_name, Name::Required(req) if req == native_name)
+    }
 }
 
 #[derive(Debug, Clone, PartialEq, Eq)]
@@ -105,8 +116,8 @@ impl Ord for WrappedValue {
     }
 }
 impl PartialOrd for WrappedValue {
-    fn partial_cmp(&self, _: &Self) -> Option<std::cmp::Ordering> {
-        Some(std::cmp::Ordering::Equal)
+    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+        Some(self.cmp(other))
     }
 }
 
@@ -464,6 +475,17 @@ impl TypeEntry {
             details: TypeEntryDetails::Native(TypeEntryNative {
                 type_name: type_name.to_string(),
                 impls: impls.to_vec(),
+                parameters: Default::default(),
+            }),
+            extra_derives: Default::default(),
+        }
+    }
+    pub(crate) fn new_native_params<S: ToString>(type_name: S, params: &[TypeId]) -> Self {
+        TypeEntry {
+            details: TypeEntryDetails::Native(TypeEntryNative {
+                type_name: type_name.to_string(),
+                impls: Default::default(),
+                parameters: params.to_vec(),
             }),
             extra_derives: Default::default(),
         }
@@ -1644,17 +1666,40 @@ impl TypeEntry {
                 quote! { [#item_ident; #length]}
             }
 
+            TypeEntryDetails::Native(TypeEntryNative {
+                type_name,
+                impls: _,
+                parameters,
+            }) => {
+                let path =
+                    syn::parse_str::<syn::TypePath>(type_name).expect("type path wasn't valid");
+
+                let type_idents = (!parameters.is_empty()).then(|| {
+                    let type_idents = parameters.iter().map(|type_id| {
+                        type_space
+                            .id_to_entry
+                            .get(type_id)
+                            .expect("unresolved type id for tuple")
+                            .type_ident(type_space, type_mod)
+                    });
+                    quote! { < #(#type_idents,)* > }
+                });
+
+                quote! {
+                    #path
+                    #type_idents
+                }
+            }
+
             TypeEntryDetails::Unit => quote! { () },
             TypeEntryDetails::String => quote! { String },
             TypeEntryDetails::Boolean => quote! { bool },
             TypeEntryDetails::JsonValue => quote! { serde_json::Value },
-            TypeEntryDetails::Native(TypeEntryNative {
-                type_name: name, ..
-            })
-            | TypeEntryDetails::Integer(name)
-            | TypeEntryDetails::Float(name) => syn::parse_str::<syn::TypePath>(name)
-                .unwrap()
-                .to_token_stream(),
+            TypeEntryDetails::Integer(name) | TypeEntryDetails::Float(name) => {
+                syn::parse_str::<syn::TypePath>(name)
+                    .unwrap()
+                    .to_token_stream()
+            }
 
             TypeEntryDetails::Reference(_) => panic!("references should be resolved by now"),
         }
diff --git a/typify-macro/Cargo.toml b/typify-macro/Cargo.toml
index b491cbb..c3ab84c 100644
--- a/typify-macro/Cargo.toml
+++ b/typify-macro/Cargo.toml
@@ -14,6 +14,7 @@ proc-macro = true
 proc-macro2 = "1.0.81"
 quote = "1.0.36"
 schemars = "0.8.17"
+semver = { version = "1.0.22", features = ["serde"] }
 serde = "1.0.200"
 serde_json = "1.0.116"
 serde_tokenstream = "0.2.0"
diff --git a/typify-macro/src/lib.rs b/typify-macro/src/lib.rs
index c11d9bb..ad5e1bd 100644
--- a/typify-macro/src/lib.rs
+++ b/typify-macro/src/lib.rs
@@ -1,4 +1,4 @@
-// Copyright 2023 Oxide Computer Company
+// Copyright 2024 Oxide Computer Company
 
 //! typify macro implementation.
 
@@ -12,7 +12,7 @@ use serde::Deserialize;
 use serde_tokenstream::ParseWrapper;
 use syn::LitStr;
 use token_utils::TypeAndImpls;
-use typify_impl::{TypeSpace, TypeSpacePatch, TypeSpaceSettings};
+use typify_impl::{CrateVers, TypeSpace, TypeSpacePatch, TypeSpaceSettings, UnknownPolicy};
 
 mod token_utils;
 
@@ -37,6 +37,20 @@ mod token_utils;
 ///   method for each generated struct that can be used to specify each
 ///   property and construct the struct
 ///
+/// - `unknown_crates`: optional policy regarding the handling of schemas that
+///   contain the `x-rust-type` extension whose crates are not explicitly named
+///   in the `crates` section. The options are `generate` to ignore the
+///   extension and generate a *de novo* type, `allow` to use the named type
+///   (which may require the addition of a new dependency to compile, and which
+///   ignores version compatibility checks), or `deny` to produce a
+///   compile-time error (requiring the user to specify the crate's disposition
+///   in the `crates` section).
+///
+/// - `crates`: optional map from crate name to the version of the crate in
+///   use. Types encountered with the Rust type extension (`x-rust-type`) will
+///   use types from the specified crates rather than generating them (within
+///   the constraints of type compatibility).
+///
 /// - `patch`: optional map from type to an object with the optional members
 ///   `rename` and `derives`. This may be used to renamed generated types or
 ///   to apply additional (non-default) derive macros to them.
@@ -64,6 +78,11 @@ struct MacroSettings {
     #[serde(default)]
     struct_builder: bool,
 
+    #[serde(default)]
+    unknown_crates: UnknownPolicy,
+    #[serde(default)]
+    crates: HashMap<CrateName, MacroCrateSpec>,
+
     #[serde(default)]
     patch: HashMap<ParseWrapper<syn::Ident>, MacroPatch>,
     #[serde(default)]
@@ -73,6 +92,68 @@ struct MacroSettings {
         serde_tokenstream::OrderedMap<schemars::schema::SchemaObject, ParseWrapper<TypeAndImpls>>,
 }
 
+struct MacroCrateSpec {
+    original: Option<String>,
+    version: CrateVers,
+}
+
+impl<'de> Deserialize<'de> for MacroCrateSpec {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: serde::Deserializer<'de>,
+    {
+        let ss = String::deserialize(deserializer)?;
+
+        let (original, vers_str) = if let Some(ii) = ss.find('@') {
+            let original_str = &ss[..ii];
+            let rest = &ss[ii + 1..];
+            if !is_crate(original_str) {
+                return Err(<D::Error as serde::de::Error>::invalid_value(
+                    serde::de::Unexpected::Str(&ss),
+                    &"valid crate name",
+                ));
+            }
+
+            (Some(original_str.to_string()), rest)
+        } else {
+            (None, ss.as_ref())
+        };
+
+        let Some(version) = CrateVers::parse(vers_str) else {
+            return Err(<D::Error as serde::de::Error>::invalid_value(
+                serde::de::Unexpected::Str(&ss),
+                &"valid version",
+            ));
+        };
+
+        Ok(Self { original, version })
+    }
+}
+
+#[derive(Hash, PartialEq, Eq)]
+struct CrateName(String);
+impl<'de> Deserialize<'de> for CrateName {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: serde::Deserializer<'de>,
+    {
+        let ss = String::deserialize(deserializer)?;
+
+        if is_crate(&ss) {
+            Ok(Self(ss))
+        } else {
+            Err(<D::Error as serde::de::Error>::invalid_value(
+                serde::de::Unexpected::Str(&ss),
+                &"valid crate name",
+            ))
+        }
+    }
+}
+
+fn is_crate(s: &str) -> bool {
+    !s.contains(|cc: char| !cc.is_alphanumeric() && cc != '_' && cc != '-')
+}
+
 #[derive(Deserialize)]
 struct MacroPatch {
     #[serde(default)]
@@ -106,6 +187,8 @@ fn do_import_types(item: TokenStream) -> Result<TokenStream, syn::Error> {
             patch,
             struct_builder,
             convert,
+            unknown_crates,
+            crates,
         } = serde_tokenstream::from_tokenstream(&item.into())?;
         let mut settings = TypeSpaceSettings::default();
         derives.into_iter().for_each(|derive| {
@@ -125,6 +208,17 @@ fn do_import_types(item: TokenStream) -> Result<TokenStream, syn::Error> {
             settings.with_conversion(schema, type_name, impls);
         });
 
+        crates.into_iter().for_each(
+            |(CrateName(crate_name), MacroCrateSpec { original, version })| {
+                if let Some(original_crate) = original {
+                    settings.with_crate(original_crate, version, Some(&crate_name));
+                } else {
+                    settings.with_crate(crate_name, version, None);
+                }
+            },
+        );
+        settings.with_unknown_crates(unknown_crates);
+
         (schema.into_inner(), settings)
     };
 
diff --git a/typify/Cargo.toml b/typify/Cargo.toml
index 1170433..96fc3f9 100644
--- a/typify/Cargo.toml
+++ b/typify/Cargo.toml
@@ -28,5 +28,5 @@ rustfmt-wrapper = "0.2.1"
 schemars = "0.8.17"
 serde = "1.0.200"
 serde_json = "1.0.116"
-trybuild = "1.0.93"
+trybuild = "1.0.95"
 uuid = { version = "1.8.0", features = ["serde"] }
diff --git a/typify/src/lib.rs b/typify/src/lib.rs
index ae39d39..28ef02a 100644
--- a/typify/src/lib.rs
+++ b/typify/src/lib.rs
@@ -1,4 +1,4 @@
-// Copyright 2023 Oxide Computer Company
+// Copyright 2024 Oxide Computer Company
 
 //! # Typify
 //!
@@ -156,6 +156,7 @@
 
 #![deny(missing_docs)]
 
+pub use typify_impl::CrateVers;
 pub use typify_impl::Error;
 pub use typify_impl::Type;
 pub use typify_impl::TypeDetails;
@@ -169,5 +170,6 @@ pub use typify_impl::TypeSpacePatch;
 pub use typify_impl::TypeSpaceSettings;
 pub use typify_impl::TypeStruct;
 pub use typify_impl::TypeStructPropInfo;
+pub use typify_impl::UnknownPolicy;
 #[cfg(feature = "macro")]
 pub use typify_macro::import_types;


Test patch:
diff --git a/cargo-typify/tests/outputs/help.txt b/cargo-typify/tests/outputs/help.txt
index d02307a..7417216 100644
--- a/cargo-typify/tests/outputs/help.txt
+++ b/cargo-typify/tests/outputs/help.txt
@@ -21,6 +21,14 @@ Options:
           
           If `-` is specified, the output will be written to stdout.
 
+      --crate <CRATES>
+          Specify each crate@version that can be assumed to be in use for types found in the schema with the x-rust-type extension
+
+      --unknown-crates <UNKNOWN_CRATES>
+          Specify the policy unknown crates found in schemas with the x-rust-type extension
+          
+          [possible values: generate, allow, deny]
+
   -h, --help
           Print help (see a summary with '-h')
 
diff --git a/typify-impl/tests/test_github.rs b/typify-impl/tests/test_github.rs
index d4ff0e9..dd14820 100644
--- a/typify-impl/tests/test_github.rs
+++ b/typify-impl/tests/test_github.rs
@@ -29,7 +29,7 @@ fn test_github() {
 fn test_vega() {
     env_logger::init();
     let mut settings = TypeSpaceSettings::default();
-    let raw_schema = serde_json::json! {
+    let raw_schema = serde_json::json!(
         {
             "enum": [
               null,
@@ -57,7 +57,7 @@ fn test_vega() {
               900
             ]
           }
-    };
+    );
     let schema = serde_json::from_value(raw_schema).unwrap();
     settings
         .with_conversion(schema, "MyEnum", [TypeSpaceImpl::FromStr].into_iter())
diff --git a/typify/tests/schemas.rs b/typify/tests/schemas.rs
index 3854d6e..751aa84 100644
--- a/typify/tests/schemas.rs
+++ b/typify/tests/schemas.rs
@@ -1,4 +1,4 @@
-// Copyright 2023 Oxide Computer Company
+// Copyright 2024 Oxide Computer Company
 
 use std::{error::Error, fs::File, io::BufReader};
 
@@ -32,11 +32,11 @@ fn validate_schema(path: std::path::PathBuf) -> Result<(), Box<dyn Error>> {
     // Read the JSON contents of the file as an instance of `User`.
     let root_schema: RootSchema = serde_json::from_reader(reader)?;
 
-    let schema_raw = json! {
+    let schema_raw = json!(
         {
             "enum": [ 1, "one" ]
         }
-    };
+    );
     let schema = serde_json::from_value(schema_raw).unwrap();
 
     let mut type_space = TypeSpace::new(
@@ -57,6 +57,13 @@ fn validate_schema(path: std::path::PathBuf) -> Result<(), Box<dyn Error>> {
                 schema,
                 "serde_json::Value",
                 [TypeSpaceImpl::Display].into_iter(),
+            )
+            // Our test use of the x-rust-type extension only refers to things
+            // in std.
+            .with_crate(
+                "std",
+                typify::CrateVers::Version("1.0.0".parse().unwrap()),
+                None,
             ),
     );
     type_space.add_root_schema(root_schema)?;
diff --git a/typify/tests/schemas/x-rust-type.json b/typify/tests/schemas/x-rust-type.json
new file mode 100644
index 0000000..139d379
--- /dev/null
+++ b/typify/tests/schemas/x-rust-type.json
@@ -0,0 +1,42 @@
+{
+  "$schema": "https://json-schema.org/draft-07/schema",
+  "$comment": "this test makes weird use of types from std to avoid requiring other dependencies",
+  "$defs": {
+    "AllTheThings": {
+      "type": "object",
+      "properties": {
+        "path": {
+          "$ref": "#/$defs/PathBuf"
+        },
+        "option_marker": {
+          "$ref": "#/$defs/OptionMarker"
+        }
+      }
+    },
+    "PathBuf": {
+      "type": "string",
+      "x-rust-type": {
+        "crate": "std",
+        "version": "1.0.0",
+        "path": "std::path::PathBuf"
+      }
+    },
+    "OptionMarker": {
+      "$comment": "this is silly, but shows type parameters",
+      "type": "null",
+      "x-rust-type": {
+        "crate": "std",
+        "version": "1.0.0",
+        "path": "std::option::Option",
+        "parameters": [
+          {
+            "$ref": "#/$defs/Marker"
+          }
+        ]
+      }
+    },
+    "Marker": {
+      "not": true
+    }
+  }
+}
\ No newline at end of file
diff --git a/typify/tests/schemas/x-rust-type.rs b/typify/tests/schemas/x-rust-type.rs
new file mode 100644
index 0000000..3d21c09
--- /dev/null
+++ b/typify/tests/schemas/x-rust-type.rs
@@ -0,0 +1,75 @@
+#[allow(unused_imports)]
+use serde::{Deserialize, Serialize};
+#[doc = r" Error types."]
+pub mod error {
+    #[doc = r" Error from a TryFrom or FromStr implementation."]
+    pub struct ConversionError(std::borrow::Cow<'static, str>);
+    impl std::error::Error for ConversionError {}
+    impl std::fmt::Display for ConversionError {
+        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
+            std::fmt::Display::fmt(&self.0, f)
+        }
+    }
+    impl std::fmt::Debug for ConversionError {
+        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
+            std::fmt::Debug::fmt(&self.0, f)
+        }
+    }
+    impl From<&'static str> for ConversionError {
+        fn from(value: &'static str) -> Self {
+            Self(value.into())
+        }
+    }
+    impl From<String> for ConversionError {
+        fn from(value: String) -> Self {
+            Self(value.into())
+        }
+    }
+}
+#[doc = "AllTheThings"]
+#[doc = r""]
+#[doc = r" <details><summary>JSON schema</summary>"]
+#[doc = r""]
+#[doc = r" ```json"]
+#[doc = "{"]
+#[doc = "  \"type\": \"object\","]
+#[doc = "  \"properties\": {"]
+#[doc = "    \"option_marker\": {"]
+#[doc = "      \"$ref\": \"#/$defs/OptionMarker\""]
+#[doc = "    },"]
+#[doc = "    \"path\": {"]
+#[doc = "      \"$ref\": \"#/$defs/PathBuf\""]
+#[doc = "    }"]
+#[doc = "  }"]
+#[doc = "}"]
+#[doc = r" ```"]
+#[doc = r" </details>"]
+#[derive(Clone, Debug, Deserialize, Serialize)]
+pub struct AllTheThings {
+    #[serde(default, skip_serializing_if = "Option::is_none")]
+    pub option_marker: Option<::std::option::Option<Marker>>,
+    #[serde(default, skip_serializing_if = "Option::is_none")]
+    pub path: Option<::std::path::PathBuf>,
+}
+impl From<&AllTheThings> for AllTheThings {
+    fn from(value: &AllTheThings) -> Self {
+        value.clone()
+    }
+}
+#[doc = "Marker"]
+#[doc = r""]
+#[doc = r" <details><summary>JSON schema</summary>"]
+#[doc = r""]
+#[doc = r" ```json"]
+#[doc = "false"]
+#[doc = r" ```"]
+#[doc = r" </details>"]
+#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
+#[serde(deny_unknown_fields)]
+pub enum Marker {}
+impl From<&Marker> for Marker {
+    fn from(value: &Marker) -> Self {
+        value.clone()
+    }
+}
+fn main() {}