api-bones 4.0.2

Opinionated REST API types: errors (RFC 9457), pagination, health checks, and more
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [4.0.2] - 2026-04-23

### Security

- Bumped transitive dependency `rand 0.9.2 → 0.9.4` (RUSTSEC-2026-0097 / GHSA-cq8v-f236-94qc):
  unsound aliased mutable reference when a custom logger calls `rand::rng()` during reseeding.
  This crate's own code was not affected (rand is only used via the optional `fake` feature and
  dev-dependencies); the lock-file pin eliminates the vulnerable version entirely.

### Changed

- CI: weekly scheduled run added for security audit and Dependabot allowlist review.

## [4.0.1] - 2026-04-21

### Changed

- Upgraded optional dependencies: `rand 0.9 → 0.10`, `hmac 0.12 → 0.13`, `fake 3 → 5`,
  `sha2 0.10 → 0.11`, `validator 0.18 → 0.20`, `uuid 1.11 → 1.23`,
  `reqwest 0.12 → 0.13` (in `api-bones-reqwest`). All are internal to optional feature
  flags and not exposed in the public API.
- CI: Dependabot enabled for Cargo and GitHub Actions.
- CI: CodeQL scanning workflow added with false-positive suppression config.
- CI: Workflow permissions hardened (`contents: read` default; `tag.yml` minimal grant).

### Fixed

- CI workflow permissions were overly broad (no explicit `permissions:` block). Now
  locked to least privilege per job.

## [4.0.0] - 2026-04-20

### BREAKING CHANGE

- Removed `impl FromRequestParts for OrgId`. The bare axum extractor allowed tenant-confusion — a client holding a valid token for org A could send `X-Org-Id: <org B>` and land at a handler that wrote to org B with org A's principal. Handlers must take `OrganizationContext` exclusively, which reconciles token claims, the `X-Org-Id` header, and any path parameter (see ADR platform/0015).

### Added

- `OrgId::try_from_headers(&http::HeaderMap) -> Result<OrgId, OrgIdHeaderError>` (feature `http`) — non-extractor parser for callers without an `AuthLayer` (webhook verifiers, out-of-band tooling).
- `OrgIdHeaderError` with `Missing`, `NotUtf8`, `Invalid` variants.
- `examples/header_parser.rs` demonstrating the sanctioned non-axum usage.

### Migration

```rust
// Before (rejected — tenant-confusion loophole):
async fn handler(org_id: OrgId) { /* writes to org_id without reconciling auth */ }

// After:
async fn handler(ctx: OrganizationContext) {
    let org_id = ctx.org_id;
    // ctx also carries the authenticated Principal, RequestId, roles, and attestation.
}
```

## [3.1.0] - 2026-04-19

### Added

- `Principal::org_path_display() -> String` — returns the org ancestry path as a
  comma-separated UUID string (`""` for platform-internal actors). Gated behind the
  `uuid` feature. Canonical formatter for the `enduser.org_path` OTEL span attribute
  (platform/0010).

## [3.0.0] - 2026-04-19

### Added

- `PrincipalId(Cow<'static, str>)` — identity string newtype extracted from `Principal`.
- `PrincipalKind``User | Service | System` enum on `Principal`.
- `Principal.org_path: Vec<OrgId>` — org ancestry carried on the principal (requires `uuid` feature).
- `RoleScope``Self_ | Subtree | Specific(OrgId)` scoping for role bindings.
- `RoleBinding { role: Role, scope: RoleScope }` — scope-aware role assignment.
- `OrgPath(Vec<OrgId>)``X-Org-Path` header newtype with `HeaderId`, `FromStr`, and axum extractor.
- `OrganizationContext.org_path: Vec<OrgId>` + `with_org_path` builder.
- `ErrorCode::OrgOutsideSubtree`, `AncestorRequired`, `CrossSubtreeAccess` — new 403 variants.

### Changed

