@zio.dev/zio-blocks 0.0.33 → 0.0.55

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