@zio.dev/zio-blocks 0.0.33 → 0.0.51

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/guides/compile-time-resource-safety-with-scope.md +16 -17
  2. package/guides/getting-started-with-mux.md +1507 -0
  3. package/guides/query-dsl-extending.md +161 -102
  4. package/guides/query-dsl-fluent-builder.md +217 -157
  5. package/guides/query-dsl-reified-optics.md +12 -10
  6. package/guides/query-dsl-sql.md +246 -165
  7. package/guides/telemetry-guide.md +1069 -0
  8. package/guides/zio-schema-migration.md +29 -22
  9. package/index.md +292 -50
  10. package/package.json +1 -1
  11. package/plans/config-follow-up-prs.md +188 -0
  12. package/plans/config-pr-assessment-roadmap.md +310 -0
  13. package/reference/MuxDataFlow.jsx +250 -0
  14. package/reference/async.md +651 -0
  15. package/reference/chunk.md +3533 -308
  16. package/reference/codegen/case-class.md +436 -0
  17. package/reference/codegen/emitter-config.md +383 -0
  18. package/reference/codegen/examples.md +664 -0
  19. package/reference/codegen/field.md +316 -0
  20. package/reference/codegen/index.md +317 -0
  21. package/reference/codegen/scala-emitter.md +392 -0
  22. package/reference/codegen/scala-file.md +276 -0
  23. package/reference/codegen/sealed-trait.md +408 -0
  24. package/reference/codegen/type-definition.md +340 -0
  25. package/reference/codegen/type-ref.md +201 -0
  26. package/reference/combinators.md +347 -117
  27. package/reference/config.md +158 -0
  28. package/reference/context.md +4 -4
  29. package/reference/datastar.md +346 -0
  30. package/reference/docs.md +1461 -345
  31. package/reference/endpoint/auth-type.md +146 -0
  32. package/reference/endpoint/endpoint.md +297 -0
  33. package/reference/endpoint/http-codec.md +249 -0
  34. package/reference/endpoint/index.md +825 -0
  35. package/reference/endpoint/path-codec.md +237 -0
  36. package/reference/endpoint/route-pattern.md +196 -0
  37. package/reference/endpoint/route-tree.md +111 -0
  38. package/reference/endpoint/segment-codec.md +212 -0
  39. package/reference/html.md +1120 -0
  40. package/reference/htmx/attribute-values.md +359 -0
  41. package/reference/htmx/hx-encoding.md +111 -0
  42. package/reference/htmx/hx-params.md +204 -0
  43. package/reference/htmx/hx-swap.md +276 -0
  44. package/reference/htmx/hx-sync.md +251 -0
  45. package/reference/htmx/hx-target.md +314 -0
  46. package/reference/htmx/hx-trigger.md +457 -0
  47. package/reference/htmx/hx-url-update.md +239 -0
  48. package/reference/htmx/index.md +855 -0
  49. package/reference/http-model/index.md +47 -0
  50. package/reference/http-model/model.md +1481 -0
  51. package/reference/http-model/schema.md +747 -0
  52. package/reference/maybe.md +826 -0
  53. package/reference/media-type.md +2 -2
  54. package/reference/mux.mdx +823 -0
  55. package/reference/openapi.md +1351 -0
  56. package/reference/resource-management/defer-handle.md +1 -1
  57. package/reference/resource-management/resource.md +31 -2
  58. package/reference/resource-management/scope.md +28 -12
  59. package/reference/resource-management/wire.md +3 -7
  60. package/reference/ringbuffer/MpmcDiagram.jsx +717 -0
  61. package/reference/ringbuffer/MpscDiagram.jsx +618 -0
  62. package/reference/ringbuffer/SpmcDiagram.jsx +680 -0
  63. package/reference/ringbuffer/SpscDiagram.jsx +677 -0
  64. package/reference/ringbuffer/advanced.mdx +109 -0
  65. package/reference/ringbuffer/index.mdx +145 -0
  66. package/reference/ringbuffer/mpmc.mdx +151 -0
  67. package/reference/ringbuffer/mpsc.mdx +132 -0
  68. package/reference/ringbuffer/spmc.mdx +108 -0
  69. package/reference/ringbuffer/spsc.mdx +344 -0
  70. package/reference/{allows.md → schema/allows.md} +4 -4
  71. package/reference/{binding-resolver.md → schema/binding-resolver.md} +1 -1
  72. package/reference/{binding.md → schema/binding.md} +2 -3
  73. package/reference/schema/built-in-codecs/avro.md +451 -0
  74. package/reference/schema/built-in-codecs/bson.md +480 -0
  75. package/reference/schema/built-in-codecs/csv.md +564 -0
  76. package/reference/schema/built-in-codecs/index.md +77 -0
  77. package/reference/schema/built-in-codecs/json/index.md +295 -0
  78. package/reference/schema/built-in-codecs/json/json-config.md +217 -0
  79. package/reference/{json-patch.md → schema/built-in-codecs/json/json-patch.md} +5 -5
  80. package/reference/{json-schema.md → schema/built-in-codecs/json/json-schema.md} +14 -47
  81. package/reference/schema/built-in-codecs/json/json-selection.md +322 -0
  82. package/reference/{json.md → schema/built-in-codecs/json/json.md} +32 -64
  83. package/reference/schema/built-in-codecs/messagepack.md +508 -0
  84. package/reference/schema/built-in-codecs/thrift.md +433 -0
  85. package/reference/schema/built-in-codecs/toon.md +1078 -0
  86. package/reference/{xml.md → schema/built-in-codecs/xml.md} +13 -9
  87. package/reference/schema/built-in-codecs/yaml.md +552 -0
  88. package/reference/{codec.md → schema/codec.md} +10 -10
  89. package/reference/{dynamic-optic.md → schema/dynamic-optic.md} +151 -5
  90. package/reference/{dynamic-schema.md → schema/dynamic-schema.md} +8 -8
  91. package/reference/schema/format.md +92 -0
  92. package/reference/schema/index.md +50 -0
  93. package/reference/schema/migration.md +297 -0
  94. package/reference/{modifier.md → schema/modifier.md} +58 -7
  95. package/reference/{optics.md → schema/optics.md} +2 -2
  96. package/reference/{patch.md → schema/patch.md} +1 -1
  97. package/{path-interpolator.md → reference/schema/path-interpolator.md} +165 -72
  98. package/reference/{schema-evolution → schema/schema-evolution}/as.md +8 -8
  99. package/reference/{schema-evolution → schema/schema-evolution}/index.md +2 -2
  100. package/reference/{schema-evolution → schema/schema-evolution}/into.md +8 -8
  101. package/reference/{schema-expr.md → schema/schema-expr.md} +110 -175
  102. package/reference/{schema.md → schema/schema.md} +12 -0
  103. package/reference/{structural-types.md → schema/structural-types.md} +1 -1
  104. package/reference/{type-class-derivation.md → schema/type-class-derivation.md} +63 -1
  105. package/reference/smithy.md +533 -0
  106. package/reference/sql/db-codec-deriver.md +71 -0
  107. package/reference/sql/db-codec.md +687 -0
  108. package/reference/sql/db-con.md +271 -0
  109. package/reference/sql/db-connection.md +153 -0
  110. package/reference/sql/db-param-writer.md +77 -0
  111. package/reference/sql/db-param.md +66 -0
  112. package/reference/sql/db-result-reader.md +146 -0
  113. package/reference/sql/db-tx.md +82 -0
  114. package/reference/sql/db-value.md +41 -0
  115. package/reference/sql/ddl.md +85 -0
  116. package/reference/sql/frag.md +254 -0
  117. package/reference/sql/index.md +341 -0
  118. package/reference/sql/repo.md +600 -0
  119. package/reference/sql/sql-dialect.md +73 -0
  120. package/reference/sql/sql-logger.md +62 -0
  121. package/reference/sql/sql-name-mapper.md +70 -0
  122. package/reference/sql/table-metadata.md +134 -0
  123. package/reference/sql/table.md +448 -0
  124. package/reference/sql/transactor-zio.md +399 -0
  125. package/reference/sql/transactor.md +353 -0
  126. package/reference/sql-zio.md +112 -0
  127. package/reference/streams/concurrent-operators.md +106 -0
  128. package/reference/streams/index.md +653 -0
  129. package/reference/streams/pipeline.md +718 -0
  130. package/reference/streams/reader.md +1284 -0
  131. package/reference/streams/scala-2-compatibility.md +55 -0
  132. package/reference/streams/sink.md +1426 -0
  133. package/reference/streams/stream.md +2526 -0
  134. package/reference/streams/writer.md +1045 -0
  135. package/reference/streams/zero-boxing.md +275 -0
  136. package/reference/telemetry.md +693 -0
  137. package/reference/typeid.md +5 -19
  138. package/sidebars.js +238 -43
  139. package/reference/formats.md +0 -694
  140. package/reference/http-model.md +0 -1716
  141. package/reference/streams.md +0 -989
  142. package/ringbuffer.md +0 -249
  143. /package/reference/{json-differ.md → schema/built-in-codecs/json/json-differ.md} +0 -0
  144. /package/reference/{dynamic-value.md → schema/dynamic-value.md} +0 -0
  145. /package/reference/{lazy.md → schema/lazy.md} +0 -0
  146. /package/reference/{reflect.md → schema/reflect.md} +0 -0
  147. /package/reference/{registers.md → schema/registers.md} +0 -0
  148. /package/reference/{schema-error.md → schema/schema-error.md} +0 -0
  149. /package/reference/{syntax.md → schema/syntax.md} +0 -0
  150. /package/reference/{validation.md → schema/validation.md} +0 -0
