Skip to content
Merged

Next #189

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
362 changes: 362 additions & 0 deletions AGENTS.md

Large diffs are not rendered by default.

203 changes: 203 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,208 @@
# Changelog

## Next

This is a major release that has been in development for over a year. The macro layer has been
rewritten from the ground up, utilizing new patterns to improve maintainability and extendability,
while the schema types have been updated to be more flexible and composable.

#### 💥 Breaking

##### Config

- Structs will no longer default to `camelCase` field name casing.
- Enums will no longer default to `kebab-case` variant name casing.
- Removed `#[config(serde(...))]` on containers. Use `#[serde(...)]` directly instead.
- Removed `#[variant(value)]` on enum variants. Use `#[variant(rename)]` instead, which does the
same thing.
- Removed the `tracing` Cargo feature, which wrapped generated code in `#[tracing::instrument]`. The
loader is still instrumented; only the derive output no longer is.
- Replaced `reqwest` with `ureq` for the `url` feature. `ureq` is blocking but builds no runtime of
its own, so loading a URL from inside an async runtime now blocks the thread instead of panicking.
`ConfigError::ReadUrlFailed` carries a `ureq::Error` instead of a `reqwest::Error`.
- Changed a non-2xx response to a URL source into a `ConfigError::ReadUrlFailed`. It previously
passed the response body to the parser, so a 404 page surfaced as a parse error.
- Changed doc comments to render as a single flowing paragraph instead of one line per source line.
Markdown list items are still kept on their own line.
- Changed `#[setting(env_prefix)]` keys to no longer appear as `@env` annotations in generated
templates. A derived key depends on the prefix in effect at runtime, which a parent's
`#[setting(nested, env_prefix)]` can override, so it isn't known when the schema is built.
Explicit `#[setting(env)]` keys are unaffected.

##### Schema

- Updated `schemars` to v1, which replaces its typed schema model with plain JSON. This reshapes
`JsonSchemaOptions`:
- `visitors: Vec<Box<dyn GenVisitor>>` is now `transforms: Vec<Box<dyn GenTransform>>`.
- `option_nullable` and `option_add_null_type` were removed. Schemars no longer has them, and this
renderer never read them.
- `definitions_path` keeps its meaning as the `$ref` prefix, and is no longer inherited from
schemars, whose field of that name is now a JSON pointer.
- `JsonSchemaRenderer` implements `SchemaRenderer<schemars::Schema>` rather than
`SchemaRenderer<schemars::schema::Schema>`.
- Changed a `$ref` with no sibling keys to render on its own, instead of inside a single-element
`allOf`. The two are equivalent, and a `$ref` that does have siblings is still wrapped.
- Changed `SchemaType::Reference` from a newtype into a struct variant. Serde is unable to
internally tag a newtype whose value isn't a map, so any schema containing a reference (which is
how cycles are represented) previously failed to serialize.

```rust
// Before
SchemaType::Reference("Name".into())

// After
SchemaType::Reference { name: "Name".into(), partial: false }
// Or
Schema::reference("Name")
```

- Changed `StructType.fields` from a `BTreeMap` to an `IndexMap`, so the schema preserves the order
fields were declared in. Generators still render alphabetically, via
`StructType::sorted_fields()`.
- Changed `ArrayType.contains` from `Option<bool>` to `Option<Box<Schema>>`. It's now the JSON
Schema `contains` subschema, and applies alongside `items_type` instead of reinterpreting it.
- Changed `SchemaType::add_field` and `SchemaType::set_default` to return a `bool` indicating
whether the operation applied. They previously did nothing when the type couldn't hold the value.
- Removed `Deref`/`DerefMut` for `Schema` and `SchemaBuilder`. `Schema` now forwards the
`SchemaType` accessors directly (`get_default`, `set_default`, `add_field`, `is_null`,
`is_nullable`, `is_reference`, `is_struct`); reach for `schema.ty` for anything else.
- Removed `ArrayType.max_contains` and `ArrayType.min_contains`, which no renderer could emit.
- Removed `FloatType.name`, which was never rendered and duplicated `Schema.name`.
- Removed `LiteralType.format`, which was never rendered.
- Updated `Duration` and `SystemTime` to model as structs instead of strings. Serde encodes them as
`{ secs, nanos }` and `{ secs_since_epoch, nanos_since_epoch }`, so the previous string schemas
rejected valid documents.
- Updated `Schema.nullable` to serialize when true, instead of always being skipped.

