From f3444ab802af15063255bec373af19d5936368c1 Mon Sep 17 00:00:00 2001 From: Andreas Hasselberg Date: Wed, 17 Jun 2026 19:56:49 +0200 Subject: [PATCH 1/3] Surface Spectral's features so agents use them AI agents building APIs with PhoenixSpectral underuse Spectral's capabilities because the library's docs never enumerate them, link only to GitHub rather than hexdocs, and the example demonstrates only a subset. Agents act on what's in front of them, so the fix is discoverability, not code. - README: add a "Going further with Spectral" feature-reference table (string constraints, field aliases, only, struct defaults, enums, codecs, type docs) with hexdocs links and a worked type_parameters example; re-point all Spectral links from GitHub to hexdocs. - Add AGENTS.md: an agent-facing "I'm tempted to... -> use this instead" map, wired into ExDoc extras and the hex :files so it ships; tells downstream maintainers to copy it into their own AGENTS.md. - Example: demonstrate declarative validation via type_parameters string constraints on a named type, with a test proving a too-short name returns 400. - Moduledocs: add "Powered by Spectral" callouts with auto-linked Spectral.* refs on the pages agents land on first. --- AGENTS.md | 38 ++++++++++++++++++++++++++ README.md | 43 +++++++++++++++++++++++++++--- example/lib/example/types.ex | 9 ++++++- example/test/example_test.exs | 9 +++++++ lib/phoenix_spectral.ex | 5 ++++ lib/phoenix_spectral/controller.ex | 9 +++++++ mix.exs | 8 ++++-- 7 files changed, 114 insertions(+), 7 deletions(-) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..3f54188 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,38 @@ +# AGENTS.md + +Guidance for AI agents building or modifying APIs with **PhoenixSpectral**. (Human contributors: see [CLAUDE.md](CLAUDE.md) for repo workflow.) + +## The one thing to internalize + +PhoenixSpectral is a **thin Phoenix adapter over [Spectral](https://hexdocs.pm/spectral)**. It adds almost no validation or schema features of its own — it reads your controller `@spec`s and delegates decoding, validation, encoding, and JSON-schema generation to Spectral. + +**Consequence:** if you only read PhoenixSpectral's docs, you will write more controller code than you need and miss validation that Spectral does declaratively. Before hand-rolling anything, read the [Spectral docs](https://hexdocs.pm/spectral) — the request/response shaping power lives there, configured on your *types* with the `spectral/1` macro. + +## Reach for a Spectral type feature before writing controller code + +When you're about to do any of these in an action, stop — Spectral does it on the type instead: + +| You're tempted to… | Use instead (on the type) | Spectral docs | +|---|---|---| +| Check a string's length / format in the controller | `spectral type_parameters: %{min_length:, max_length:, pattern:, format:}` on a `String.t()` type | [String constraints](https://hexdocs.pm/spectral/readme.html#string-and-binary-constraints) | +| Read `conn.body_params` / `conn.query_params` and validate by hand | the typed `body` / `query_params` arguments — already decoded and validated against the `@spec` | README "Step 2" | +| Map `camelCase` JSON to `snake_case` fields manually | `spectral field_aliases: %{first_name: "firstName"}` | [Field Aliases](https://hexdocs.pm/spectral/readme.html#field-aliases) | +| Strip secret fields (`password_hash`) before responding | `spectral only: [:id, :name, ...]` | [`only`](https://hexdocs.pm/spectral/readme.html#field-filtering-with-only) | +| Make a body field optional / give it a default | struct `defstruct` default + a nullable type | [Struct defaults](https://hexdocs.pm/spectral/readme.html#struct-defaults) | +| Parse an enum from a path/query param | an atom-union type (`:: :a \| :b`) | [Data Serialization API](https://hexdocs.pm/spectral/readme.html#data-serialization-api) | +| Format `DateTime`/`Date`/`MapSet` | the built-in codecs (automatic) | [Built-in Codecs](https://hexdocs.pm/spectral/readme.html#built-in-codecs) | +| Encode/decode a domain type (prefixed IDs, money) | a custom codec via `use Spectral.Codec` | [Custom Codecs](https://hexdocs.pm/spectral/readme.html#custom-codecs) | + +Anything you declare on the type also flows automatically into the generated OpenAPI 3.1 spec — you do not write schema separately. + +## Conventions specific to PhoenixSpectral + +- Actions take `(conn, path_args, query_params, headers, body)` and return `{status, headers, body}` (or a `Plug.Conn` for streaming/raw responses). +- Use `conn` only for out-of-band context (`conn.assigns`, `conn.remote_ip`). Do **not** read `conn.path_params`, `conn.query_params`, `conn.req_headers`, or `conn.body_params`. +- Response bodies must be Spectral-typed structs, not plain maps. +- Union return types (e.g. `{200, %{}, User.t()} | {404, %{}, Error.t()}`) produce multiple OpenAPI responses. +- See the runnable [`example/`](example/) app for working uses of `only`, a custom codec, optional fields, examples, and `type_parameters` string constraints. + +## If you maintain a downstream project + +A dependency's `AGENTS.md` is **not** auto-read by an agent working in your repo. If you want agents in *your* project to use Spectral's full feature set, copy the table above (or a link to it) into your own project's `AGENTS.md` / `CLAUDE.md`. diff --git a/README.md b/README.md index 199d631..5836147 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # PhoenixSpectral -PhoenixSpectral integrates [Spectral](https://github.com/andreashasse/spectral) with Phoenix, making controller typespecs the single source of truth for OpenAPI 3.1 spec generation and request/response validation. Define your types once — PhoenixSpectral derives the API docs and enforces them at runtime. +PhoenixSpectral integrates [Spectral](https://hexdocs.pm/spectral) with Phoenix, making controller typespecs the single source of truth for OpenAPI 3.1 spec generation and request/response validation. Define your types once — PhoenixSpectral derives the API docs and enforces them at runtime. + +> **Most of the power lives in Spectral.** PhoenixSpectral is a thin Phoenix adapter; the type system that shapes and validates your requests and responses is [Spectral](https://hexdocs.pm/spectral). Features like string/length/pattern constraints, camelCase field aliases, custom codecs, and the built-in date/time codecs are configured on your *types* via Spectral, not here. Read the [Spectral docs](https://hexdocs.pm/spectral) and the [Going further with Spectral](#going-further-with-spectral) section below before assuming a capability is missing. AI agents (and the humans guiding them) should start from [AGENTS.md](AGENTS.md), which maps "I want to…" tasks to the Spectral feature that does them. ## Installation @@ -18,7 +20,7 @@ end ### Step 1: Define typed structs with Spectral -[Spectral](https://github.com/andreashasse/spectral) is an Elixir library that validates, decodes, and encodes data according to your `@type` definitions. Add `use Spectral` to a module and your types become the schema — PhoenixSpectral reads them to validate requests, decode inputs, encode responses, and generate the OpenAPI spec. +[Spectral](https://hexdocs.pm/spectral) is an Elixir library that validates, decodes, and encodes data according to your `@type` definitions. Add `use Spectral` to a module and your types become the schema — PhoenixSpectral reads them to validate requests, decode inputs, encode responses, and generate the OpenAPI spec. ```elixir defmodule MyApp.User do @@ -261,7 +263,7 @@ make integration-test # runs the ExUnit suite in-process ## Configuration -PhoenixSpectral delegates encoding, decoding, and schema generation to [Spectral](https://github.com/andreashasse/spectral) / [spectra](https://github.com/andreashasse/spectra). Configure them directly in `config/config.exs` (or `config/runtime.exs`). +PhoenixSpectral delegates encoding, decoding, and schema generation to [Spectral](https://hexdocs.pm/spectral) / [spectra](https://hexdocs.pm/spectra). Configure them directly in `config/config.exs` (or `config/runtime.exs`). ### Custom codecs @@ -276,7 +278,7 @@ config :spectra, :codecs, %{ } ``` -The key is `{ModuleOwningType, {:type, type_name, arity}}`. User-configured codecs always take precedence over built-ins. See the [Spectral codec guide](https://github.com/andreashasse/spectral) for writing your own codecs with `use Spectral.Codec`. +The key is `{ModuleOwningType, {:type, type_name, arity}}`. User-configured codecs always take precedence over built-ins. See the [Spectral codec guide](https://hexdocs.pm/spectral/readme.html#custom-codecs) for writing your own codecs with `use Spectral.Codec`. ### Production: enable the module types cache @@ -297,6 +299,39 @@ spectra skips Unicode validation of list-based strings by default. Enable it whe config :spectra, :check_unicode, true ``` +## Going further with Spectral + +PhoenixSpectral only wires Phoenix to Spectral — it adds no validation or schema features of its own. Everything below is a **Spectral** feature you configure on your *types*; PhoenixSpectral then applies it automatically to request decoding, response encoding, and the generated OpenAPI spec. This list is a map, not the full manual — follow the links into the [Spectral docs](https://hexdocs.pm/spectral) for the details. + +| Want to… | Use Spectral's… | Docs | +|---|---|---| +| Constrain a string's length or shape (min/max length, regex `pattern`, `format`) **without writing a codec** | `spectral type_parameters: %{min_length: …, max_length: …, pattern: …}` on a `String.t()` type | [String and binary constraints](https://hexdocs.pm/spectral/readme.html#string-and-binary-constraints) | +| Expose `camelCase` (or any) JSON keys while keeping `snake_case` structs | `spectral field_aliases: %{first_name: "firstName"}` | [Field Aliases](https://hexdocs.pm/spectral/readme.html#field-aliases) | +| Hide internal fields (e.g. `password_hash`) or expose different views of one struct | `spectral only: [:id, :name]` | [Field Filtering with `only`](https://hexdocs.pm/spectral/readme.html#field-filtering-with-only) | +| Make a body field optional / supply a default | struct `defstruct` defaults + nullable types | [Struct defaults](https://hexdocs.pm/spectral/readme.html#struct-defaults) | +| Accept an enum from a path/query param (e.g. `?role=admin`) | an atom-union type `:: :admin \| :user`, decoded via the `binary_string` format | [Data Serialization API](https://hexdocs.pm/spectral/readme.html#data-serialization-api) | +| Serialize `DateTime`, `Date`, or `MapSet` | the built-in codecs (registered automatically) | [Built-in Codecs](https://hexdocs.pm/spectral/readme.html#built-in-codecs) | +| Encode/decode a domain type with custom rules (prefixed IDs, money, etc.) | `use Spectral.Codec` | [Custom Codecs](https://hexdocs.pm/spectral/readme.html#custom-codecs) | +| Reuse one codec across types with different config | `spectral type_parameters: …` read as the codec's `params` argument | [Codec-specific configuration](https://hexdocs.pm/spectral/readme.html#codec-specific-configuration) | +| Add `title`, `description`, or example payloads to a schema | `spectral title:`, `description:`, `examples_function:` | [Documenting Types with `spectral`](https://hexdocs.pm/spectral/readme.html#documenting-types-with-spectral) | +| Annotate a path/header/query parameter's description | a named type alias with `spectral description: …` | [Parameter descriptions](#parameter-descriptions) (above) | + +For example, length and pattern validation needs no controller code at all — declare the constraint on the type and PhoenixSpectral enforces it on every request and advertises it in the OpenAPI schema: + +```elixir +defmodule MyApp.Types do + use Spectral + + spectral type_parameters: %{min_length: 3, max_length: 30, pattern: "^[a-z0-9_]+$"} + @type username :: String.t() +end + +# A request body field typed as username() now rejects "ab" or "Bad Name" with a 400, +# and the OpenAPI schema shows minLength/maxLength/pattern. +``` + +If you reach for `conn.body_params` or hand-roll validation in a controller, stop and check this table first — Spectral almost certainly does it declaratively. + ## Design - **Typespecs are the single source of truth** — no separate schema definitions; `@spec` drives both docs and validation diff --git a/example/lib/example/types.ex b/example/lib/example/types.ex index 60691c2..074b7fe 100644 --- a/example/lib/example/types.ex +++ b/example/lib/example/types.ex @@ -85,6 +85,13 @@ defmodule Example.Types do # the request body — a missing email field decodes as nil rather than an error. defstruct [:name, email: nil] + # A named type with `type_parameters` string constraints. No custom codec is + # needed: Spectral enforces min/max length (and `pattern`, `format`) on both + # decode and encode, and emits minLength/maxLength into the OpenAPI schema. + # A name shorter than 2 or longer than 50 characters fails with a 400. + spectral(type_parameters: %{min_length: 2, max_length: 50}) + @type name :: String.t() + spectral( title: "UserInput", description: "Input for creating or updating a user. email is optional.", @@ -92,7 +99,7 @@ defmodule Example.Types do ) @type t :: %UserInput{ - name: String.t(), + name: name(), email: String.t() | nil } diff --git a/example/test/example_test.exs b/example/test/example_test.exs index d993d61..96e5ec9 100644 --- a/example/test/example_test.exs +++ b/example/test/example_test.exs @@ -73,6 +73,15 @@ defmodule ExampleTest do assert conn.status == 201 end + + test "returns 400 when name is shorter than the min_length constraint" do + conn = + build_conn() + |> authed() + |> post("/users", Jason.encode!(%{name: "A"})) + + assert conn.status == 400 + end end describe "Bearer auth on write endpoints" do diff --git a/lib/phoenix_spectral.ex b/lib/phoenix_spectral.ex index 0c98d1e..a74da54 100644 --- a/lib/phoenix_spectral.ex +++ b/lib/phoenix_spectral.ex @@ -5,6 +5,11 @@ defmodule PhoenixSpectral do Controllers that `use PhoenixSpectral.Controller` and define typespecs on their action functions become the single source of truth for OpenAPI documentation. + The schema for each request and response is derived from your types by `Spectral`. + Schema details — descriptions, examples, string constraints, field aliases, custom + codecs — are declared on the types via Spectral's `spectral/1` macro; see the + [Spectral docs](https://hexdocs.pm/spectral). + ## Usage {:ok, spec} = PhoenixSpectral.generate_openapi(MyAppWeb.Router, %{title: "My API", version: "1.0.0"}) diff --git a/lib/phoenix_spectral/controller.ex b/lib/phoenix_spectral/controller.ex index 119dc4b..200a05e 100644 --- a/lib/phoenix_spectral/controller.ex +++ b/lib/phoenix_spectral/controller.ex @@ -7,6 +7,15 @@ defmodule PhoenixSpectral.Controller do Phoenix `(conn, params)`. Request data is decoded and validated against your typespecs, and responses are encoded automatically. + > #### Powered by Spectral {: .info} + > + > Decoding, validation, and encoding are all done by `Spectral` based on the types + > in your `@spec`. How a field is validated, made optional, renamed, length-limited, + > or pattern-matched is configured on the *type* with Spectral's `spectral/1` macro — + > not here. Before validating by hand in an action, see the + > [Spectral docs](https://hexdocs.pm/spectral) and the "Going further with Spectral" + > section of the PhoenixSpectral README. + ## Usage defmodule MyAppWeb.UserController do diff --git a/mix.exs b/mix.exs index 40180b4..f5a52f1 100644 --- a/mix.exs +++ b/mix.exs @@ -15,7 +15,7 @@ defmodule PhoenixSpectral.MixProject do source_url: "https://github.com/andreashasse/phoenix_spectral", docs: [ main: "readme", - extras: ["README.md", "CHANGELOG.md"] + extras: ["README.md", "AGENTS.md", "CHANGELOG.md"] ] ] end @@ -23,7 +23,11 @@ defmodule PhoenixSpectral.MixProject do defp package do [ licenses: ["Apache-2.0"], - links: %{"GitHub" => "https://github.com/andreashasse/phoenix_spectral"} + files: ~w(lib .formatter.exs mix.exs README.md AGENTS.md CHANGELOG.md), + links: %{ + "GitHub" => "https://github.com/andreashasse/phoenix_spectral", + "Spectral" => "https://hexdocs.pm/spectral" + } ] end From fa381a547836a51e25aeb0e1a6fde354293333f5 Mon Sep 17 00:00:00 2001 From: Andreas Hasselberg Date: Wed, 17 Jun 2026 20:24:00 +0200 Subject: [PATCH 2/3] Address PR review: trim Spectral-delegation framing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove AGENTS.md and its mix.exs wiring — too much focus on delegating to Spectral (reviewer feedback). - Simplify the Controller moduledoc note from a verbose admonition to one clear sentence. - Trim the overlong type_parameters comment in the example to one line. - Drop the "codec-specific configuration" row from the README feature table — maintainer considers type_parameters-as-codec-params a mistake and doesn't want it highlighted. --- AGENTS.md | 38 ------------------------------ README.md | 3 +-- example/lib/example/types.ex | 5 +--- lib/phoenix_spectral/controller.ex | 11 +++------ mix.exs | 3 +-- 5 files changed, 6 insertions(+), 54 deletions(-) delete mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index 3f54188..0000000 --- a/AGENTS.md +++ /dev/null @@ -1,38 +0,0 @@ -# AGENTS.md - -Guidance for AI agents building or modifying APIs with **PhoenixSpectral**. (Human contributors: see [CLAUDE.md](CLAUDE.md) for repo workflow.) - -## The one thing to internalize - -PhoenixSpectral is a **thin Phoenix adapter over [Spectral](https://hexdocs.pm/spectral)**. It adds almost no validation or schema features of its own — it reads your controller `@spec`s and delegates decoding, validation, encoding, and JSON-schema generation to Spectral. - -**Consequence:** if you only read PhoenixSpectral's docs, you will write more controller code than you need and miss validation that Spectral does declaratively. Before hand-rolling anything, read the [Spectral docs](https://hexdocs.pm/spectral) — the request/response shaping power lives there, configured on your *types* with the `spectral/1` macro. - -## Reach for a Spectral type feature before writing controller code - -When you're about to do any of these in an action, stop — Spectral does it on the type instead: - -| You're tempted to… | Use instead (on the type) | Spectral docs | -|---|---|---| -| Check a string's length / format in the controller | `spectral type_parameters: %{min_length:, max_length:, pattern:, format:}` on a `String.t()` type | [String constraints](https://hexdocs.pm/spectral/readme.html#string-and-binary-constraints) | -| Read `conn.body_params` / `conn.query_params` and validate by hand | the typed `body` / `query_params` arguments — already decoded and validated against the `@spec` | README "Step 2" | -| Map `camelCase` JSON to `snake_case` fields manually | `spectral field_aliases: %{first_name: "firstName"}` | [Field Aliases](https://hexdocs.pm/spectral/readme.html#field-aliases) | -| Strip secret fields (`password_hash`) before responding | `spectral only: [:id, :name, ...]` | [`only`](https://hexdocs.pm/spectral/readme.html#field-filtering-with-only) | -| Make a body field optional / give it a default | struct `defstruct` default + a nullable type | [Struct defaults](https://hexdocs.pm/spectral/readme.html#struct-defaults) | -| Parse an enum from a path/query param | an atom-union type (`:: :a \| :b`) | [Data Serialization API](https://hexdocs.pm/spectral/readme.html#data-serialization-api) | -| Format `DateTime`/`Date`/`MapSet` | the built-in codecs (automatic) | [Built-in Codecs](https://hexdocs.pm/spectral/readme.html#built-in-codecs) | -| Encode/decode a domain type (prefixed IDs, money) | a custom codec via `use Spectral.Codec` | [Custom Codecs](https://hexdocs.pm/spectral/readme.html#custom-codecs) | - -Anything you declare on the type also flows automatically into the generated OpenAPI 3.1 spec — you do not write schema separately. - -## Conventions specific to PhoenixSpectral - -- Actions take `(conn, path_args, query_params, headers, body)` and return `{status, headers, body}` (or a `Plug.Conn` for streaming/raw responses). -- Use `conn` only for out-of-band context (`conn.assigns`, `conn.remote_ip`). Do **not** read `conn.path_params`, `conn.query_params`, `conn.req_headers`, or `conn.body_params`. -- Response bodies must be Spectral-typed structs, not plain maps. -- Union return types (e.g. `{200, %{}, User.t()} | {404, %{}, Error.t()}`) produce multiple OpenAPI responses. -- See the runnable [`example/`](example/) app for working uses of `only`, a custom codec, optional fields, examples, and `type_parameters` string constraints. - -## If you maintain a downstream project - -A dependency's `AGENTS.md` is **not** auto-read by an agent working in your repo. If you want agents in *your* project to use Spectral's full feature set, copy the table above (or a link to it) into your own project's `AGENTS.md` / `CLAUDE.md`. diff --git a/README.md b/README.md index 5836147..8556d78 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ PhoenixSpectral integrates [Spectral](https://hexdocs.pm/spectral) with Phoenix, making controller typespecs the single source of truth for OpenAPI 3.1 spec generation and request/response validation. Define your types once — PhoenixSpectral derives the API docs and enforces them at runtime. -> **Most of the power lives in Spectral.** PhoenixSpectral is a thin Phoenix adapter; the type system that shapes and validates your requests and responses is [Spectral](https://hexdocs.pm/spectral). Features like string/length/pattern constraints, camelCase field aliases, custom codecs, and the built-in date/time codecs are configured on your *types* via Spectral, not here. Read the [Spectral docs](https://hexdocs.pm/spectral) and the [Going further with Spectral](#going-further-with-spectral) section below before assuming a capability is missing. AI agents (and the humans guiding them) should start from [AGENTS.md](AGENTS.md), which maps "I want to…" tasks to the Spectral feature that does them. +> **Most of the power lives in Spectral.** PhoenixSpectral is a thin Phoenix adapter; the type system that shapes and validates your requests and responses is [Spectral](https://hexdocs.pm/spectral). Features like string/length/pattern constraints, camelCase field aliases, custom codecs, and the built-in date/time codecs are configured on your *types* via Spectral, not here. Read the [Spectral docs](https://hexdocs.pm/spectral) and the [Going further with Spectral](#going-further-with-spectral) section below before assuming a capability is missing. ## Installation @@ -312,7 +312,6 @@ PhoenixSpectral only wires Phoenix to Spectral — it adds no validation or sche | Accept an enum from a path/query param (e.g. `?role=admin`) | an atom-union type `:: :admin \| :user`, decoded via the `binary_string` format | [Data Serialization API](https://hexdocs.pm/spectral/readme.html#data-serialization-api) | | Serialize `DateTime`, `Date`, or `MapSet` | the built-in codecs (registered automatically) | [Built-in Codecs](https://hexdocs.pm/spectral/readme.html#built-in-codecs) | | Encode/decode a domain type with custom rules (prefixed IDs, money, etc.) | `use Spectral.Codec` | [Custom Codecs](https://hexdocs.pm/spectral/readme.html#custom-codecs) | -| Reuse one codec across types with different config | `spectral type_parameters: …` read as the codec's `params` argument | [Codec-specific configuration](https://hexdocs.pm/spectral/readme.html#codec-specific-configuration) | | Add `title`, `description`, or example payloads to a schema | `spectral title:`, `description:`, `examples_function:` | [Documenting Types with `spectral`](https://hexdocs.pm/spectral/readme.html#documenting-types-with-spectral) | | Annotate a path/header/query parameter's description | a named type alias with `spectral description: …` | [Parameter descriptions](#parameter-descriptions) (above) | diff --git a/example/lib/example/types.ex b/example/lib/example/types.ex index 074b7fe..daa1830 100644 --- a/example/lib/example/types.ex +++ b/example/lib/example/types.ex @@ -85,10 +85,7 @@ defmodule Example.Types do # the request body — a missing email field decodes as nil rather than an error. defstruct [:name, email: nil] - # A named type with `type_parameters` string constraints. No custom codec is - # needed: Spectral enforces min/max length (and `pattern`, `format`) on both - # decode and encode, and emits minLength/maxLength into the OpenAPI schema. - # A name shorter than 2 or longer than 50 characters fails with a 400. + # type_parameters enforces length constraints (no codec) and emits them into the schema. spectral(type_parameters: %{min_length: 2, max_length: 50}) @type name :: String.t() diff --git a/lib/phoenix_spectral/controller.ex b/lib/phoenix_spectral/controller.ex index 200a05e..77fc7a1 100644 --- a/lib/phoenix_spectral/controller.ex +++ b/lib/phoenix_spectral/controller.ex @@ -7,14 +7,9 @@ defmodule PhoenixSpectral.Controller do Phoenix `(conn, params)`. Request data is decoded and validated against your typespecs, and responses are encoded automatically. - > #### Powered by Spectral {: .info} - > - > Decoding, validation, and encoding are all done by `Spectral` based on the types - > in your `@spec`. How a field is validated, made optional, renamed, length-limited, - > or pattern-matched is configured on the *type* with Spectral's `spectral/1` macro — - > not here. Before validating by hand in an action, see the - > [Spectral docs](https://hexdocs.pm/spectral) and the "Going further with Spectral" - > section of the PhoenixSpectral README. + Decoding, validation, and encoding are performed by `Spectral` from the types in + your `@spec`. See the [Spectral docs](https://hexdocs.pm/spectral) for how to shape + a type (optional fields, string constraints, field aliases, custom codecs). ## Usage diff --git a/mix.exs b/mix.exs index f5a52f1..d6cfafa 100644 --- a/mix.exs +++ b/mix.exs @@ -15,7 +15,7 @@ defmodule PhoenixSpectral.MixProject do source_url: "https://github.com/andreashasse/phoenix_spectral", docs: [ main: "readme", - extras: ["README.md", "AGENTS.md", "CHANGELOG.md"] + extras: ["README.md", "CHANGELOG.md"] ] ] end @@ -23,7 +23,6 @@ defmodule PhoenixSpectral.MixProject do defp package do [ licenses: ["Apache-2.0"], - files: ~w(lib .formatter.exs mix.exs README.md AGENTS.md CHANGELOG.md), links: %{ "GitHub" => "https://github.com/andreashasse/phoenix_spectral", "Spectral" => "https://hexdocs.pm/spectral" From b754cdbc5521c09dc0feb04ab9eda8c63487e669 Mon Sep 17 00:00:00 2001 From: Andreas Hasselberg Date: Wed, 17 Jun 2026 20:42:16 +0200 Subject: [PATCH 3/3] Prepare release 0.6.1 --- CHANGELOG.md | 6 ++++++ README.md | 2 +- mix.exs | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d69d5ee..df2d958 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.6.1] - 2026-06-17 + +### Changed + +- Documentation now surfaces Spectral's type features directly: a "Going further with Spectral" reference table in the README (string/length/pattern constraints, field aliases, `only`, struct defaults, enums, built-in and custom codecs), hexdocs links throughout, and "powered by Spectral" pointers in the module docs. The example app gained a `type_parameters` string-constraint demonstration with a test. + ## [0.6.0] - 2026-06-14 ### Added diff --git a/README.md b/README.md index 8556d78..d5b8f79 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Add `phoenix_spectral` to your dependencies in `mix.exs`: ```elixir def deps do [ - {:phoenix_spectral, "~> 0.6.0"} + {:phoenix_spectral, "~> 0.6.1"} ] end ``` diff --git a/mix.exs b/mix.exs index d6cfafa..9bf83d6 100644 --- a/mix.exs +++ b/mix.exs @@ -4,7 +4,7 @@ defmodule PhoenixSpectral.MixProject do def project do [ app: :phoenix_spectral, - version: "0.6.0", + version: "0.6.1", elixir: ">= 1.18.0", start_permanent: Mix.env() == :prod, elixirc_paths: elixirc_paths(Mix.env()),