- **BREAKING** `Principal` is now `struct { id: PrincipalId, kind: PrincipalKind, org_path: Vec<OrgId> }` (was a `Cow<str>` newtype). Serde wire format changed.
- **BREAKING** `Principal::system()` is no longer `const` (contains `Vec`).
- **BREAKING** `OrganizationContext.roles` is now `Vec<RoleBinding>` (was `Vec<Role>`).

### Removed

- **BREAKING** `Principal::from_owned(String)` moved to `PrincipalId::from_owned`.

## [2.3.1] - 2026-04-19

### Added

- `Principal::from_owned(String) -> Self` — construct a principal from a runtime-owned
  string for database round-trips. Accepts any string without UUID validation. Use only for
  deserialization; prefer `Principal::human` / `Principal::system` for new actors.

## [2.3.0] - 2026-04-18

### Added

- `OrgId` — UUID v4 tenant identifier newtype, propagated via the `X-Org-Id` HTTP header.
  Implements `HeaderId`, `FromStr`, `Display`, `From<Uuid>`, axum `FromRequestParts`
  (behind the `axum` feature), and serde. Requires the `uuid` feature.
- `OrganizationContext` — cross-cutting platform context bundle carrying `OrgId`,
  `Principal`, `RequestId`, `Vec<Role>`, and `Option<Attestation>` in a single
  cheap-to-clone struct. Constructor: `OrganizationContext::new(org_id, principal,
  request_id)`. Builder methods: `with_roles`, `with_attestation`. Serde derives gated on
  the `serde` feature.
- `Role(Arc<str>)` — token-neutral role label newtype. No permission semantics in
  api-bones; permission evaluation belongs to consumer auth layers.
- `Attestation { kind: AttestationKind, raw: Vec<u8> }` — opaque credential payload with
  kind tag. Downstream auth crates decode the raw bytes per kind.
- `AttestationKind``#[non_exhaustive]` enum: `Biscuit`, `Jwt`, `ApiKey`, `Mtls`.

## [2.2.1] - 2026-04-18

### Added

- `Principal::from_stored(String) -> Principal` — reconstruct a `Principal` from a
  previously persisted string (database column, serialised message). Accepts both UUID
  and system-name formats without validation, since the value was already validated at
  write time. Use on read paths only; prefer `human` or `system` for new principals.

## [2.2.0] - 2026-04-18

### Added

- `Principal::human(uuid: Uuid) -> Principal` — UUID-typed constructor for human/operator
  actors. Requires the `uuid` feature (default). Prevents PII (emails, display names) from
  entering audit logs and OTEL spans.
- `Principal::try_parse(&str) -> Result<Principal, PrincipalParseError>` — parses any
  valid UUID text form; rejects arbitrary strings, emails, and empty input.
- `PrincipalParseError` — error type returned by `try_parse`; exposes the offending input
  string. Implements `Display` and `std::error::Error`.
- `ResolvedPrincipal { id: Principal, display_name: Option<String> }` — read-path display
  helper that pairs an opaque `Principal` with an optional name resolved at read time by
  the identity service. Never persisted. Implements `Display` fallback to UUID, serde
  (skips `display_name` when `None`), and `From<Principal>`.

### Changed (BREAKING)

