All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[2.0.1] - 2026-08-11
No behavior changes. Documentation and test coverage only.
Added
- Property-based test suite (
stream_data) covering the two assumptions 2.0 rests on: the empty-path deviation from:uri_string.normalize/1is limited to the root path and nothing else, andnormalize/2is idempotent (including underrestore_old_query_behaviorand the path-shaping options). - A short "Upgrading from 1.x in five minutes" table in the README, next to the full migration guide.
Fixed
- Removed a docs reference to a
@doc falsefunction that madeex_docwarn on every build.
[2.0.0] - 2026-08-11
Breaking release. Migration notes with before/after strings: Migrating to 2.0.
Changed
- D6 — RFC 3986 normalization default-on.
form_url/1finishes with:uri_string.normalize/1(dot-segment removal, host case folding, percent-encoding normalization, scheme-aware default-port omission). The opinionated scheme-blind@masked_portslist andform_port/1are removed. Ports such as8080and non-default ports for the scheme (e.g.http://…:443) are no longer stripped. - D1 — Query order and duplicate keys preserved. Query rebuild no longer
round-trips through a map (
URI.decode_query/URI.encode_query), which sorted keys and collapsed duplicates. Pairs keep input order and multi-value keys. Opt-inrestore_old_query_behavior: truerestores the 1.x sort+dedupe path. - D3 — Trailing slash looks at the final path segment only.
add_trailing_slashskips only when the last segment contains.(file-like), not when a.appears anywhere in the path. Versioned directories like/v1.2/docsnow get a trailing slash. - D4 —
force_root_pathwipes query and fragment. Setting path to/also clears:queryand:fragment. New additive optionadd_root_path: truesets path to/only when the path is nil or empty (non-empty paths stay). - D5 — Parse via
URI.new/1. Replaces the recursivesafe_parse/has_valid_host?loop.URI.new/1returns{:ok, uri} | {:error, part}and needs Elixir 1.13+. Scheme-less//…inference remains a single explicit branch, not mutual recursion. - D8 —
downcase_hostoption removed. Host case folding is always on via D6 (:uri_string.normalize/1). Passing%{downcase_host: true | false}is ignored; there is no way to keep an uppercase host in the normalized output.
Toolchain
- Minimum Elixir:
~> 1.13(was~> 1.11). - CI matrix: Elixir 1.20/OTP 29, 1.19/OTP 28, 1.16/OTP 26, 1.13/OTP 24.
[1.9.0] - 2026-08-11
Added
- Package docs via
ex_doc(docs: [main: "Norma", extras: ["CHANGELOG.md"]]) dialyxirandex_docas development dependencies
Fixed
- Emit query before fragment in
form_url(RFC 3986 order). Previously the internal URL assembly concatenated the fragment before the query, producing invalid URLs like/Path#Frag?Q=Foo. The fragment must come last. (#11) - Avoid a duplicate trailing slash when the URL path already ends with
/andadd_trailing_slashis set. (#11)