@@ -0,0 +1,747 @@
1
+ ---
2
+ id: schema
3
+ title: "Schema-Based Typed Access"
4
+ ---
5
+
6
+ `zio-http-model-schema` adds **type-safe, validated extraction** of query parameters and headers to the core HTTP model. It provides extension methods on `QueryParams`, `Headers`, `Request`, and `Response` that automatically decode string values to typed objects using schema-based decoding with comprehensive error reporting.
7
+
8
+ Core features are built on **extension methods** — `QueryParamsSchemaOps`, `HeadersSchemaOps`, `RequestSchemaOps`, `ResponseSchemaOps` — which add typed, schema-based extraction to query parameters and headers. Complemented by error types `QueryParamError` and `HeaderError`, the module provides automatic decoding for 11 primitive types and extensible support for custom types via `Schema[T]`.
9
+
10
+ ## Motivation
11
+
12
+ Building HTTP handlers often requires extracting and validating query parameters or headers — "get the `userId` query parameter as a `UUID`." Without schema-based extraction, this becomes tedious and error-prone:
13
+
14
+ ```scala
15
+ import zio.http.QueryParams
16
+
17
+ // Manual extraction (error-prone, repetitive)
18
+ val params = QueryParams("userId" -> "550e8400-e29b-41d4-a716-446655440000")
19
+ val userIdStr = params.getFirst("userId")
20
+ val userId = userIdStr match {
21
+ case None => Left("Missing userId")
22
+ case Some(s) =>
23
+ try Right(java.util.UUID.fromString(s))
24
+ catch { case e: IllegalArgumentException => Left(s"Invalid UUID format: ${e.getMessage}") }
25
+ }
26
+ ```
27
+
28
+ Every parameter requires 8+ lines of boilerplate with manual exception handling, error message creation, and type-specific parsing. UUID parsing alone involves `IllegalArgumentException` handling; multiply this across dozens of handlers extracting `UUID`, `Int`, `Boolean` parameters, and you have duplicated extraction logic everywhere — inconsistent error messages, risk of forgotten error handling, and no compile-time guarantees on correctness.
29
+
30
+ The solution is to use schema-based extraction for clean, declarative code:
31
+
32
+ ```scala
33
+ import zio.http.QueryParams
34
+ import zio.http.schema._
35
+
36
+ val params = QueryParams("userId" -> "550e8400-e29b-41d4-a716-446655440000")
37
+ val userId = params.query[java.util.UUID]("userId") // 1 line, automatic UUID parsing + errors
38
+ ```
39
+
40
+ `zio-http-model-schema` separates **extraction logic from business logic**. The module achieves this through:
41
+
42
+ - **Automatic decoding** — Pass a `Schema[T]`, get `Either[Error, T]` back. Works for 11 primitive types out of the box.
43
+ - **Explicit error handling** — `Either` forces error handling. `QueryParamError` and `HeaderError` distinguish "missing" from "malformed" cases.
44
+ - **Composable** — Works directly on `QueryParams`, `Headers`, `Request`, `Response` with zero configuration.
45
+ - **Zero-dependency** — Pure extraction layer; doesn't pull in ZIO, async runtimes, or HTTP client libraries.
46
+
47
+ This keeps HTTP request handling clean, testable, and portable across different effect systems.
48
+
49
+
50
+ ## Installation
51
+
52
+ Add the following to your `build.sbt`:
53
+
54
+ ```
55
+ libraryDependencies += "dev.zio" %% "zio-http-model-schema" % "0.0.51"
56
+ ```
57
+
58
+ For cross-platform projects (Scala.js):
59
+
60
+ ```
61
+ libraryDependencies += "dev.zio" %%% "zio-http-model-schema" % "0.0.51"
62
+ ```
63
+
64
+ Supported Scala versions: Scala 3.x only. Requires `zio-http-model` and `zio-blocks-schema` as dependencies.
65
+
66
+ ## How They Work Together
67
+
68
+ To understand how the module works, we add a **schema-based extraction layer** on top of core HTTP model types:
69
+
70
+ ```
71
+ HTTP Model Types (from zio-http-model)
72
+ ├─ QueryParams: raw string key-value pairs
73
+ ├─ Headers: raw string header name-value pairs
74
+ ├─ Request: contains queryParams and headers
75
+ └─ Response: contains headers
76
+
77
+ Schema-Based Extraction (this module)
78
+ ├─ QueryParamsSchemaOps.query[T](key) ─────┐
79
+ ├─ HeadersSchemaOps.header[T](name) ───────┼──> StringDecoder.decode(raw, Schema[T])
80
+ ├─ RequestSchemaOps.query[T](key) ─────────┤ ├─> Right(typedValue)
81
+ ├─ RequestSchemaOps.header[T](name) ───────┤ └─> Left(error)
82
+ └─ ResponseSchemaOps.header[T](name) ──────┘
83
+
84
+ Typical Workflow:
85
+ 1. Parse URL or receive Request (core HTTP model)
86
+ 2. Extract queryParams or headers (access raw strings)
87
+ 3. Use schema methods to decode to typed values (this module)
88
+ 4. Handle Either[Error, T] in business logic
89
+ ```
90
+
91
+
92
+ ## Quick Showcase
93
+
94
+ Setting up and extracting query parameters with type safety:
95
+
96
+ ```scala
97
+ import zio.http.{Request, URL}
98
+ import zio.http.schema._
99
+
100
+ val url = URL.parse("/api/users?page=2&limit=50&sort=name").toOption.get
101
+ // url: URL = URL(
102
+ // scheme = None,
103
+ // host = None,
104
+ // port = None,
105
+ // path = Path(
106
+ // segments = IndexedSeq("api", "users"),
107
+ // hasLeadingSlash = true,
108
+ // trailingSlash = false
109
+ // ),
110
+ // queryParams = QueryParams((page,2), (limit,50), (sort,name)),
111
+ // fragment = None
112
+ // )
113
+ val request = Request.get(url)
114
+ // request: Request = Request(
115
+ // method = GET,
116
+ // url = URL(
117
+ // scheme = None,
118
+ // host = None,
119
+ // port = None,
120
+ // path = Path(
121
+ // segments = IndexedSeq("api", "users"),
122
+ // hasLeadingSlash = true,
123
+ // trailingSlash = false
124
+ // ),
125
+ // queryParams = QueryParams((page,2), (limit,50), (sort,name)),
126
+ // fragment = None
127
+ // ),
128
+ // headers = Headers(),
129
+ // body = Body(length=0, contentType=ContentType(MediaType(application,octet-stream,true,true,List(bin, dms, lrf, mar, so, dist, distz, pkg, bpk, dump, elc, deploy, exe, dll, deb, dmg, iso, img, msi, msp, msm, buffer),Map(),Map()),None,None)),
130
+ // version = HTTP/1.1
131
+ // )
132
+
133
+ // Extract query parameters
134
+ val pageResult = request.query[Int]("page")
135
+ // pageResult: Either[QueryParamError, Int] = Right(2)
136
+ val limitResult = request.query[Int]("limit")
137
+ // limitResult: Either[QueryParamError, Int] = Right(50)
138
+ val sortResult = request.query[String]("sort")
139
+ // sortResult: Either[QueryParamError, String] = Right("name")
140
+
141
+ // Results are properly typed and decoded
142
+ (pageResult, limitResult, sortResult)
143
+ // res4: Tuple3[Either[QueryParamError, Int], Either[QueryParamError, Int], Either[QueryParamError, String]] = (
144
+ // Right(2),
145
+ // Right(50),
146
+ // Right("name")
147
+ // )
148
+
149
+ // Handle errors with pattern matching
150
+ pageResult match {
151
+ case Right(page) => s"Page: $page"
152
+ case Left(QueryParamError.Missing(key)) => s"Missing $key"
153
+ case Left(QueryParamError.Malformed(key, value, cause)) => s"Bad $key: $cause"
154
+ }
155
+ // res5: String = "Page: 2"
156
+ ```
157
+
158
+
159
+ ## Extension Classes
160
+
161
+ ### QueryParamsSchemaOps
162
+
163
+ Extension methods for `QueryParams` to extract and decode query parameters with type safety.
164
+
165
+ #### `QueryParams#query[T]`
166
+
167
+ Extract a single query parameter value and decode it to type `T`.
168
+
169
+ **Signature:** `query[T](key: String): Either[QueryParamError, T]`
170
+
171
+ Returns `Right(value)` if parameter exists and decoding succeeds. Returns `Left(QueryParamError.Missing(key))` if parameter is missing. Returns `Left(QueryParamError.Malformed(...))` if parameter exists but decoding fails.
172
+
173
+ When a query parameter is required, use `query[T]` and handle the error:
174
+
175
+ ```scala
176
+ import zio.http.{URL}
177
+ import zio.http.schema._
178
+
179
+ val url = URL.parse("/search?q=zio").toOption.get
180
+ // url: URL = URL(
181
+ // scheme = None,
182
+ // host = None,
183
+ // port = None,
184
+ // path = Path(
185
+ // segments = IndexedSeq("search"),
186
+ // hasLeadingSlash = true,
187
+ // trailingSlash = false
188
+ // ),
189
+ // queryParams = QueryParams((q,zio)),
190
+ // fragment = None
191
+ // )
192
+ val params = url.queryParams
193
+ // params: QueryParams = QueryParams((q,zio))
194
+
195
+ params.query[String]("q") match {
196
+ case Right(q) => s"Search for: $q"
197
+ case Left(error) => s"Error: ${error.message}"
198
+ }
199
+ // res7: String = "Search for: zio"
200
+ ```
201
+
202
+
203
+ #### `QueryParams#queryAll[T]`
204
+
205
+ Extract all values for a query parameter key and decode them to type `T`.
206
+
207
+ **Signature:** `queryAll[T](key: String): Either[QueryParamError, Chunk[T]]`
208
+
209
+ Returns `Right(chunk)` with all decoded values if parameter exists and all values decode successfully. Returns `Left(QueryParamError.Missing(key))` if no values exist for the key. Returns `Left(QueryParamError.Malformed(...))` if any value fails to decode.
210
+
211
+ **Pattern: Extract Multiple Values for Same Parameter**
212
+
213
+ When a query parameter appears multiple times (e.g., `?tag=scala&tag=fp`), use `queryAll[T]`:
214
+
215
+ ```scala
216
+ import zio.http.{URL}
217
+ import zio.http.schema._
218
+
219
+ val url = URL.parse("/search?tag=scala&tag=functional&tag=zio").toOption.get
220
+ // url: URL = URL(
221
+ // scheme = None,
222
+ // host = None,
223
+ // port = None,
224
+ // path = Path(
225
+ // segments = IndexedSeq("search"),
226
+ // hasLeadingSlash = true,
227
+ // trailingSlash = false
228
+ // ),
229
+ // queryParams = QueryParams((tag,scala), (tag,functional), (tag,zio)),
230
+ // fragment = None
231
+ // )
232
+ val params = url.queryParams
233
+ // params: QueryParams = QueryParams((tag,scala), (tag,functional), (tag,zio))
234
+ ```
235
+
236
+ Extract all values for a multi-valued parameter:
237
+
238
+ ```scala
239
+ params.queryAll[String]("tag") match {
240
+ case Right(tags) => s"Tags: ${tags.toList}"
241
+ case Left(error) => s"Error: ${error.message}"
242
+ }
243
+ // res9: String = "Tags: List(scala, functional, zio)"
244
+ ```
245
+
246
+
247
+ **Short-circuit behavior:** Decoding stops at the first malformed value; only the first error is reported.
248
+
249
+ #### `QueryParams#queryOrElse[T]`
250
+
251
+ Extract a query parameter with a default fallback.
252
+
253
+ **Signature:** `queryOrElse[T](key: String, default: => T): T`
254
+
255
+ Returns the decoded value if parameter exists and decodes successfully. Returns `default` if parameter is missing or decoding fails (errors are silently ignored).
256
+
257
+ **Pattern: Extract with Default Fallback**
258
+
259
+ When a query parameter is optional with a sensible default, use `queryOrElse`:
260
+
261
+ ```scala
262
+ import zio.http.{URL}
263
+ import zio.http.schema._
264
+
265
+ val url = URL.parse("/api/items?page=2").toOption.get
266
+ // url: URL = URL(
267
+ // scheme = None,
268
+ // host = None,
269
+ // port = None,
270
+ // path = Path(
271
+ // segments = IndexedSeq("api", "items"),
272
+ // hasLeadingSlash = true,
273
+ // trailingSlash = false
274
+ // ),
275
+ // queryParams = QueryParams((page,2)),
276
+ // fragment = None
277
+ // )
278
+ val params = url.queryParams
279
+ // params: QueryParams = QueryParams((page,2))
280
+ ```
281
+
282
+ Extract with fallback defaults:
283
+
284
+ ```scala
285
+ val page = params.queryOrElse[Int]("page", 1)
286
+ // page: Int = 2
287
+ val limit = params.queryOrElse[Int]("limit", 20)
288
+ // limit: Int = 20
289
+ (page, limit)
290
+ // res11: Tuple2[Int, Int] = (2, 20)
291
+ ```
292
+
293
+
294
+ ### HeadersSchemaOps
295
+
296
+ Extension methods for `Headers` to extract and decode header values with type safety. Uses `rawGet`/`rawGetAll` internally for raw string header access. API identical to `QueryParamsSchemaOps`.
297
+
298
+ #### `Headers#header[T]`
299
+
300
+ Extract a single header value and decode it to type `T`.
301
+
302
+ **Signature:** `header[T](name: String): Either[HeaderError, T]`
303
+
304
+ Header name matching is **case-insensitive** (HTTP spec). Returns `Right(value)` on success, `Left(HeaderError.Missing(name))` if header not found, or `Left(HeaderError.Malformed(...))` if decoding fails.
305
+
306
+ Here's how to use `header[T]`:
307
+
308
+ ```scala
309
+ import zio.http.Headers
310
+ import zio.http.schema._
311
+
312
+ val headers = Headers("x-user-id" -> "42", "x-api-version" -> "2")
313
+ // headers: Headers = Headers(x-user-id: 42, x-api-version: 2)
314
+ ```
315
+
316
+ Calling `header[T]` returns an `Either` with the decoded value or an error:
317
+
318
+ ```scala
319
+ headers.header[Int]("x-user-id")
320
+ // res13: Either[HeaderError, Int] = Right(42)
321
+ ```
322
+
323
+ Header names are **case-insensitive**:
324
+
325
+ ```scala
326
+ headers.header[Int]("X-User-ID")
327
+ // res14: Either[HeaderError, Int] = Right(42)
328
+ ```
329
+
330
+ Missing headers produce a `Missing` error:
331
+
332
+ ```scala
333
+ headers.header[Int]("x-missing")
334
+ // res15: Either[HeaderError, Int] = Left(Missing("x-missing"))
335
+ ```
336
+
337
+ You can also decode with a custom `Header.Codec[A]` when the header type belongs to your domain instead of the core HTTP model:
338
+
339
+ ```scala
340
+ import zio.http.{Header, Headers}
341
+ import zio.http.schema._
342
+
343
+ object TraceIdHeader extends Header.Codec[String] {
344
+ def name: String = "x-trace-id"
345
+ def parse(value: String): Either[String, String] =
346
+ if (value.startsWith("trace-")) Right(value) else Left("trace id must start with trace-")
347
+ def render(value: String): String = value
348
+ }
349
+
350
+ val customHeaders = Headers("x-trace-id" -> "trace-123")
351
+ // customHeaders: Headers = Headers(x-trace-id: trace-123)
352
+ customHeaders.header(TraceIdHeader)
353
+ // res16: Either[HeaderError, String] = Right("trace-123")
354
+ ```
355
+
356
+
357
+ #### `Headers#headerAll[T]`
358
+
359
+ Extract all values for a header name and decode them to type `T`.
360
+
361
+ **Signature:** `headerAll[T](name: String): Either[HeaderError, Chunk[T]]`
362
+
363
+ HTTP allows multiple headers with the same name; this method collects and decodes all of them. Returns `Right(chunk)` with all decoded values, `Left(HeaderError.Missing(name))` if no headers exist for the name, or `Left(HeaderError.Malformed(...))` if any value fails to decode.
364
+
365
+ Here's how to extract multiple headers:
366
+
367
+ ```scala
368
+ import zio.http.Headers
369
+ import zio.http.schema._
370
+
371
+ val headers = Headers("x-tag" -> "scala", "x-tag" -> "functional", "x-tag" -> "zio")
372
+ // headers: Headers = Headers(x-tag: scala, x-tag: functional, x-tag: zio)
373
+ ```
374
+
375
+ Extract all values for a header:
376
+
377
+ ```scala
378
+ headers.headerAll[String]("x-tag")
379
+ // res18: Either[HeaderError, Chunk[String]] = Right(
380
+ // IndexedSeq("scala", "functional", "zio")
381
+ // )
382
+ ```
383
+
384
+ Missing headers return a `Missing` error:
385
+
386
+ ```scala
387
+ headers.headerAll[String]("x-missing")
388
+ // res19: Either[HeaderError, Chunk[String]] = Left(Missing("x-missing"))
389
+ ```
390
+
391
+ The codec-based overload works for repeated headers as well and reports the first malformed value as `HeaderError.Malformed`:
392
+
393
+ ```scala
394
+ import zio.http.{Header, Headers}
395
+ import zio.http.schema._
396
+
397
+ object TraceIdHeader extends Header.Codec[String] {
398
+ def name: String = "x-trace-id"
399
+ def parse(value: String): Either[String, String] =
400
+ if (value.startsWith("trace-")) Right(value) else Left("trace id must start with trace-")
401
+ def render(value: String): String = value
402
+ }
403
+
404
+ val repeatedHeaders = Headers("x-trace-id" -> "trace-1", "x-trace-id" -> "trace-2")
405
+ // repeatedHeaders: Headers = Headers(x-trace-id: trace-1, x-trace-id: trace-2)
406
+ repeatedHeaders.headerAll(TraceIdHeader)
407
+ // res20: Either[HeaderError, Chunk[String]] = Right(
408
+ // IndexedSeq("trace-1", "trace-2")
409
+ // )
410
+ ```
411
+
412
+
413
+ #### `Headers#headerOrElse[T]`
414
+
415
+ Extract a header with a default fallback (errors are silently ignored).
416
+
417
+ **Signature:** `headerOrElse[T](name: String, default: => T): T`
418
+
419
+ Use `headerOrElse[T]` when a header is optional with a sensible default:
420
+
421
+ ```scala
422
+ import zio.http.Headers
423
+ import zio.http.schema._
424
+
425
+ val headers = Headers("x-count" -> "5")
426
+ // headers: Headers = Headers(x-count: 5)
427
+ ```
428
+
429
+ When the header exists, it's decoded and returned:
430
+
431
+ ```scala
432
+ headers.headerOrElse[Int]("x-count", 0)
433
+ // res22: Int = 5
434
+ ```
435
+
436
+ When missing, the default is used:
437
+
438
+ ```scala
439
+ headers.headerOrElse[Int]("x-missing", 0)
440
+ // res23: Int = 0
441
+ ```
442
+
443
+
444
+ ### RequestSchemaOps
445
+
446
+ Extension methods for `Request` to extract query parameters and headers using the same schema-based API.
447
+
448
+ Exposes all methods from `QueryParamsSchemaOps` and `HeadersSchemaOps` directly on `Request` — they work identically but operate on the request object.
449
+
450
+ Query parameters and headers are extracted identically; just use `header[T]` or `headerAll[T]`:
451
+
452
+ ```scala
453
+ import zio.http.{Request, URL}
454
+ import zio.http.schema._
455
+
456
+ val request = Request.get(URL.parse("/").toOption.get)
457
+ .addHeader("x-user-id", "42")
458
+ .addHeader("x-api-version", "2")
459
+ // request: Request = Request(
460
+ // method = GET,
461
+ // url = URL(
462
+ // scheme = None,
463
+ // host = None,
464
+ // port = None,
465
+ // path = Path(
466
+ // segments = IndexedSeq(),
467
+ // hasLeadingSlash = true,
468
+ // trailingSlash = false
469
+ // ),
470
+ // queryParams = QueryParams(),
471
+ // fragment = None
472
+ // ),
473
+ // headers = Headers(x-user-id: 42, x-api-version: 2),
474
+ // body = Body(length=0, contentType=ContentType(MediaType(application,octet-stream,true,true,List(bin, dms, lrf, mar, so, dist, distz, pkg, bpk, dump, elc, deploy, exe, dll, deb, dmg, iso, img, msi, msp, msm, buffer),Map(),Map()),None,None)),
475
+ // version = HTTP/1.1
476
+ // )
477
+ ```
478
+
479
+ Extract headers from the request using the headers API:
480
+
481
+ ```scala
482
+ val userId = request.headers.header[Int]("x-user-id")
483
+ // userId: Either[HeaderError, Int] = Right(42)
484
+ val apiVersion = request.headers.headerOrElse[Int]("x-api-version", 1)
485
+ // apiVersion: Int = 2
486
+ (userId, apiVersion)
487
+ // res25: Tuple2[Either[HeaderError, Int], Int] = (Right(42), 2)
488
+ ```
489
+
490
+
491
+ ### ResponseSchemaOps
492
+
493
+ Extension methods for `Response` to extract headers using the schema-based API.
494
+
495
+ Exposes all header methods from `HeadersSchemaOps` directly on `Response` — they work identically but operate on the response object. Note: `Response` does not have `query*` methods (responses don't have query parameters).
496
+
497
+ `Response` provides the same header extraction methods:
498
+
499
+ ```scala
500
+ import zio.http.Response
501
+ import zio.http.schema._
502
+
503
+ val response = Response.ok
504
+ .addHeader("x-request-id", "req-12345")
505
+ .addHeader("x-ratelimit-remaining", "99")
506
+ // response: Response = Response(
507
+ // status = 200,
508
+ // headers = Headers(x-request-id: req-12345, x-ratelimit-remaining: 99),
509
+ // body = Body(length=0, contentType=ContentType(MediaType(application,octet-stream,true,true,List(bin, dms, lrf, mar, so, dist, distz, pkg, bpk, dump, elc, deploy, exe, dll, deb, dmg, iso, img, msi, msp, msm, buffer),Map(),Map()),None,None)),
510
+ // version = HTTP/1.1
511
+ // )
512
+ ```
513
+
514
+ Extract a header from the response using the headers API:
515
+
516
+ ```scala
517
+ response.headers.header[String]("x-request-id")
518
+ // res27: Either[HeaderError, String] = Right("req-12345")
519
+ ```
520
+
521
+ Use a default if the header is missing:
522
+
523
+ ```scala
524
+ response.headers.headerOrElse[Int]("x-ratelimit-remaining", 100)
525
+ // res28: Int = 99
526
+ ```
527
+
528
+
529
+ ## Composing Multiple Extractions
530
+
531
+ Extract multiple parameters or headers in a single operation using `Either`'s monadic operations:
532
+
533
+ ```scala
534
+ import zio.http.{Request, URL}
535
+ import zio.http.schema._
536
+
537
+ val request = Request.get(URL.parse("/api/posts?userId=5&page=2").toOption.get)
538
+ // request: Request = Request(
539
+ // method = GET,
540
+ // url = URL(
541
+ // scheme = None,
542
+ // host = None,
543
+ // port = None,
544
+ // path = Path(
545
+ // segments = IndexedSeq("api", "posts"),
546
+ // hasLeadingSlash = true,
547
+ // trailingSlash = false
548
+ // ),
549
+ // queryParams = QueryParams((userId,5), (page,2)),
550
+ // fragment = None
551
+ // ),
552
+ // headers = Headers(),
553
+ // body = Body(length=0, contentType=ContentType(MediaType(application,octet-stream,true,true,List(bin, dms, lrf, mar, so, dist, distz, pkg, bpk, dump, elc, deploy, exe, dll, deb, dmg, iso, img, msi, msp, msm, buffer),Map(),Map()),None,None)),
554
+ // version = HTTP/1.1
555
+ // )
556
+ ```
557
+
558
+ Combine multiple extractions with a for-comprehension:
559
+
560
+ ```scala
561
+ val result = for {
562
+ userId <- request.query[Int]("userId")
563
+ page <- request.query[Int]("page")
564
+ } yield (userId, page)
565
+ // result: Either[QueryParamError, Tuple2[Int, Int]] = Right((5, 2))
566
+ ```
567
+
568
+ Handle the combined result:
569
+
570
+ ```scala
571
+ result match {
572
+ case Right((userId, page)) => s"User $userId, page $page"
573
+ case Left(error) => s"Extraction failed: ${error.message}"
574
+ }
575
+ // res30: String = "User 5, page 2"
576
+ ```
577
+
578
+ The for-comprehension short-circuits on the first error, so only the first error is reported if any extraction fails. This pattern is useful when you need multiple parameters to be present and valid before proceeding with business logic.
579
+
580
+
581
+ ## Error Handling
582
+
583
+ The module provides two error types for explicit error handling: `QueryParamError` and `HeaderError`.
584
+
585
+ ### QueryParamError
586
+
587
+ Error type for query parameter extraction failures:
588
+
589
+ ```scala
590
+ sealed trait QueryParamError extends Product with Serializable {
591
+ def message: String
592
+ }
593
+
594
+ object QueryParamError {
595
+ final case class Missing(key: String) extends QueryParamError {
596
+ def message: String = s"Missing query parameter: $key"
597
+ }
598
+ final case class Malformed(key: String, value: String, cause: String) extends QueryParamError {
599
+ def message: String = s"Malformed query parameter '$key' value '$value': $cause"
600
+ }
601
+ }
602
+ ```
603
+
604
+ **Variants:**
605
+
606
+ - **`Missing(key)`** — Query parameter with name `key` is not present in the parameters
607
+ - Example: `QueryParamError.Missing("page")` when accessing a non-existent parameter
608
+ - Message: `"Missing query parameter: page"`
609
+
610
+ - **`Malformed(key, value, cause)`** — Query parameter with name `key` is present but decoding the `value` to the requested type fails
611
+ - Example: `QueryParamError.Malformed("age", "abc", "Cannot parse 'abc' as Int")` when `age=abc` but `Int` was requested
612
+ - Message: `"Malformed query parameter 'age' value 'abc': Cannot parse 'abc' as Int"`
613
+
614
+ **Accessing error messages:**
615
+
616
+ All `QueryParamError` subtypes have a `message` property for user-friendly error reporting:
617
+
618
+ ```scala
619
+ import zio.http.schema._
620
+
621
+ val error: QueryParamError = QueryParamError.Malformed("page", "invalid", "Cannot parse 'invalid' as Int")
622
+ // error: QueryParamError = Malformed(
623
+ // key = "page",
624
+ // value = "invalid",
625
+ // cause = "Cannot parse 'invalid' as Int"
626
+ // )
627
+ ```
628
+
629
+ The message provides detailed error information:
630
+
631
+ ```scala
632
+ error.message
633
+ // res33: String = "Malformed query parameter 'page' value 'invalid': Cannot parse 'invalid' as Int"
634
+ ```
635
+
636
+ ### HeaderError
637
+
638
+ Error type for header extraction failures. Structurally identical to `QueryParamError`, with `name` replacing `key` and header-specific message prefixes:
639
+
640
+ ```scala
641
+ sealed trait HeaderError extends Product with Serializable {
642
+ def message: String
643
+ }
644
+
645
+ object HeaderError {
646
+ final case class Missing(name: String) extends HeaderError {
647
+ def message: String = s"Missing header: $name"
648
+ }
649
+ final case class Malformed(name: String, value: String, cause: String) extends HeaderError {
650
+ def message: String = s"Malformed header '$name' value '$value': $cause"
651
+ }
652
+ }
653
+ ```
654
+
655
+ **Handling patterns:**
656
+
657
+ Pattern-match on error type to distinguish "missing" from "malformed":
658
+
659
+ ```scala
660
+ import zio.http.{Request, URL}
661
+ import zio.http.schema._
662
+
663
+ val request = Request.get(URL.parse("/").toOption.get)
664
+ .addHeader("x-token", "invalid-token")
665
+ // request: Request = Request(
666
+ // method = GET,
667
+ // url = URL(
668
+ // scheme = None,
669
+ // host = None,
670
+ // port = None,
671
+ // path = Path(
672
+ // segments = IndexedSeq(),
673
+ // hasLeadingSlash = true,
674
+ // trailingSlash = false
675
+ // ),
676
+ // queryParams = QueryParams(),
677
+ // fragment = None
678
+ // ),
679
+ // headers = Headers(x-token: invalid-token),
680
+ // body = Body(length=0, contentType=ContentType(MediaType(application,octet-stream,true,true,List(bin, dms, lrf, mar, so, dist, distz, pkg, bpk, dump, elc, deploy, exe, dll, deb, dmg, iso, img, msi, msp, msm, buffer),Map(),Map()),None,None)),
681
+ // version = HTTP/1.1
682
+ // )
683
+ ```
684
+
685
+ When you extract a header with the wrong type, you get a `Malformed` error:
686
+
687
+ ```scala
688
+ request.headers.header[Int]("x-token") match {
689
+ case Right(token) => s"Token: $token"
690
+ case Left(HeaderError.Missing(name)) => s"Missing required header: $name"
691
+ case Left(HeaderError.Malformed(name, value, cause)) => s"Bad header: $cause"
692
+ }
693
+ // res35: String = "Bad header: Cannot parse 'invalid-token' as Int"
694
+ ```
695
+
696
+ ## Supported Types
697
+
698
+ The module supports decoding to any type with a `Schema[T]` instance. Built-in support includes:
699
+
700
+ ### Primitives
701
+
702
+ - **`String`** — No decoding, raw string value
703
+ - **`Int`** — Parsed via `String#toInt`, error on invalid format
704
+ - **`Long`** — Parsed via `String#toLong`, error on invalid format
705
+ - **`Boolean`** — Parsed via `String#toBoolean` (case-insensitive; accepts "true"/"True"/"TRUE" → true and "false"/"False"/"FALSE" → false; any other value produces a Malformed error)
706
+ - **`Double`** — Parsed via `String#toDouble`, error on invalid format
707
+ - **`Float`** — Parsed via `String#toFloat`, error on invalid format
708
+ - **`Short`** — Parsed via `String#toShort`, error on invalid format
709
+ - **`Byte`** — Parsed via `String#toByte`, error on invalid format
710
+ - **`Char`** — Parses single character; returns a `Left` with error message `"Expected single character but got 'value'"` if string length ≠ 1 (differs from standard error pattern)
711
+
712
+ ### Big Numbers
713
+
714
+ - **`BigInt`** — Parsed via `scala.BigInt(string)`, error on invalid format
715
+ - **`BigDecimal`** — Parsed via `scala.BigDecimal(string)`, error on invalid format
716
+
717
+ ### UUID
718
+
719
+ - **`java.util.UUID`** — Parsed via `java.util.UUID.fromString(string)`, error on invalid format (must be standard UUID format: `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`)
720
+
721
+ ### Error Messages
722
+
723
+ Most decoding errors follow the pattern: `"Cannot parse 'value' as TypeName"`. Example error messages:
724
+
725
+ Here are common error message formats:
726
+
727
+ ```
728
+ Cannot parse 'abc' as Int
729
+ Cannot parse 'notaboolean' as Boolean
730
+ Cannot parse 'not-a-uuid' as UUID
731
+ Cannot parse '12.34.56' as BigDecimal
732
+ ```
733
+
734
+ **Exception:** `Char` parsing uses a different error message format:
735
+
736
+ ```
737
+ Expected single character but got 'multichar'
738
+ Expected single character but got ''
739
+ ```
740
+
741
+ ### Custom Types
742
+
743
+ To support custom types, provide a `Schema[T]` instance. The module automatically uses the schema's primitive type information via `StringDecoder`. For case classes or other compound types, manually create a `Schema[T]` using the schema module's derivation tools or manual construction.
744
+
745
+ ## See Also
746
+
747
+ - [Combinators](../combinators.md) — Systematically compose and canonicalize Either types for uniform error handling across multiple query parameter or header extractions. The `Eithers` combinator canonicalizes nested Either types, making error accumulation consistent.