You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Release v0.1.0: add Exdantic.Settings, boolean schema support, and modular guides
This update introduces the Exdantic.Settings module, providing a robust
environment-variable-based configuration loader with schema validation.
It supports field-driven lookups, custom prefixes, and nested delimiters.
Settings Loader:
- Added load/2, load!/2, and from_system_env/2 for configuration.
- Support for field-level env overrides and case-insensitive normalization.
- Implemented prefix-based matching to resolve underscore naming ambiguity.
- Added deep-merge logic for exploded nested env values over JSON maps.
- Introduced specific error codes for casting, JSON, and key conflicts.
- Added ignore_empty and allow_atoms options for flexible env parsing.
JSON Schema and Resolvers:
- Added support for boolean schemas (true/false) in JsonSchema.Resolver.
- Implemented allOf wrapping when merging metadata into boolean schemas.
- Resolved schema metadata serialization issues using term_to_binary.
- Updated reference resolver to support both definitions and $defs.
- Refactored visitor tracking from MapSet to maps for better performance.
Documentation and Examples:
- Restructured documentation into nine modular guides.
- Added a comprehensive settings loader example and automation script.
- Documented helper functions, wrapper factories, and custom type rules.
- Added branding assets (SVG logo) and refreshed the README.
Refactoring and Performance:
- Refactored RootSchema to use private functions for root type storage.
- Optimized list emptiness checks across the codebase.
- Extracted constraint keys to module attributes in the Runtime module.
- Removed legacy documentation and broken example files.
- Resolved Dialyzer issues and removed the global ignore file.
Testing:
- Added property-based tests for settings precedence and decoding.
- Expanded unit tests for boolean schema resolution and nested paths.
- Validated underscore delimiter strategies for nested settings paths.
-**Boolean schema support** in `JsonSchema.Resolver` — `true`/`false` can now be used as definitions and are resolved correctly
25
+
- When merging metadata into a boolean schema, the resolver wraps elements in an `allOf` structure to preserve schema validity
26
+
- Support for both `definitions` and `$defs` in the reference resolver
27
+
-**Schema metadata serialization** — fields are now serialized via `:erlang.term_to_binary` during compilation and decoded at runtime, fixing compile-time escaping failures for Regex constraints on newer Elixir/OTP versions
- Strict mode deprecation analysis and JSV compatibility analysis
36
+
- Production error handling guide
37
+
-**Project branding** — SVG logo asset (`assets/exdantic.svg`)
38
+
-**Examples**:
39
+
-`examples/settings_loader.exs` — comprehensive settings loader example
40
+
-`examples/run_all.sh` — bash script to automate execution of all examples
41
+
-**Tests**:
42
+
-`settings_test.exs` — unit tests for the settings loader
43
+
-`settings_property_test.exs` — property-based tests covering precedence, env decoding, nested merge, union behavior, and atom safety
44
+
- Boolean schema resolution tests in `resolver_test.exs`
45
+
- Underscore delimiter tests for nested settings paths
17
46
18
47
### Changed
19
-
- Settings decoding and merge behavior now documented and validated by property tests:
20
-
- precedence: `input > env > defaults`
21
-
- structured types are JSON-only
22
-
- exploded nested values deep-merge over top-level JSON values
23
-
- conservative union env decoding (no union-level scalar coercion probing)
24
-
- no exploded addressing into arrays in v1
48
+
-**Settings behavior** (documented and validated by property tests):
49
+
- Precedence: `input > env > defaults`
50
+
- Structured types (arrays, maps, nested schemas) are JSON-only via env
51
+
- Exploded nested values deep-merge over top-level JSON values
52
+
- Conservative union env decoding (no union-level scalar coercion probing)
53
+
- No exploded addressing into arrays in v1
54
+
-**RootSchema** refactored to use private functions (`__root_type__/0`) instead of module attributes for storing root types, improving consistency in code generation
55
+
-**JsonSchema.Resolver** updated to use maps instead of `MapSet` for tracking visited nodes, improving performance and adding stricter definition checks
56
+
-**Runtime module** — constraint keys extracted to a module attribute (`@constraint_keys`) replacing runtime `MapSet` creation; refined type specifications
57
+
-**EnhancedValidator** — refined type specifications
58
+
- Optimized list emptiness checks across the codebase by replacing `length/1 > 0` with direct empty list comparisons (`!= []`)
59
+
- Updated ExDoc configuration with grouped extras for HexDocs navigation
60
+
- Updated dependencies in `mix.lock` (including `ex_doc` and `dialyxir`)
61
+
- Rewrote `README.md` for better clarity and faster onboarding
62
+
63
+
### Removed
64
+
-`.dialyzer_ignore.exs` — removed after resolving underlying Dialyzer analysis issues
Copy file name to clipboardExpand all lines: guides/06_json_schema_and_resolvers.md
+26-1Lines changed: 26 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -49,7 +49,7 @@ Constraint mapping examples:
49
49
50
50
`Exdantic.JsonSchema.ReferenceStore` tracks references and emitted definitions during generation.
51
51
52
-
Generated schemas may contain `definitions`+`$ref` entries for nested schema modules.
52
+
Generated schemas may contain `definitions`or`$defs` plus `$ref` entries for nested schema modules. The resolver supports both `definitions` and `$defs` keys and merges them when both are present.
JSON Schema allows `true` and `false` as valid schemas (accept-all and reject-all). The resolver handles these correctly:
84
+
85
+
- Boolean values in `definitions` or `$defs` are resolved as-is when referenced via `$ref`.
86
+
- When metadata (title, description) needs to be merged into a boolean schema, the resolver wraps the result in an `allOf` structure to preserve schema validity:
A field can declare an absolute env key that bypasses prefix derivation:
144
+
145
+
```elixir
146
+
schema do
147
+
field :db_url, :string, required:true, extra: %{"env"=>"DATABASE_URL"}
148
+
end
149
+
```
150
+
151
+
When both the override key and the derived key exist in the environment, the override wins. The prefix is **not** applied to the override key.
152
+
141
153
## Env Decoding Behavior
142
154
143
-
- Scalar env values are decoded by expected type (`integer`, `float`, `boolean`, etc.)
144
-
- Structured types (`array`, maps, objects, refs) use JSON decoding for top-level values
145
-
- Union decoding is conservative for structured union members
146
-
- Field override via `extra: %{"env" => "CUSTOM_KEY"}` is supported
147
-
- Nested exploded env keys are supported for nested maps/objects (arrays are intentionally limited)
155
+
Scalar types are decoded from their string env representation:
156
+
157
+
-`:string` — passed through as-is
158
+
-`:integer` — parsed with `Integer.parse/1`; must consume entire string
159
+
-`:float` — parsed with `Float.parse/1`; must consume entire string
160
+
-`:boolean` — accepts `"true"` / `"false"` (case-insensitive); when `bool_numeric: true` (default), also accepts `"1"` / `"0"`
161
+
-`:atom` — disabled by default; set `allow_atoms: :existing` to allow `String.to_existing_atom/1`
162
+
-`:any` — passed through as-is
163
+
164
+
Structured types (`{:array, _}`, `{:map, _, _}`, `{:object, _}`, schema module refs) must be provided as JSON strings:
165
+
166
+
```elixir
167
+
# env: %{"TAGS" => "[1,2,3]"}
168
+
# decodes to [1, 2, 3]
169
+
```
170
+
171
+
Invalid JSON returns an `:env_json` error. Invalid scalar parsing returns an `:env_cast` error.
172
+
173
+
Union decoding is conservative:
174
+
175
+
- If the union contains structured members and the value starts with `{` or `[`, JSON decoding is attempted.
176
+
- Otherwise the raw string is passed to the validator to resolve the union.
177
+
178
+
## Nested Exploded Env Keys
179
+
180
+
For nested schemas, the loader supports exploded env keys where the delimiter separates parent and child field names:
181
+
182
+
```elixir
183
+
# Schema: NestedSettings with a `database` field of type DatabaseSchema
184
+
# DatabaseSchema has `host` and `pool_size` fields
185
+
186
+
env = %{
187
+
"APP_DATABASE__HOST"=>"localhost",
188
+
"APP_DATABASE__POOL_SIZE"=>"10"
189
+
}
190
+
191
+
{:ok, settings} =Settings.load(NestedSettings,
192
+
env: env,
193
+
env_prefix:"APP_",
194
+
env_nested_delimiter:"__"
195
+
)
196
+
# settings.database.host == "localhost"
197
+
# settings.database.pool_size == 10
198
+
```
199
+
200
+
When both a top-level JSON value and exploded keys exist for the same field, the exploded values are deep-merged over the JSON-decoded map, with exploded keys taking precedence:
201
+
202
+
```elixir
203
+
env = %{
204
+
"APP_DATABASE"=>~s({"host":"a","pool_size":5}),
205
+
"APP_DATABASE__POOL_SIZE"=>"10"
206
+
}
207
+
# Result: host == "a", pool_size == 10
208
+
```
209
+
210
+
### Prefix-Based Matching for Underscore Fields
211
+
212
+
When using `"_"` as the nested delimiter, field names containing underscores (e.g., `pool_size`) create ambiguity. The loader resolves this with a prefix-based matching strategy that sorts fields by name length (longest first), ensuring `POOL_SIZE` matches the `pool_size` field before `POOL` could match a hypothetical `pool` field.
213
+
214
+
### Limitations
215
+
216
+
Exploded addressing into arrays is not supported in v1. For example, `APP_ITEMS__0` will not set the first element of an `items` array field. Arrays must be provided as JSON strings.
2. Field candidate key lookup (override key first, then derived key)
155
224
3. Decode + exploded nested decode merge
156
225
4. Deep merge of env values with `input` (`input` wins)
157
226
5. Key normalization by schema field definitions
158
227
6. Final validation through `Exdantic.StructValidator`
159
228
229
+
Case-insensitive mode (default) uppercases all env keys and detects collisions. If two env keys normalize to the same uppercase key (e.g., `app_port` and `APP_PORT`), an `:env_key_conflict` error is returned.
0 commit comments