- `Principal::new(impl Into<String>)` **removed**. Passing arbitrary strings is a
  GDPR/PII violation (see issue #204). Migrate:
  - Human actors: `Principal::human(uuid)` or `Principal::try_parse(uuid_str)?`
  - System actors: `Principal::system("my-service.worker")` (unchanged)
- `fake` / `arbitrary` / `proptest` impls for `Principal` now generate UUID-backed values
  instead of random strings.

### Changed

- CI: migrated to reusable shared workflow.

## [2.1.0] - 2026-04-16

### Added

- `audit::Principal` — canonical actor-identity newtype (`Cow<'static, str>`-backed).
  - `Principal::new(impl Into<String>)` for end-user/operator IDs.
  - `Principal::system(&'static str)``const`, infallible, zero-alloc for autonomous actors.
  - `as_str`, `Display`, non-redacting `Debug`, `Eq`, `Hash`, `Clone`.
  - Feature-gated integrations: `serde` (transparent string), `utoipa`, `schemars`,
    `arbitrary`, `proptest`.
- Re-export: `api_bones::Principal`.

### Changed (BREAKING)

- `AuditInfo::created_by` and `AuditInfo::updated_by` are now non-optional
  `Principal` fields (previously `Option<String>`). System processes are still
  actors and must declare themselves via `Principal::system`.
- `AuditInfo::new(created_at, updated_at, created_by: Principal, updated_by: Principal)`.
- `AuditInfo::now(created_by: Principal)``updated_by` now initialized to a
  clone of `created_by` rather than `None`.
- `AuditInfo::touch(&mut self, updated_by: Principal)` — no longer accepts
  `Option`.

### Migration

Downstream crates that previously passed `Option<String>` must migrate to
`Principal`:

```rust
// Before
AuditInfo::now(Some("alice".to_string()));
audit.touch(None);

// After
use api_bones::Principal;
AuditInfo::now(Principal::new("alice"));
audit.touch(Principal::system("my-service.cleanup"));
```

## [2.0.3] - 2026-04-10

### Added

- `api-bones-tower`: optional `uuid` and `chrono` feature passthroughs for parity with `api-bones-reqwest`
- `api-bones-tower`, `api-bones-reqwest`: rustdoc feature tables documenting default and optional `api-bones` features

### Changed

- `api-bones-tower`, `api-bones-reqwest`: versions aligned to `2.0.3`

## [2.0.2] - 2026-04-10

### Fixed

- `ApiError::causes` annotated with `#[schema(value_type = Vec<Object>)]` to prevent infinite recursion in utoipa schema generation (`causes: Vec<Self>` caused a stack overflow when any crate called `OpenApi::openapi()` with `ApiError` in its components)

## [2.0.1] - 2026-04-10

### Fixed

- CI: publish pipeline now correctly publishes all workspace crates (`api-bones-tower`, `api-bones-reqwest`)
- `api-bones-tower` version aligned to `2.0.1` (was incorrectly left at `0.1.0`)

## [2.0.0] - 2026-04-09

### Breaking Changes

- **`response::Links` removed** — use `links::Links` everywhere. `links::Links` is `Vec<Link>`-backed and supports arbitrary `rel` types; the old 3-field struct is gone.
- **`models::ErrorResponse` removed** — use `ApiError` directly for all error responses.
- **`reqwest` feature removed from main crate**`ApiError::from_response` moved to the new `api-bones-reqwest` satellite crate.
- **`RequestIdParseError` renamed to `RequestIdError`** — now an enum with meaningful variants.

### Added

- **`api-bones-tower`** satellite crate — Tower `RequestIdLayer` and `ProblemJsonLayer` extracted from main crate; main crate is now pure types.
- **`api-bones-reqwest`** satellite crate — reqwest client extensions (`RequestBuilderExt`, `ResponseExt`) extracted from main crate.
- **`HeaderId` trait** — shared abstraction over `RequestId`, `CorrelationId`, `IdempotencyKey` HTTP header newtypes (`as_str()`, `header_name()`).
- **`TryFrom<StatusCode> for ErrorCode`** — 4xx/5xx status codes now convert back to their canonical `ErrorCode`.
- **`QueryBuilder`** — typed query parameter builder with `.set()`, `.set_opt()`, `.extend_from_struct<T: Serialize>()`, `.merge_into_url()`.
- **Fallible constructors** for all constrained param types: `PaginationParams::new()`, `CursorPaginationParams::new()`, `KeysetPaginationParams::new()`, `SearchParams::new()` — enforce constraints without the `validator` feature.
- **`FromRequestParts`** implemented directly on core types (`PaginationParams`, `SortParams`, `IfMatch`, `IfNoneMatch`) when `axum` feature is enabled — no wrapper newtypes needed.
- **`ETag::parse_list()`** — parse comma-separated `If-Match`/`If-None-Match` header values.
- **Feature powerset CI** via `cargo-hack` — all feature combinations tested.
- New runnable examples: `auth_flow`, `bulk_envelope`, `cursor_hmac`, `error_construction`, `cache_control`, `etag_conditional`, `range_headers`, `trace_context`, `axum_core_extractors`, `query_builder`.

### Changed

- `api-bones-tower` and `api-bones-reqwest` version aligned with main crate (`2.0.0`).
- `api-bones-tower` adds optional `uuid` and `chrono` feature passthroughs.
- `PaginationParams` fields use `#[serde(default)]` matching the rest of the query param types.
- `CorrelationId` redundant constructors (`new_random`, `new_id`) removed — keep only `new_uuid()`.
- `ErrorTypeMode` std-only limitation (`error_type_mode()`, `set_error_type_mode()`) documented explicitly.

### Removed

- `models::ErrorResponse` (use `ApiError`)
- `response::Links` (use `links::Links`)
- `reqwest` feature from main crate (moved to `api-bones-reqwest`)
- `tower` feature from main crate (moved to `api-bones-tower`)
- `CorrelationId::new_random()`, `CorrelationId::new_id()` (redundant)

## [1.10.0] - 2026-04-09

### Added

- 12 new `ErrorCode` variants rounding out common HTTP error codes (#82):
  - `MethodNotAllowed` (405), `NotAcceptable` (406), `RequestTimeout` (408)
  - `Gone` (410), `PreconditionFailed` (412), `PayloadTooLarge` (413)
  - `UnsupportedMediaType` (415), `PreconditionRequired` (428)
  - `RequestHeaderFieldsTooLarge` (431), `NotImplemented` (501)
  - `BadGateway` (502), `GatewayTimeout` (504)
- Each variant wired through `status_code()`, `title()`, `urn_slug()`, and `from_type_uri()` roundtrip

## [1.9.0] - 2026-04-09

### Added

- **Axum extractors** (`axum` feature) — `FromRequestParts` impls removing the need for downstream newtype wrappers:
  - `PaginationParams` and `CursorPaginationParams` — parse query string and run `validator` range checks (limit 1..=100)
  - `SortParams` — parse `sort_by` / `direction` query params
  - `IfMatch` and `IfNoneMatch` — parse the matching conditional request headers, including the `*` wildcard and comma-separated tag lists
  - All rejections are `ApiError::bad_request`, so consumers get Problem+JSON bodies for free
- **`ETag` wire-format parsing** (`http` feature):
  - `impl FromStr for ETag` — accepts `"v1"` / `W/"v1"`, rejects unquoted, empty, or malformed input
  - `ETag::parse_list(&str)` — handles comma-separated header values (e.g. `If-Match: "a", W/"b"`)
  - `ParseETagError` enum with `Display` + `std::error::Error`
- **Structured rate-limit metadata on `ApiError`**:
  - `ApiError::with_rate_limit(RateLimitInfo)` — attaches quota data to any error
  - `ApiError::rate_limited_with(RateLimitInfo)` — 429 constructor that derives `detail` from `retry_after`
  - New optional `rate_limit` field serializes inline on `ApiError` and propagates to `ProblemJson` as the `rate_limit` extension member
- Runnable example `axum_extractors_and_ratelimit` demonstrating the new extractors and structured 429 bodies

## [1.8.0] - 2026-04-08

### Added

- `ProblemJson` — RFC 7807 / 9457 wire-format response type with flat extension members
  - Fields: `type`, `title`, `status`, `detail`, `instance`, `extensions` (flattened `HashMap`)
  - `ProblemJson::new(type, title, status, detail)` constructor
  - `ProblemJson::with_instance(uri)` builder method
  - `ProblemJson::extend(key, value)` for inserting extension members (e.g. `trace_id`)
  - `From<ApiError> for ProblemJson` — converts `request_id``instance`, `errors` → extension
  - `IntoResponse` for `ProblemJson` (requires `axum` feature) with `application/problem+json` Content-Type
  - `utoipa::ToSchema` and `schemars::JsonSchema` derives (requires respective features)
  - `problem_json` runnable example

## [1.7.0] - 2026-04-08

### Fixed

- `ErrorCode` utoipa schema now emits the actual wire format (`urn:api-bones:error:*` strings) instead of Rust variant names, fixing SDK client deserialization of error responses

## [1.6.0] - 2026-04-06

### Added

- `schemars` support: `JsonSchema` derive on all public types
- Runnable usage examples for axum, pagination, and health checks
- Comprehensive per-type example files
- Doc-tests on all public items

### Changed

- Renamed crate from `shared-types` to `api-bones`

### Fixed

- `ErrorTypeMode` now uses `RwLock` instead of `OnceLock` for test safety
- CI: correct crate name in publish summary
- CI: add Forgejo cargo registry configuration

## [1.5.0] - 2026-04-06

### Added

- `Slug` validated newtype for URL-friendly identifiers
- `source()` chaining for `ApiError` and `ValidationError` implementations
- `no_std` support behind feature flags

## [1.4.0] - 2026-04-04

### Added

- `SortParams`, `FilterParams`, and `SearchParams` query types
- Generic `ApiResponse<T>` envelope with metadata
- HATEOAS `Link` and `Links` types
- `RateLimitInfo` type for rate limit metadata
- `ETag` and conditional request types (RFC 7232)
- `arbitrary` and `proptest` feature flags
- `BulkRequest`, `BulkResponse`, and `BulkItemResult` types
- `AuditInfo` struct for resource audit metadata with arbitrary/proptest support
- `fake` crate integration for test fixture generation
- Typestate builder patterns for `ApiError`, `HealthCheck`, `ReadinessResponse`
- `CursorPaginationParams` and usage guidance

### Fixed

- Restored `lib.rs` modules and synced `Cargo.toml` after bulk types addition

## [1.3.1] - 2026-04-04

### Fixed

- Removed duplicate page-based pagination types

### Changed

- Pinned Rust toolchain to 1.94
- Removed Gherkin feature file from shared-types

## [1.3.0] - 2026-04-04

### Added

- Flat `PaginatedResponse` and validated `PaginationParams`

## [1.2.0] - 2026-03-25

## [1.1.0] - 2026-03-25

### Added

- iCalendar RFC 5545 support as optional `calendar` feature

## [1.0.0] - 2026-03-14

### Added

- Initial release with core API types: `ApiError`, `ValidationError`, `HealthCheck`, `ReadinessResponse`, `PaginationParams`

[Unreleased]: https://github.com/brefwiz/api-bones/compare/v4.0.0...HEAD
[4.0.0]: https://github.com/brefwiz/api-bones/compare/v3.1.0...v4.0.0
[3.1.0]: https://github.com/brefwiz/api-bones/compare/v3.0.0...v3.1.0
[3.0.0]: https://github.com/brefwiz/api-bones/compare/v2.0.0...v3.0.0
[2.0.0]: https://github.com/brefwiz/api-bones/compare/v1.10.0...v2.0.0
[1.10.0]: https://github.com/brefwiz/api-bones/compare/v1.9.0...v1.10.0
[1.9.0]: https://github.com/brefwiz/api-bones/compare/v1.8.0...v1.9.0
[1.8.0]: https://github.com/brefwiz/api-bones/compare/v1.7.0...v1.8.0
[1.7.0]: https://github.com/brefwiz/api-bones/compare/v1.6.0...v1.7.0
[1.6.0]: https://github.com/brefwiz/api-bones/compare/v1.5.0...v1.6.0
[1.5.0]: https://github.com/brefwiz/api-bones/compare/v1.4.0...v1.5.0
[1.4.0]: https://github.com/brefwiz/api-bones/compare/v1.3.1...v1.4.0
[1.3.1]: https://github.com/brefwiz/api-bones/compare/v1.3.0...v1.3.1
[1.3.0]: https://github.com/brefwiz/api-bones/compare/v1.2.0...v1.3.0
[1.2.0]: https://github.com/brefwiz/api-bones/compare/v1.1.0...v1.2.0
[1.1.0]: https://github.com/brefwiz/api-bones/compare/v1.0.0...v1.1.0
[1.0.0]: https://github.com/brefwiz/api-bones/releases/tag/v1.0.0