#### 🚀 Updates

##### Config

- Added support for unnamed tuple and newtype structs. Unnamed fields within the struct support
`#[setting]`.
- Added support for `#[setting(nested = NestedConfig)]` on struct fields and enum variants, where
the nested config name can be explicitly defined if we fail to detect it. This is useful for
extremely complex/composed types.
- Added support for `#[setting(transform)]` on enum variants.
- Added support for env prefixes at the field level when the field is also nested. This will
override the env prefix defined on the nested container:
`#[setting(nested, env_prefix = "OVERRIDE_")]`.
- Updated `#[setting(extend)]` settings to support `Option` wrapped values.
- Updated the methods of `PartialConfig` to all have a default implementation. This helps to greatly
reduce the amount of macro generated code.
- Added generics support to `#[derive(Config)]` and `#[derive(ConfigEnum)]`, which previously only
worked on `#[derive(Schematic)]`. Type arguments are carried into the partial type and every
generated implementation, and a generic type's schema name appends them, so `Wrapper<String>` and
`Wrapper<usize>` no longer collide.
- Bounds are not inferred. A generic `Config` needs
`Clone + Default + DeserializeOwned + Schematic + Serialize` on its type parameters, since
`PartialConfig` requires them.
- Added support for `#[setting(parse_env)]` alongside a container `env_prefix`. It previously
required an explicit `#[setting(env)]` and panicked at derive time otherwise.
- Added support for `#[deprecated(since = "...", note = "...")]`, whose note is now used as the
deprecation message. Only `#[deprecated]` and `#[deprecated = "..."]` were recognized before.
- Added support for struct literals and paths that name a value in `#[setting(default)]`, so an enum
variant or constant can be written directly: `#[setting(default = LevelFilter::Debug)]`. A path
whose last segment starts uppercase is a value, and one starting lowercase is still a handler
function to call. ([#173](https://github.com/moonrepo/schematic/issues/173))
- Added a `validate::uuid` function, which validates a string is a UUID in the canonical hyphenated
form. It checks the shape only, so the nil UUID and unknown versions are both accepted, and it
needs no Cargo feature. ([#156](https://github.com/moonrepo/schematic/issues/156))
- Improved the parse, handling, and validation of container and field attributes.
- Updated `#[config(before_parse)]` on `ConfigEnum` to accept every case that `rename_all` does,
instead of only `lowercase` and `UPPERCASE`. Incoming values are normalized before being matched,
so `very_high`, `VeryHigh`, and `VERY HIGH` can all resolve to the same variant.

##### Serde

- Added support for explicit deserialize and serialize renaming on containers and fields:
`#[serde(rename(deserialize = "de_name", serialize = "ser_name"))]`.
- Added support for `skip_serializing_if` on fields.
- Updated `alias` to support multiple aliases: `#[serde(alias = "alias1", alias = "alias2")]`

##### Schema

- Added `Schema::reference()`, to go with the constructors for the other types.
- Added `EnumType::get_default()` and `EnumType::set_default()`, which resolve `default_index`
against whichever list it indexes.
- Added `StructType::sorted_fields()`, which renderers use to keep generated output alphabetical.
- Added `schema_name_of()`, which resolves the schema name of a type, for composing the name of a
generic type from its arguments. Types without a name of their own fall back to their Rust type
name, so instantiations over primitives still resolve distinctly.
- Added `Schematic` implementations for `[T]`, `VecDeque`, `LinkedList`, `BinaryHeap`, `IpAddr`,
`SocketAddr`, `SocketAddrV4`, `SocketAddrV6`, `Range`, and `RangeInclusive`.
- Added `Schematic` implementations for the `NonZero` integers. The unsigned ones carry a `min` of
1; signed ones can't express "not zero" as a bound, so they stay unconstrained.
- Added a `Schematic` implementation for `ron::Value`, behind the existing `serde_ron` feature,
which until now enabled a dependency but no implementations.
- Updated `Box`, `Rc`, and `Arc` to accept unsized inner types, so `Box<str>`, `Arc<str>` and
`Box<[T]>` can now build a schema. This is required for partial configs, which keep the wrapper
when the inner type is unsized.
- Updated `SchemaBuilder::generate`, `build_root`, `infer`, `infer_as_nested`, and
`infer_with_default` to accept unsized types.
- Updated `SchemaGenerator::add` to panic when two types claim the same `schema_name`. Schemas are
keyed by name, so one would previously be dropped and every reference to it resolved to the other.

#### 🐞 Fixes

##### Config

- Fixed validators being unable to accept a borrowed form of the setting. A `String` setting now
reaches a `&str` validator and a `Vec<T>` a `&[T]` one, which built-ins like
`validate::extends_string` and `validate::extends_list` rely on.
- Fixed nested configs in a map keyed by anything other than `String` failing to compile.
- Fixed nested configs wrapped in an `Option` inside a collection, such as `Vec<Option<Config>>`,
not being validated.
- Fixed adjacently tagged unit variants declaring a `content` field in their schema. Serde emits
only the tag for a unit variant.
- Fixed the schema of externally and internally tagged unit variants. An externally tagged unit is a
bare string, and an internally tagged one is an object holding just the tag.
- Fixed the partial schema of a tagged enum marking the tag itself as nullable and optional, which
claimed that a variant with a missing or null tag was valid.
- Fixed a block doc comment (`/** ... */`) keeping its leading `*` continuation markers, which
rendered them as stray markdown list items.
- Fixed the `config` feature failing to compile without `env`.
- Fixed the `url` feature being unable to request any HTTPS URL. `reqwest` was declared without
a TLS backend, so it only worked in this repository, where a dev-dependency happened to enable
one. `ureq` enables rustls by default.
- Fixed the `type_regex` and `type_semver` features failing to compile alongside `config` without
`schema`. Their `Schematic` implementations are now gated, so the setting types themselves no
longer require the schema layer.

##### Schema

- Fixed the `serde` feature not enabling `indexmap/serde`, which made `schematic_types` fail to
compile on its own.
- Fixed `SchemaType::set_default` doing nothing for unions and enums, which silently dropped the
default of every `Option` wrapped setting from the generated schema.
- Fixed `EnumType` resolving `default_index` against `values` instead of `variants`. The two differ
in length when a variant carries no literal value, so the default could be lost or point
elsewhere.
- Fixed `EnumType::from_schemas` panicking without an explanation when a variant schema had no name.
- Fixed `Schema::partialize` not recursing into tuples, and not marking references, which made a
recursive nested config render a `$ref` to a type that was never generated.
- Fixed `Schema::nullify` not detecting an already nullable type, which double wrapped named unions
and left the method non-idempotent.
- Fixed `Schema.nullable` never being set when inferring an `Option`, and never surviving a
serialization round trip.
- Fixed `Schema::get_nonnull_schema` not resolving nested unions, so a union of nothing but nulls
was returned as if it were non-null.
- Fixed `LiteralValue` comparing unequal to itself when holding a `NaN` float.

#### ⚙️ Internal

- Moved the derive implementation into a new `schematic_core` crate. `schematic_macros` is now a
thin proc-macro shell over it, and every derive shares one code path.
- Changed the signatures of `internal::ValidateManager`. `check` and `check_variant` take the value
by move and an opaque callable instead of a boxed `Validator`, and `nested_list`/`nested_map` take
items as `Option`s. These are `#[doc(hidden)]` and only called by generated code.
- Updated `syn` to v3.
- Updated `darling` to v0.24.
- Updated `pkl` to v0.8.
- Updated `garde` (validation) to v0.23.
- Updated Rust to v1.98.
- Updated dependencies.

## 0.19.7

#### ⚙️ Internal
Expand Down
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@AGENTS.md
Loading
Loading