Repository: GREsau/schemars
Pull request #342 (2024-09-20 08:00:04)

chore: Update jsonschema to 0.20.0
I've been updating the public API in `jsonschema` and I am here to update the version & add usage of the new API instead of the deprecated one. 


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a84c477..9f6ce93 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,14 +1,21 @@
 # Changelog
 
-## [1.0.0-alpha.16] - **in-dev**
+## [1.0.0-alpha.17] - 2024-12-02
+
+### Changed
+
+- For newtype variants of internally-tagged enums, prefer referencing the inner type's schema via `$ref` instead of always inlining the schema (https://github.com/GREsau/schemars/pull/355) _(this change was included in the release notes for 1.0.0-alpha.16, but was accidentally excluded from the published crate)_
+
+## [1.0.0-alpha.16] - 2024-11-25
 
 ### Removed (_⚠️ breaking changes ⚠️_)
 
 - the `enumset1`/`enumset` optional dependency has been removed, as its `JsonSchema` impl did not actually match the default serialization format of `EnumSet` (https://github.com/GREsau/schemars/pull/339)
 
-### Changed
+### Changed (_⚠️ breaking changes ⚠️_)
 
-- ⚠️ MSRV is now 1.70 ⚠️
+- MSRV is now 1.70
+- [The `example` attribute](https://graham.cool/schemars/deriving/attributes/#example) value is now an arbitrary expression, rather than a string literal identifying a function to call. To avoid silent behaviour changes, the expression must not be a string literal where the value can be parsed as a function path - e.g. `#[schemars(example = "foo")]` is now a compile error, but `#[schemars(example = foo())]` is allowed (as is `#[schemars(example = &"foo")]` if you want the the literal string value `"foo"` to be the example).
 
 ### Fixed
 
diff --git a/Cargo.lock b/Cargo.lock
index 0f07620..0f30998 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -416,9 +416,9 @@ dependencies = [
 
 [[package]]
 name = "jsonschema"
-version = "0.19.0"
+version = "0.20.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2be7aa9f08a262039c0b5eb11b792e4353dad5263f78e33312b7ba9e43b32ae9"
+checksum = "f2eef4e82b548e08ac880d307c8e8838b45f497a08d3202f3b26c9debaed8058"
 dependencies = [
  "ahash",
  "anyhow",
@@ -771,7 +771,7 @@ checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
 
 [[package]]
 name = "schemars"
-version = "1.0.0-alpha.15"
+version = "1.0.0-alpha.17"
 dependencies = [
  "arrayvec",
  "bigdecimal",
@@ -802,7 +802,7 @@ dependencies = [
 
 [[package]]
 name = "schemars_derive"
-version = "1.0.0-alpha.15"
+version = "1.0.0-alpha.17"
 dependencies = [
  "pretty_assertions",
  "proc-macro2",
diff --git a/README.md b/README.md
index b8afef2..fc68efc 100644
--- a/README.md
+++ b/README.md
@@ -280,5 +280,5 @@ For example, to implement `JsonSchema` on types from `chrono`, enable it as a fe
 
 ```toml
 [dependencies]
-schemars = { version = "1.0.0-alpha.15", features = ["chrono04"] }
+schemars = { version = "1.0.0-alpha.17", features = ["chrono04"] }
 ```
diff --git a/docs/4-features.md b/docs/4-features.md
index b0b2046..1141154 100644
--- a/docs/4-features.md
+++ b/docs/4-features.md
@@ -30,5 +30,5 @@ For example, to implement `JsonSchema` on types from `chrono`, enable it as a fe
 
 ```toml
 [dependencies]
-schemars = { version = "1.0.0-alpha.15", features = ["chrono04"] }
+schemars = { version = "1.0.0-alpha.17", features = ["chrono04"] }
 ```
diff --git a/docs/_includes/attributes.md b/docs/_includes/attributes.md
index 37f6833..1497b0f 100644
--- a/docs/_includes/attributes.md
+++ b/docs/_includes/attributes.md
@@ -298,13 +298,15 @@ Set on a container, variant or field to set the generated schema's `title` and/o
 
 <h3 id="example">
 
-`#[schemars(example = "some::function")]`
+`#[schemars(example = value)]`
 
 </h3>
 
-Set on a container, variant or field to include the result of the given function in the generated schema's `examples`. The function should take no parameters and can return any type that implements serde's `Serialize` trait - it does not need to return the same type as the attached struct/field. This attribute can be repeated to specify multiple examples.
+Set on a container, variant or field to include the given value in the generated schema's `examples`. The value can be any type that implements serde's `Serialize` trait - it does not need to be the same type as the attached struct/field. This attribute can be repeated to specify multiple examples.
 
-To use the result of arbitrary expressions as examples, you can instead use the [`extend`](#extend) attribute, e.g. `[schemars(extend("examples" = ["example string"]))]`.
+In previous versions of schemars, the value had to be a string literal identifying a defined function that would be called to return the actual example value (similar to the [`default`](#default) attribute). To avoid the new attribute behaviour from silently breaking old consumers, string literals consisting of a single word (e.g. `#[schemars(example = "my_fn")]`) or a path (e.g. `#[schemars(example = "my_mod::my_fn")]`) are currently disallowed. This restriction may be relaxed in a future version of schemars, but for now if you want to include such a string as the literal example value, this can be done by borrowing the value, e.g. `#[schemars(example = &"my_fn")]`. If you instead want to call a function to get the example value (mirrorring the old behaviour), you must use an explicit function call expression, e.g. `#[schemars(example = my_fn())]`.
+
+Alternatively, to directly set multiple examples without repeating `example = ...` attribute, you can instead use the [`extend`](#extend) attribute, e.g. `#[schemars(extend("examples" = [1, 2, 3]))]`.
 
 <h3 id="deprecated">
 
diff --git a/schemars/Cargo.toml b/schemars/Cargo.toml
index 384f3ea..96232ea 100644
--- a/schemars/Cargo.toml
+++ b/schemars/Cargo.toml
@@ -3,7 +3,7 @@ name = "schemars"
 description = "Generate JSON Schemas from Rust code"
 homepage = "https://graham.cool/schemars/"
 repository = "https://github.com/GREsau/schemars"
-version = "1.0.0-alpha.15"
+version = "1.0.0-alpha.17"
 authors = ["Graham Esau <gesau@hotmail.co.uk>"]
 edition = "2021"
 license = "MIT"
@@ -13,7 +13,7 @@ categories = ["encoding", "no-std"]
 rust-version = "1.70"
 
 [dependencies]
-schemars_derive = { version = "=1.0.0-alpha.15", optional = true, path = "../schemars_derive" }
+schemars_derive = { version = "=1.0.0-alpha.17", optional = true, path = "../schemars_derive" }
 serde = { version = "1.0", default-features = false, features = ["alloc"]}
 serde_json = { version =  "1.0.127", default-features = false, features = ["alloc"] }
 dyn-clone = "1.0"
@@ -37,7 +37,7 @@ uuid1 = { version = "1.0", default-features = false, optional = true, package =
 pretty_assertions = "1.2.1"
 trybuild = "1.0"
 serde = { version = "1.0", features = ["derive"] }
-jsonschema = { version = "0.19.0", default-features = false }
+jsonschema = { version = "0.20", default-features = false }
 snapbox = { version = "0.6.17", features = ["json"] }
 serde_repr = "0.1.19"
 # Use github source until published garde version supports `length(equal = ...)` attr
diff --git a/schemars/src/_private/mod.rs b/schemars/src/_private/mod.rs
index 3989f49..aa5c6fe 100644
--- a/schemars/src/_private/mod.rs
+++ b/schemars/src/_private/mod.rs
@@ -1,5 +1,5 @@
 use crate::_alloc_prelude::*;
-use crate::transform::transform_immediate_subschemas;
+use crate::transform::{transform_immediate_subschemas, Transform};
 use crate::{JsonSchema, Schema, SchemaGenerator};
 use serde::Serialize;
 use serde_json::{json, map::Entry, Map, Value};
@@ -12,6 +12,39 @@ pub extern crate serde_json;
 
 pub use rustdoc::get_title_and_description;
 
+pub fn json_schema_for_internally_tagged_enum_newtype_variant<T: ?Sized + JsonSchema>(
+    generator: &mut SchemaGenerator,
+) -> Schema {
+    let mut schema = T::json_schema(generator);
+
+    // Inline the newtype's inner schema if any of:
+    // - The type specifies that its schema should always be inlined
+    // - The generator settings specify that all schemas should be inlined
+    // - The inner type is a unit struct, which would cause an unsatisfiable schema due to mismatched `type`.
+    //    In this case, we replace its type with "object" in `apply_internal_enum_variant_tag`
+    // - The inner schema specified `"additionalProperties": false` or `"unevaluatedProperties": false`,
+    //    since that would disallow the variant tag. If additional/unevaluatedProperties is in the top-level
+    //    schema, then we can leave it there, because it will "see" the variant tag property. But if it is
+    //    nested e.g. in an `allOf`, then it must be removed, which is why we run `AllowUnknownProperties`
+    //    but only on immediate subschemas.
+
+    let mut transform = AllowUnknownProperties::default();
+    transform_immediate_subschemas(&mut transform, &mut schema);
+
+    if T::always_inline_schema()
+        || generator.settings().inline_subschemas
+        || schema.get("type").and_then(Value::as_str) == Some("null")
+        || schema.get("additionalProperties").and_then(Value::as_bool) == Some(false)
+        || schema.get("unevaluatedProperties").and_then(Value::as_bool) == Some(false)
+        || transform.did_modify
+    {
+        return schema;
+    }
+
+    // ...otherwise, we can freely refer to the schema via a `$ref`
+    generator.subschema_for::<T>()
+}
+
 // Helper for generating schemas for flattened `Option` fields.
 pub fn json_schema_for_flatten<T: ?Sized + JsonSchema>(
     generator: &mut SchemaGenerator,
@@ -25,20 +58,29 @@ pub fn json_schema_for_flatten<T: ?Sized + JsonSchema>(
 
     // Always allow aditional/unevaluated properties, because the outer struct determines
     // whether it denies unknown fields.
-    allow_unknown_properties(&mut schema);
+    AllowUnknownProperties::default().transform(&mut schema);
 
     schema
 }
 
-fn allow_unknown_properties(schema: &mut Schema) {
-    if schema.get("additionalProperties").and_then(Value::as_bool) == Some(false) {
-        schema.remove("additionalProperties");
-    }
-    if schema.get("unevaluatedProperties").and_then(Value::as_bool) == Some(false) {
-        schema.remove("unevaluatedProperties");
-    }
+#[derive(Default)]
+struct AllowUnknownProperties {
+    did_modify: bool,
+}
 
-    transform_immediate_subschemas(&mut allow_unknown_properties, schema);
+impl Transform for AllowUnknownProperties {
+    fn transform(&mut self, schema: &mut Schema) {
+        if schema.get("additionalProperties").and_then(Value::as_bool) == Some(false) {
+            schema.remove("additionalProperties");
+            self.did_modify = true;
+        }
+        if schema.get("unevaluatedProperties").and_then(Value::as_bool) == Some(false) {
+            schema.remove("unevaluatedProperties");
+            self.did_modify = true;
+        }
+
+        transform_immediate_subschemas(self, schema);
+    }
 }
 
 /// Hack to simulate specialization:
diff --git a/schemars/src/generate.rs b/schemars/src/generate.rs
index 375fa08..498d3b4 100644
--- a/schemars/src/generate.rs
+++ b/schemars/src/generate.rs
@@ -671,13 +671,14 @@ where
 
 impl Debug for Box<dyn GenTransform> {
     fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        #[allow(clippy::used_underscore_items)]
         self._debug_type_name(f)
     }
 }
 
 fn _assert_send() {
-    fn _assert<T: Send>() {}
+    fn assert<T: Send>() {}
 
-    _assert::<SchemaSettings>();
-    _assert::<SchemaGenerator>();
+    assert::<SchemaSettings>();
+    assert::<SchemaGenerator>();
 }
diff --git a/schemars/src/lib.rs b/schemars/src/lib.rs
index b516c6f..b62a153 100644
--- a/schemars/src/lib.rs
+++ b/schemars/src/lib.rs
@@ -145,7 +145,6 @@ pub mod r#gen {
 ///
 /// assert_eq!(<GenericType<i32>>::schema_id(), <&mut GenericType<&i32>>::schema_id());
 /// ```
-
 pub trait JsonSchema {
     /// Whether JSON Schemas generated for this type should be included directly in parent schemas,
     /// rather than being re-used where possible using the `$ref` keyword.
diff --git a/schemars_derive/Cargo.toml b/schemars_derive/Cargo.toml
index f6c2ae7..1264f59 100644
--- a/schemars_derive/Cargo.toml
+++ b/schemars_derive/Cargo.toml
@@ -3,7 +3,7 @@ name = "schemars_derive"
 description = "Macros for #[derive(JsonSchema)], for use with schemars"
 homepage = "https://graham.cool/schemars/"
 repository = "https://github.com/GREsau/schemars"
-version = "1.0.0-alpha.15"
+version = "1.0.0-alpha.17"
 authors = ["Graham Esau <gesau@hotmail.co.uk>"]
 edition = "2021"
 license = "MIT"
diff --git a/schemars_derive/src/ast/mod.rs b/schemars_derive/src/ast/mod.rs
index 9e632c5..c098959 100644
--- a/schemars_derive/src/ast/mod.rs
+++ b/schemars_derive/src/ast/mod.rs
@@ -48,7 +48,7 @@ impl<'a> Container<'a> {
             .map(|_| result.expect("from_ast set no errors on Ctxt, so should have returned Ok"))
     }
 
-    pub fn transparent_field(&'a self) -> Option<&'a Field> {
+    pub fn transparent_field(&'a self) -> Option<&'a Field<'a>> {
         if self.serde_attrs.transparent() {
             if let Data::Struct(_, fields) = &self.data {
                 return Some(&fields[0]);
@@ -63,7 +63,7 @@ impl<'a> Container<'a> {
     }
 }
 
-impl<'a> Variant<'a> {
+impl Variant<'_> {
     pub fn name(&self) -> Name {
         Name(self.serde_attrs.name())
     }
@@ -85,7 +85,7 @@ impl<'a> Variant<'a> {
     }
 }
 
-impl<'a> Field<'a> {
+impl Field<'_> {
     pub fn name(&self) -> Name {
         Name(self.serde_attrs.name())
     }
diff --git a/schemars_derive/src/attr/mod.rs b/schemars_derive/src/attr/mod.rs
index 14fd6eb..37350d3 100644
--- a/schemars_derive/src/attr/mod.rs
+++ b/schemars_derive/src/attr/mod.rs
@@ -21,7 +21,7 @@ pub struct CommonAttrs {
     pub deprecated: bool,
     pub title: Option<Expr>,
     pub description: Option<Expr>,
-    pub examples: Vec<Path>,
+    pub examples: Vec<Expr>,
     pub extensions: Vec<(String, TokenStream)>,
     pub transforms: Vec<Expr>,
 }
@@ -84,7 +84,24 @@ impl CommonAttrs {
             },
 
             "example" => {
-                self.examples.extend(parse_name_value_lit_str(meta, cx));
+                if let Ok(expr) = parse_name_value_expr(meta, cx) {
+                    if let Expr::Lit(ExprLit {
+                        lit: Lit::Str(lit_str),
+                        ..
+                    }) = &expr
+                    {
+                        if lit_str.parse::<Path>().is_ok() {
+                            let lit_str_value = lit_str.value();
+                            cx.error_spanned_by(&expr, format_args!(
+                                "`example` value must be an expression, and string literals that may be interpreted as function paths are currently disallowed to avoid migration errors \
+                                 (this restriction may be relaxed in a future version of schemars).\n\
+                                If you want to use the result of a function, use `#[schemars(example = {lit_str_value}())]`.\n\
+                                Or to use the string literal value, use `#[schemars(example = &\"{lit_str_value}\")]`."));
+                        }
+                    }
+
+                    self.examples.push(expr);
+                }
             }
 
             "extend" => {
@@ -114,7 +131,7 @@ impl CommonAttrs {
                             cx.error_spanned_by(
                                 &expr,
                                 format_args!(
-                                    "Expected a `fn(&mut Schema)` or other value implementing `schemars::transform::Transform`, found `&str`.\nDid you mean `[schemars(transform = {})]`?",
+                                    "Expected a `fn(&mut Schema)` or other value implementing `schemars::transform::Transform`, found `&str`.\nDid you mean `#[schemars(transform = {})]`?",
                                     lit_str.value()
                                 ),
                             )
@@ -178,7 +195,7 @@ impl CommonAttrs {
         if !self.examples.is_empty() {
             let examples = self.examples.iter().map(|eg| {
                 quote! {
-                    schemars::_private::serde_json::value::to_value(#eg())
+                    schemars::_private::serde_json::value::to_value(#eg)
                 }
             });
             mutators.push(quote! {
diff --git a/schemars_derive/src/schema_exprs.rs b/schemars_derive/src/schema_exprs.rs
index 0b30e2e..7721103 100644
--- a/schemars_derive/src/schema_exprs.rs
+++ b/schemars_derive/src/schema_exprs.rs
@@ -112,23 +112,24 @@ pub fn expr_for_repr(cont: &Container) -> Result<SchemaExpr, syn::Error> {
     Ok(schema_expr)
 }
 
-fn expr_for_field(field: &Field, allow_ref: bool) -> SchemaExpr {
+fn expr_for_field(field: &Field, is_internal_tagged_enum_newtype: bool) -> SchemaExpr {
     let (ty, type_def) = type_for_field_schema(field);
     let span = field.original.span();
 
-    let mut schema_expr = SchemaExpr::from(if field.attrs.validation.required {
+    let schema_expr = if field.attrs.validation.required {
         quote_spanned! {span=>
             <#ty as schemars::JsonSchema>::_schemars_private_non_optional_json_schema(#GENERATOR)
         }
-    } else if allow_ref {
+    } else if is_internal_tagged_enum_newtype {
         quote_spanned! {span=>
-            #GENERATOR.subschema_for::<#ty>()
+            schemars::_private::json_schema_for_internally_tagged_enum_newtype_variant::<#ty>(#GENERATOR)
         }
     } else {
         quote_spanned! {span=>
-            <#ty as schemars::JsonSchema>::json_schema(#GENERATOR)
+            #GENERATOR.subschema_for::<#ty>()
         }
-    });
+    };
+    let mut schema_expr = SchemaExpr::from(schema_expr);
 
     schema_expr.definitions.extend(type_def);
     field.add_mutators(&mut schema_expr.mutators);
@@ -407,7 +408,7 @@ fn expr_for_untagged_enum_variant(variant: &Variant, deny_unknown_fields: bool)
 
     match variant.style {
         Style::Unit => expr_for_unit_struct(),
-        Style::Newtype => expr_for_field(&variant.fields[0], true),
+        Style::Newtype => expr_for_field(&variant.fields[0], false),
         Style::Tuple => expr_for_tuple_struct(&variant.fields),
         Style::Struct => expr_for_struct(&variant.fields, &SerdeDefault::None, deny_unknown_fields),
     }
@@ -430,7 +431,7 @@ fn expr_for_internal_tagged_enum_variant(
 
     match variant.style {
         Style::Unit => expr_for_unit_struct(),
-        Style::Newtype => expr_for_field(&variant.fields[0], false),
+        Style::Newtype => expr_for_field(&variant.fields[0], true),
         Style::Tuple => expr_for_tuple_struct(&variant.fields),
         Style::Struct => expr_for_struct(&variant.fields, &SerdeDefault::None, deny_unknown_fields),
     }
@@ -444,14 +445,14 @@ fn expr_for_unit_struct() -> SchemaExpr {
 }
 
 fn expr_for_newtype_struct(field: &Field) -> SchemaExpr {
-    expr_for_field(field, true)
+    expr_for_field(field, false)
 }
 
 fn expr_for_tuple_struct(fields: &[Field]) -> SchemaExpr {
     let fields: Vec<_> = fields
         .iter()
         .map(|f| {
-            let field_expr = expr_for_field(f, true);
+            let field_expr = expr_for_field(f, false);
             f.with_contract_check(quote! {
                 prefix_items.push((#field_expr).to_value());
             })


Test patch:
diff --git a/schemars/tests/integration/enums_deny_unknown_fields.rs b/schemars/tests/integration/enums_deny_unknown_fields.rs
index 9eb79bf..e44b4f2 100644
--- a/schemars/tests/integration/enums_deny_unknown_fields.rs
+++ b/schemars/tests/integration/enums_deny_unknown_fields.rs
@@ -15,6 +15,10 @@ macro_rules! fn_values {
                     foo: 123,
                     bar: true,
                 }),
+                Self::StructDenyUnknownFieldsNewType(StructDenyUnknownFields {
+                    baz: 123,
+                    foobar: true,
+                }),
                 Self::Struct {
                     foo: 123,
                     bar: true,
@@ -30,12 +34,20 @@ struct Struct {
     bar: bool,
 }
 
+#[derive(JsonSchema, Deserialize, Serialize, Default)]
+#[serde(deny_unknown_fields)]
+struct StructDenyUnknownFields {
+    baz: i32,
+    foobar: bool,
+}
+
 #[derive(JsonSchema, Deserialize, Serialize)]
 #[serde(deny_unknown_fields)]
 enum External {
     Unit,
     StringMap(BTreeMap<String, String>),
     StructNewType(Struct),
+    StructDenyUnknownFieldsNewType(StructDenyUnknownFields),
     Struct { foo: i32, bar: bool },
 }
 
@@ -49,6 +61,7 @@ enum Internal {
     Unit,
     StringMap(BTreeMap<String, String>),
     StructNewType(Struct),
+    StructDenyUnknownFieldsNewType(StructDenyUnknownFields),
     Struct { foo: i32, bar: bool },
 }
 
@@ -62,6 +75,7 @@ enum Adjacent {
     Unit,
     StringMap(BTreeMap<String, String>),
     StructNewType(Struct),
+    StructDenyUnknownFieldsNewType(StructDenyUnknownFields),
     Struct { foo: i32, bar: bool },
 }
 
@@ -75,6 +89,7 @@ enum Untagged {
     Unit,
     StringMap(BTreeMap<String, String>),
     StructNewType(Struct),
+    StructDenyUnknownFieldsNewType(StructDenyUnknownFields),
     Struct { foo: i32, bar: bool },
 }
 
@@ -88,13 +103,22 @@ fn externally_tagged_enum() {
         .assert_snapshot()
         .assert_allows_ser_roundtrip(External::values())
         .assert_matches_de_roundtrip(arbitrary_values())
-        .assert_rejects_de([json!({
-            "Struct": {
-                "foo": 123,
-                "bar": true,
-                "extra": null
-            }
-        })])
+        .assert_rejects_de([
+            json!({
+                "Struct": {
+                    "foo": 123,
+                    "bar": true,
+                    "extra": null
+                }
+            }),
+            json!({
+                "StructDenyUnknownFieldsNewType": {
+                    "baz": 123,
+                    "foobar": true,
+                    "extra": null
+                }
+            }),
+        ])
         .assert_allows_de_roundtrip([json!({
             "StructNewType": {
                 "foo": 123,
@@ -110,12 +134,20 @@ fn internally_tagged_enum() {
         .assert_snapshot()
         .assert_allows_ser_roundtrip(Internal::values())
         .assert_matches_de_roundtrip(arbitrary_values())
-        .assert_rejects_de([json!({
-            "tag": "Struct",
-            "foo": 123,
-            "bar": true,
-            "extra": null
-        })])
+        .assert_rejects_de([
+            json!({
+                "tag": "Struct",
+                "foo": 123,
+                "bar": true,
+                "extra": null
+            }),
+            json!({
+                "tag": "StructDenyUnknownFieldsNewType",
+                "baz": 123,
+                "foobar": true,
+                "extra": null
+            }),
+        ])
         .assert_allows_de_roundtrip([json!({
             "tag": "StructNewType",
             "foo": 123,
@@ -130,14 +162,24 @@ fn adjacently_tagged_enum() {
         .assert_snapshot()
         .assert_allows_ser_roundtrip(Adjacent::values())
         .assert_matches_de_roundtrip(arbitrary_values())
-        .assert_rejects_de([json!({
-            "tag": "Struct",
-            "content": {
-                "foo": 123,
-                "bar": true,
-                "extra": null
-            }
-        })])
+        .assert_rejects_de([
+            json!({
+                "tag": "Struct",
+                "content": {
+                    "foo": 123,
+                    "bar": true,
+                    "extra": null
+                }
+            }),
+            json!({
+                "tag": "StructDenyUnknownFieldsNewType",
+                "content": {
+                    "baz": 123,
+                    "foobar": true,
+                    "extra": null
+                }
+            }),
+        ])
         .assert_allows_de_roundtrip([json!({
             "tag": "StructNewType",
             "content": {
@@ -154,6 +196,11 @@ fn untagged_enum() {
         .assert_snapshot()
         .assert_allows_ser_roundtrip(Untagged::values())
         .assert_matches_de_roundtrip(arbitrary_values())
+        .assert_rejects_de([json!({
+            "baz": 123,
+            "foobar": true,
+            "extra": null
+        })])
         .assert_allows_de_roundtrip([json!({
             "foo": 123,
             "bar": true,
diff --git a/schemars/tests/integration/examples.rs b/schemars/tests/integration/examples.rs
index 43b7419..427a666 100644
--- a/schemars/tests/integration/examples.rs
+++ b/schemars/tests/integration/examples.rs
@@ -1,21 +1,15 @@
 use crate::prelude::*;
 
 #[derive(Default, JsonSchema, Serialize)]
-#[schemars(example = "Struct::default", example = "null")]
+#[schemars(example = Struct::default(), example = ())]
 struct Struct {
-    #[schemars(example = "eight", example = "null")]
+    #[schemars(example = 4 + 4, example = ())]
     foo: i32,
     bar: bool,
-    #[schemars(example = "null")]
+    #[schemars(example = (), example = &"foo")]
     baz: Option<&'static str>,
 }
 
-fn eight() -> i32 {
-    8
-}
-
-fn null() {}
-
 #[test]
 fn examples() {
     test!(Struct).assert_snapshot();
diff --git a/schemars/tests/integration/snapshots/schemars/tests/integration/enums.rs~internally_tagged_enum.json b/schemars/tests/integration/snapshots/schemars/tests/integration/enums.rs~internally_tagged_enum.json
index e91dcb6..219be7a 100644
--- a/schemars/tests/integration/snapshots/schemars/tests/integration/enums.rs~internally_tagged_enum.json
+++ b/schemars/tests/integration/snapshots/schemars/tests/integration/enums.rs~internally_tagged_enum.json
@@ -44,22 +44,14 @@
     {
       "type": "object",
       "properties": {
-        "foo": {
-          "type": "integer",
-          "format": "int32"
-        },
-        "bar": {
-          "type": "boolean"
-        },
         "tag": {
           "type": "string",
           "const": "StructNewType"
         }
       },
+      "$ref": "#/$defs/Struct",
       "required": [
-        "tag",
-        "foo",
-        "bar"
+        "tag"
       ]
     },
     {
@@ -95,5 +87,23 @@
         "tag"
       ]
     }
-  ]
+  ],
+  "$defs": {
+    "Struct": {
+      "type": "object",
+      "properties": {
+        "foo": {
+          "type": "integer",
+          "format": "int32"
+        },
+        "bar": {
+          "type": "boolean"
+        }
+      },
+      "required": [
+        "foo",
+        "bar"
+      ]
+    }
+  }
 }
\ No newline at end of file
diff --git a/schemars/tests/integration/snapshots/schemars/tests/integration/enums_deny_unknown_fields.rs~adjacently_tagged_enum.json b/schemars/tests/integration/snapshots/schemars/tests/integration/enums_deny_unknown_fields.rs~adjacently_tagged_enum.json
index 20f2aed..9c4b6d9 100644
--- a/schemars/tests/integration/snapshots/schemars/tests/integration/enums_deny_unknown_fields.rs~adjacently_tagged_enum.json
+++ b/schemars/tests/integration/snapshots/schemars/tests/integration/enums_deny_unknown_fields.rs~adjacently_tagged_enum.json
@@ -52,6 +52,23 @@
       ],
       "additionalProperties": false
     },
+    {
+      "type": "object",
+      "properties": {
+        "tag": {
+          "type": "string",
+          "const": "StructDenyUnknownFieldsNewType"
+        },
+        "content": {
+          "$ref": "#/$defs/StructDenyUnknownFields"
+        }
+      },
+      "required": [
+        "tag",
+        "content"
+      ],
+      "additionalProperties": false
+    },
     {
       "type": "object",
       "properties": {
@@ -100,6 +117,23 @@
         "foo",
         "bar"
       ]
+    },
+    "StructDenyUnknownFields": {
+      "type": "object",
+      "properties": {
+        "baz": {
+          "type": "integer",
+          "format": "int32"
+        },
+        "foobar": {
+          "type": "boolean"
+        }
+      },
+      "additionalProperties": false,
+      "required": [
+        "baz",
+        "foobar"
+      ]
     }
   }
 }
\ No newline at end of file
diff --git a/schemars/tests/integration/snapshots/schemars/tests/integration/enums_deny_unknown_fields.rs~externally_tagged_enum.json b/schemars/tests/integration/snapshots/schemars/tests/integration/enums_deny_unknown_fields.rs~externally_tagged_enum.json
index 819e0e4..911428b 100644
--- a/schemars/tests/integration/snapshots/schemars/tests/integration/enums_deny_unknown_fields.rs~externally_tagged_enum.json
+++ b/schemars/tests/integration/snapshots/schemars/tests/integration/enums_deny_unknown_fields.rs~externally_tagged_enum.json
@@ -35,6 +35,18 @@
       ],
       "additionalProperties": false
     },
+    {
+      "type": "object",
+      "properties": {
+        "StructDenyUnknownFieldsNewType": {
+          "$ref": "#/$defs/StructDenyUnknownFields"
+        }
+      },
+      "required": [
+        "StructDenyUnknownFieldsNewType"
+      ],
+      "additionalProperties": false
+    },
     {
       "type": "object",
       "properties": {
@@ -78,6 +90,23 @@
         "foo",
         "bar"
       ]
+    },
+    "StructDenyUnknownFields": {
+      "type": "object",
+      "properties": {
+        "baz": {
+          "type": "integer",
+          "format": "int32"
+        },
+        "foobar": {
+          "type": "boolean"
+        }
+      },
+      "additionalProperties": false,
+      "required": [
+        "baz",
+        "foobar"
+      ]
     }
   }
 }
\ No newline at end of file
diff --git a/schemars/tests/integration/snapshots/schemars/tests/integration/enums_deny_unknown_fields.rs~internally_tagged_enum.json b/schemars/tests/integration/snapshots/schemars/tests/integration/enums_deny_unknown_fields.rs~internally_tagged_enum.json
index 2e5b8c8..ab5e53a 100644
--- a/schemars/tests/integration/snapshots/schemars/tests/integration/enums_deny_unknown_fields.rs~internally_tagged_enum.json
+++ b/schemars/tests/integration/snapshots/schemars/tests/integration/enums_deny_unknown_fields.rs~internally_tagged_enum.json
@@ -33,22 +33,36 @@
     {
       "type": "object",
       "properties": {
-        "foo": {
+        "tag": {
+          "type": "string",
+          "const": "StructNewType"
+        }
+      },
+      "$ref": "#/$defs/Struct",
+      "required": [
+        "tag"
+      ]
+    },
+    {
+      "type": "object",
+      "properties": {
+        "baz": {
           "type": "integer",
           "format": "int32"
         },
-        "bar": {
+        "foobar": {
           "type": "boolean"
         },
         "tag": {
           "type": "string",
-          "const": "StructNewType"
+          "const": "StructDenyUnknownFieldsNewType"
         }
       },
+      "additionalProperties": false,
       "required": [
         "tag",
-        "foo",
-        "bar"
+        "baz",
+        "foobar"
       ]
     },
     {
@@ -73,5 +87,23 @@
         "bar"
       ]
     }
-  ]
+  ],
+  "$defs": {
+    "Struct": {
+      "type": "object",
+      "properties": {
+        "foo": {
+          "type": "integer",
+          "format": "int32"
+        },
+        "bar": {
+          "type": "boolean"
+        }
+      },
+      "required": [
+        "foo",
+        "bar"
+      ]
+    }
+  }
 }
\ No newline at end of file
diff --git a/schemars/tests/integration/snapshots/schemars/tests/integration/enums_deny_unknown_fields.rs~untagged_enum.json b/schemars/tests/integration/snapshots/schemars/tests/integration/enums_deny_unknown_fields.rs~untagged_enum.json
index 9b58136..1e6a367 100644
--- a/schemars/tests/integration/snapshots/schemars/tests/integration/enums_deny_unknown_fields.rs~untagged_enum.json
+++ b/schemars/tests/integration/snapshots/schemars/tests/integration/enums_deny_unknown_fields.rs~untagged_enum.json
@@ -14,6 +14,9 @@
     {
       "$ref": "#/$defs/Struct"
     },
+    {
+      "$ref": "#/$defs/StructDenyUnknownFields"
+    },
     {
       "type": "object",
       "properties": {
@@ -48,6 +51,23 @@
         "foo",
         "bar"
       ]
+    },
+    "StructDenyUnknownFields": {
+      "type": "object",
+      "properties": {
+        "baz": {
+          "type": "integer",
+          "format": "int32"
+        },
+        "foobar": {
+          "type": "boolean"
+        }
+      },
+      "additionalProperties": false,
+      "required": [
+        "baz",
+        "foobar"
+      ]
     }
   }
 }
\ No newline at end of file
diff --git a/schemars/tests/integration/snapshots/schemars/tests/integration/examples.rs~examples.de.json b/schemars/tests/integration/snapshots/schemars/tests/integration/examples.rs~examples.de.json
index b891aa0..da04166 100644
--- a/schemars/tests/integration/snapshots/schemars/tests/integration/examples.rs~examples.de.json
+++ b/schemars/tests/integration/snapshots/schemars/tests/integration/examples.rs~examples.de.json
@@ -20,7 +20,8 @@
         "null"
       ],
       "examples": [
-        null
+        null,
+        "foo"
       ]
     }
   },
diff --git a/schemars/tests/integration/snapshots/schemars/tests/integration/examples.rs~examples.ser.json b/schemars/tests/integration/snapshots/schemars/tests/integration/examples.rs~examples.ser.json
index 97465db..7de3378 100644
--- a/schemars/tests/integration/snapshots/schemars/tests/integration/examples.rs~examples.ser.json
+++ b/schemars/tests/integration/snapshots/schemars/tests/integration/examples.rs~examples.ser.json
@@ -20,7 +20,8 @@
         "null"
       ],
       "examples": [
-        null
+        null,
+        "foo"
       ]
     }
   },
diff --git a/schemars/tests/integration/test_helper.rs b/schemars/tests/integration/test_helper.rs
index 1d99b23..64a3394 100644
--- a/schemars/tests/integration/test_helper.rs
+++ b/schemars/tests/integration/test_helper.rs
@@ -1,4 +1,4 @@
-use jsonschema::JSONSchema as CompiledSchema;
+use jsonschema::Validator;
 use schemars::{
     generate::{Contract, SchemaSettings},
     JsonSchema, Schema,
@@ -17,8 +17,8 @@ pub struct TestHelper<T: JsonSchema> {
     phantom: PhantomData<T>,
     de_schema: Schema,
     ser_schema: Schema,
-    de_schema_compiled: OnceCell<CompiledSchema>,
-    ser_schema_compiled: OnceCell<CompiledSchema>,
+    de_schema_validator: OnceCell<Validator>,
+    ser_schema_validator: OnceCell<Validator>,
     validator: fn(&T) -> bool,
 }
 
@@ -33,8 +33,8 @@ impl<T: JsonSchema> TestHelper<T> {
             phantom: PhantomData,
             de_schema,
             ser_schema,
-            de_schema_compiled: OnceCell::new(),
-            ser_schema_compiled: OnceCell::new(),
+            de_schema_validator: OnceCell::new(),
+            ser_schema_validator: OnceCell::new(),
             validator: |_| true,
         }
     }
@@ -52,8 +52,8 @@ impl<T: JsonSchema> TestHelper<T> {
             phantom: PhantomData,
             de_schema,
             ser_schema,
-            de_schema_compiled: OnceCell::new(),
-            ser_schema_compiled: OnceCell::new(),
+            de_schema_validator: OnceCell::new(),
+            ser_schema_validator: OnceCell::new(),
             validator: |_| true,
         }
     }
@@ -137,22 +137,22 @@ impl<T: JsonSchema> TestHelper<T> {
     }
 
     fn de_schema_validate(&self, instance: &Value) -> bool {
-        self.de_schema_compiled
-            .get_or_init(|| compile_schema(&self.de_schema))
+        self.de_schema_validator
+            .get_or_init(|| build_validator(&self.de_schema))
             .is_valid(instance)
     }
 
     fn ser_schema_validate(&self, instance: &Value) -> bool {
-        self.ser_schema_compiled
-            .get_or_init(|| compile_schema(&self.ser_schema))
+        self.ser_schema_validator
+            .get_or_init(|| build_validator(&self.ser_schema))
             .is_valid(instance)
     }
 }
 
-fn compile_schema(schema: &Schema) -> CompiledSchema {
-    CompiledSchema::options()
+fn build_validator(schema: &Schema) -> Validator {
+    jsonschema::options()
         .should_validate_formats(true)
-        .compile(schema.as_value())
+        .build(schema.as_value())
         .expect("valid schema")
 }
 
diff --git a/schemars/tests/ui/example_fn.rs b/schemars/tests/ui/example_fn.rs
new file mode 100644
index 0000000..c4921c5
--- /dev/null
+++ b/schemars/tests/ui/example_fn.rs
@@ -0,0 +1,9 @@
+use schemars::JsonSchema;
+
+#[derive(JsonSchema)]
+#[schemars(example = "my_fn")]
+pub struct Struct;
+
+fn my_fn() {}
+
+fn main() {}
diff --git a/schemars/tests/ui/example_fn.stderr b/schemars/tests/ui/example_fn.stderr
new file mode 100644
index 0000000..92a34f3
--- /dev/null
+++ b/schemars/tests/ui/example_fn.stderr
@@ -0,0 +1,7 @@
+error: `example` value must be an expression, and string literals that may be interpreted as function paths are currently disallowed to avoid migration errors (this restriction may be relaxed in a future version of schemars).
+       If you want to use the result of a function, use `#[schemars(example = my_fn())]`.
+       Or to use the string literal value, use `#[schemars(example = &"my_fn")]`.
+ --> tests/ui/example_fn.rs:4:22
+  |
+4 | #[schemars(example = "my_fn")]
+  |                      ^^^^^^^
diff --git a/schemars/tests/ui/transform_str.stderr b/schemars/tests/ui/transform_str.stderr
index 6ee3698..50cf7b9 100644
--- a/schemars/tests/ui/transform_str.stderr
+++ b/schemars/tests/ui/transform_str.stderr
@@ -1,5 +1,5 @@
 error: Expected a `fn(&mut Schema)` or other value implementing `schemars::transform::Transform`, found `&str`.
-       Did you mean `[schemars(transform = x)]`?
+       Did you mean `#[schemars(transform = x)]`?
  --> tests/ui/transform_str.rs:4:24
   |
 4 | #[schemars(transform = "x")]