@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,522 @@
1
+ ---
2
+ id: schema-codecs
3
+ title: "HeaderCodec and QueryCodec"
4
+ sidebar_label: "Codecs"
5
+ ---
6
+
7
+ `HeaderCodec[A]` and `QueryCodec[A]` encode a whole value of type `A` into `Headers` or `QueryParams` and decode it back. Both are derived from `Schema[A]` through a format — `DefaultHeaderFormat` or `DefaultQueryFormat` — which pairs a MIME type with the `Deriver` that builds instances. The types involved:
8
+
9
+ ```scala
10
+ abstract class HeaderCodec[A] extends Codec[Headers, HeadersBuilder, A] {
11
+ def encodeToHeaders(value: A): Headers
12
+ }
13
+
14
+ abstract class QueryCodec[A] extends Codec[QueryParams, QueryParamsBuilder, A] {
15
+ def encodeToQueryParams(value: A): QueryParams
16
+ }
17
+
18
+ abstract class HeaderFormat[TC[A] <: HeaderCodec[A]](val mimeType: String, val deriver: Deriver[TC])
19
+ abstract class QueryFormat[TC[A] <: QueryCodec[A]](val mimeType: String, val deriver: Deriver[TC])
20
+ ```
21
+
22
+ ## Motivation
23
+
24
+ The extension classes on [the schema page](./schema.md) read one field at a time: `request.query[Int]("page")` pulls a single parameter and gives you an `Either`. That is the right shape when a handler wants two or three values out of a request.
25
+
26
+ It is the wrong shape when the request *is* a value. A search endpoint taking eight query parameters becomes eight extractions, eight error branches, and a constructor call that has to be kept in step with all of them. Adding a parameter means touching four places.
27
+
28
+ A codec collapses that to one call. Define the case class, derive a `QueryCodec` from its schema, and `QueryCodec#decode` gives you `Either[SchemaError, SearchRequest]` for the whole thing. Adding a field to the case class adds a parameter, with no other change — the same bargain `Schema` makes everywhere else in the library.
29
+
30
+ The two directions are symmetric, which matters for clients as much as servers: the same codec that decodes an incoming request encodes an outgoing one.
31
+
32
+ ## Quick Showcase
33
+
34
+ Derive a codec from a schema, then round-trip a value through it:
35
+
36
+ ```scala
37
+ import zio.blocks.schema.Schema
38
+ import zio.http.schema._
39
+
40
+ final case class Search(page: Int, term: String, active: Boolean)
41
+
42
+ object Search {
43
+ implicit val schema: Schema[Search] = Schema.derived[Search]
44
+ }
45
+
46
+ val codec = Schema[Search].derive(DefaultQueryFormat)
47
+ ```
48
+
49
+ Encoding produces one parameter per field, named after the field:
50
+
51
+ ```scala
52
+ codec.encodeToQueryParams(Search(2, "boots", active = true))
53
+ // res0: QueryParams = QueryParams((page,2), (term,boots), (active,true))
54
+ ```
55
+
56
+ Decoding is the inverse, and reports a `SchemaError` rather than throwing:
57
+
58
+ ```scala
59
+ codec.decode(codec.encodeToQueryParams(Search(2, "boots", active = true)))
60
+ // res1: Either[SchemaError, Search] = Right(
61
+ // Search(page = 2, term = "boots", active = true)
62
+ // )
63
+ ```
64
+
65
+ ## Deriving a Codec
66
+
67
+ Both codecs are derived the same way: call `Schema#derive` with the format object. The implicit `Derivable` that each format supplies is what lets a format stand in for its deriver:
68
+
69
+ ```scala
70
+ trait Schema[A] {
71
+ def derive[D, TC[_]](d: D)(implicit ev: Derivable[D, TC]): TC[A]
72
+ def deriving[TC[_]](deriver: Deriver[TC]): DerivationBuilder[TC, A]
73
+ }
74
+ ```
75
+
76
+ `DefaultQueryFormat` produces a `QueryCodec`, and `DefaultHeaderFormat` produces a `HeaderCodec`:
77
+
78
+ ```scala
79
+ import zio.blocks.schema.Schema
80
+ import zio.http.schema._
81
+
82
+ final case class Trace(traceId: String, apiKey: String)
83
+
84
+ object Trace {
85
+ implicit val schema: Schema[Trace] = Schema.derived[Trace]
86
+ }
87
+
88
+ val headerCodec = Schema[Trace].derive(DefaultHeaderFormat)
89
+ ```
90
+
91
+ The derived codec carries the format's MIME type, which is what an endpoint description uses to advertise the wire shape:
92
+
93
+ ```scala
94
+ DefaultHeaderFormat.mimeType
95
+ // res3: String = "application/http-headers"
96
+ DefaultQueryFormat.mimeType
97
+ // res4: String = "application/x-www-form-urlencoded"
98
+ ```
99
+
100
+ ## Encoding and Decoding
101
+
102
+ Each codec has a convenience encoder that returns the finished collection, plus the `Codec#encode` and `Codec#decode` methods it inherits.
103
+
104
+ ### `HeaderCodec#encodeToHeaders` and `QueryCodec#encodeToQueryParams`
105
+
106
+ These build and return the collection directly, which is what application code normally wants:
107
+
108
+ ```scala
109
+ abstract class HeaderCodec[A] {
110
+ def encodeToHeaders(value: A): Headers
111
+ }
112
+ ```
113
+
114
+ Encoding a record yields one entry per field:
115
+
116
+ ```scala
117
+ headerCodec.encodeToHeaders(Trace("trace-1", "secret"))
118
+ // res5: Headers = Headers(trace-id: trace-1, api-key: secret)
119
+ ```
120
+
121
+ :::warning[The builder is a reused thread-local]
122
+ `HeaderCodec#encodeToHeaders` and `QueryCodec#encodeToQueryParams` borrow a per-thread builder, reset it, fill it, and snapshot it. The returned collection is safe to keep, but the builder is not reentrant: if a custom codec's `Codec#encode` calls `HeaderCodec#encodeToHeaders` again on the same thread, the inner call resets the buffer the outer one was filling. Inside a custom `Codec#encode`, write to the `output` builder you were handed rather than calling the convenience encoder.
123
+ :::
124
+
125
+ ### `Codec#encode` and `Codec#decode`
126
+
127
+ `Codec#encode` writes into a builder you supply, which is how nested and composed codecs avoid intermediate collections. `Codec#decode` reads a whole collection:
128
+
129
+ ```scala
130
+ abstract class QueryCodec[A] {
131
+ def encode(value: A, output: QueryParamsBuilder): Unit
132
+ def decode(input: QueryParams): Either[SchemaError, A]
133
+ }
134
+ ```
135
+
136
+ A decode failure names the field and what was expected:
137
+
138
+ ```scala
139
+ import zio.blocks.schema.Schema
140
+ import zio.http.schema._
141
+ import zio.http.QueryParams
142
+
143
+ final case class Search(page: Int, term: String)
144
+
145
+ object Search {
146
+ implicit val schema: Schema[Search] = Schema.derived[Search]
147
+ }
148
+
149
+ val codec = Schema[Search].derive(DefaultQueryFormat)
150
+ ```
151
+
152
+ A missing parameter and a malformed one are both reported as a `SchemaError`, not an exception:
153
+
154
+ ```scala
155
+ codec.decode(QueryParams("term" -> "boots"))
156
+ // res7: Either[SchemaError, Search] = Left(
157
+ // SchemaError(
158
+ // List(MissingField(source = DynamicOptic(ArraySeq()), fieldName = "page"))
159
+ // )
160
+ // )
161
+ codec.decode(QueryParams("page" -> "not-a-number", "term" -> "boots"))
162
+ // res8: Either[SchemaError, Search] = Left(
163
+ // SchemaError(
164
+ // List(
165
+ // ConversionFailed(
166
+ // source = DynamicOptic(ArraySeq()),
167
+ // details = "Malformed query parameter 'page' value 'not-a-number': Cannot parse 'not-a-number' as Int",
168
+ // cause = None
169
+ // )
170
+ // )
171
+ // )
172
+ // )
173
+ ```
174
+
175
+ ## Field Naming
176
+
177
+ The two codecs disagree about names, and this is the single most important thing to know about them.
178
+
179
+ | | Field `traceId` becomes | Decoding |
180
+ | ------------- | ----------------------- | ---------------- |
181
+ | `QueryCodec` | `traceId` — verbatim | exact match |
182
+ | `HeaderCodec` | `trace-id` — kebab-case | case-insensitive |
183
+
184
+ `QueryCodec` uses the Scala field name unchanged, because query parameters are conventionally camelCase or snake_case and the schema's name is as good a guess as any.
185
+
186
+ `HeaderCodec` rewrites camelCase into kebab-case, because that is the conventional shape of an HTTP header name. Two steps are involved: the deriver emits `TRACE-ID`, uppercasing every character and inserting a hyphen before each interior capital, and then `Headers` lowercases every name as it stores it. The second step makes the first unobservable, so what you get back is `trace-id`:
187
+
188
+ ```scala
189
+ import zio.blocks.schema.Schema
190
+ import zio.http.schema._
191
+ import zio.http.Headers
192
+
193
+ final case class Meta(traceId: String, bigIntValue: BigInt, uuidValue: java.util.UUID)
194
+
195
+ object Meta {
196
+ implicit val schema: Schema[Meta] = Schema.derived[Meta]
197
+ }
198
+
199
+ val codec = Schema[Meta].derive(DefaultHeaderFormat)
200
+ val uuid = java.util.UUID.fromString("123e4567-e89b-12d3-a456-426614174000")
201
+ ```
202
+
203
+ Every camelCase boundary becomes a hyphen, so `bigIntValue` becomes three segments rather than two:
204
+
205
+ ```scala
206
+ codec.encodeToHeaders(Meta("trace-1", BigInt(42), uuid)).toList.map(_._1)
207
+ // res10: List[String] = List("trace-id", "big-int-value", "uuid-value")
208
+ ```
209
+
210
+ Decoding ignores case, so a client that sends `trace-id` or `TRACE-ID` still decodes:
211
+
212
+ ```scala
213
+ codec.decode(Headers("trace-id" -> "t", "big-int-value" -> "42", "UUID-VALUE" -> uuid.toString))
214
+ // res11: Either[SchemaError, Meta] = Right(
215
+ // Meta(
216
+ // traceId = "t",
217
+ // bigIntValue = 42,
218
+ // uuidValue = 123e4567-e89b-12d3-a456-426614174000
219
+ // )
220
+ // )
221
+ ```
222
+
223
+ :::warning[Names are not configurable]
224
+ Neither codec offers a name mapper. The transformation is fixed, so a header or parameter whose wire name does not follow the convention cannot be reached by renaming the field — you need a custom codec instance for that type, or the field-at-a-time extraction on [the schema page](./schema.md).
225
+ :::
226
+
227
+ ## Supported Field Shapes
228
+
229
+ Four shapes are supported per field, and they compose one level deep.
230
+
231
+ ### Primitives
232
+
233
+ Each primitive field is one entry, rendered as its string form:
234
+
235
+ | Scala type | Example wire value |
236
+ | --- | --- |
237
+ | `String` | `alice` |
238
+ | `Boolean` | `true` |
239
+ | `Byte`, `Short`, `Int`, `Long` | `42` |
240
+ | `Float`, `Double` | `12.34` |
241
+ | `BigInt`, `BigDecimal` | `1234567890123456789` |
242
+ | `Char` | `z` — exactly one character |
243
+ | `UUID` | `123e4567-e89b-12d3-a456-426614174000` |
244
+
245
+ A `Char` field with more than one character fails to decode, and the message says so:
246
+
247
+ ```scala
248
+ import zio.blocks.schema.Schema
249
+ import zio.http.schema._
250
+ import zio.http.QueryParams
251
+
252
+ val charCodec = Schema[Char].derive(DefaultQueryFormat)
253
+ ```
254
+
255
+ The error names the expectation rather than the underlying exception:
256
+
257
+ ```scala
258
+ charCodec.decode(QueryParams("value" -> "too-long"))
259
+ // res13: Either[SchemaError, Char] = Left(
260
+ // SchemaError(
261
+ // List(
262
+ // ConversionFailed(
263
+ // source = DynamicOptic(ArraySeq()),
264
+ // details = "Malformed query parameter 'value' value 'too-long': Expected single character but got 'too-long'",
265
+ // cause = None
266
+ // )
267
+ // )
268
+ // )
269
+ // )
270
+ ```
271
+
272
+ ### Optional Fields
273
+
274
+ An `Option[A]` field omits its key entirely when `None`, and an absent key decodes back to `None`:
275
+
276
+ ```scala
277
+ import zio.blocks.schema.Schema
278
+ import zio.http.schema._
279
+ import zio.http.QueryParams
280
+
281
+ final case class Filters(page: Option[Int], term: Option[String])
282
+
283
+ object Filters {
284
+ implicit val schema: Schema[Filters] = Schema.derived[Filters]
285
+ }
286
+
287
+ val codec = Schema[Filters].derive(DefaultQueryFormat)
288
+ ```
289
+
290
+ Encoding drops the absent field rather than emitting an empty value:
291
+
292
+ ```scala
293
+ codec.encodeToQueryParams(Filters(Some(7), None))
294
+ // res15: QueryParams = QueryParams((page,7))
295
+ ```
296
+
297
+ An empty collection decodes to all-`None`, so a request with no parameters is valid rather than an error:
298
+
299
+ ```scala
300
+ codec.decode(QueryParams.empty)
301
+ // res16: Either[SchemaError, Filters] = Right(
302
+ // Filters(page = None, term = None)
303
+ // )
304
+ ```
305
+
306
+ ### Sequences
307
+
308
+ A `List`, `Chunk`, or other sequence field becomes **repeated entries under one key**, not a delimited single value:
309
+
310
+ ```scala
311
+ import zio.blocks.schema.Schema
312
+ import zio.blocks.chunk.Chunk
313
+ import zio.http.schema._
314
+
315
+ final case class Selection(tags: List[String], ids: Chunk[Int])
316
+
317
+ object Selection {
318
+ implicit val schema: Schema[Selection] = Schema.derived[Selection]
319
+ }
320
+
321
+ val codec = Schema[Selection].derive(DefaultQueryFormat)
322
+ ```
323
+
324
+ Two tags and three ids produce five parameters:
325
+
326
+ ```scala
327
+ codec.encodeToQueryParams(Selection(List("a", "b"), Chunk(1, 2, 3)))
328
+ // res18: QueryParams = QueryParams((tags,a), (tags,b), (ids,1), (ids,2), (ids,3))
329
+ ```
330
+
331
+ Decoding collects them back into the declared collection type, preserving order:
332
+
333
+ ```scala
334
+ codec.decode(codec.encodeToQueryParams(Selection(List("a", "b"), Chunk(1, 2, 3))))
335
+ // res19: Either[SchemaError, Selection] = Right(
336
+ // Selection(tags = List("a", "b"), ids = IndexedSeq(1, 2, 3))
337
+ // )
338
+ ```
339
+
340
+ Because there is no delimiter, a value containing a comma round-trips correctly — the repeated-key encoding has no escaping problem to solve.
341
+
342
+ ### Wrapper Types
343
+
344
+ A newtype built with `Schema#transform` encodes as its underlying value, so the wrapper is invisible on the wire:
345
+
346
+ ```scala
347
+ import zio.blocks.schema.Schema
348
+ import zio.http.schema._
349
+
350
+ final case class UserId(value: String)
351
+
352
+ object UserId {
353
+ implicit val schema: Schema[UserId] = Schema[String].transform(UserId(_), _.value)
354
+ }
355
+
356
+ final case class Owned(id: UserId)
357
+
358
+ object Owned {
359
+ implicit val schema: Schema[Owned] = Schema.derived[Owned]
360
+ }
361
+
362
+ val codec = Schema[Owned].derive(DefaultQueryFormat)
363
+ ```
364
+
365
+ The parameter holds the wrapped string, with no trace of the wrapper:
366
+
367
+ ```scala
368
+ codec.encodeToQueryParams(Owned(UserId("user-1")))
369
+ // res21: QueryParams = QueryParams((id,user-1))
370
+ ```
371
+
372
+ If the wrapping function throws — a validating newtype rejecting its input — the failure surfaces as a `SchemaError` from `QueryCodec#decode` rather than as an exception.
373
+
374
+ ## Top-Level Codecs
375
+
376
+ A schema that is not a record also derives, and the resulting codec uses the single key `value`:
377
+
378
+ ```scala
379
+ import zio.blocks.schema.Schema
380
+ import zio.http.schema._
381
+ import zio.http.{Headers, QueryParams}
382
+
383
+ val intCodec = Schema[Int].derive(DefaultQueryFormat)
384
+ val listCodec = Schema[List[Int]].derive(DefaultQueryFormat)
385
+ ```
386
+
387
+ A top-level primitive is one parameter, and a top-level sequence is repeated parameters, both under `value`:
388
+
389
+ ```scala
390
+ intCodec.encodeToQueryParams(42)
391
+ // res23: QueryParams = QueryParams((value,42))
392
+ listCodec.encodeToQueryParams(List(1, 2))
393
+ // res24: QueryParams = QueryParams((value,1), (value,2))
394
+ ```
395
+
396
+ `HeaderCodec` applies its naming rule here too, so the single-word name comes back as `value` and is matched case-insensitively on the way in:
397
+
398
+ ```scala
399
+ val headerIntCodec = Schema[Int].derive(DefaultHeaderFormat)
400
+ // headerIntCodec: HeaderCodec[Int] = zio.http.schema.HeaderCodecDeriver$$anon$3@1ceeab16
401
+ headerIntCodec.encodeToHeaders(42).toList
402
+ // res25: List[Tuple2[String, String]] = List(("value", "42"))
403
+ headerIntCodec.decode(Headers("value" -> "42"))
404
+ // res26: Either[SchemaError, Int] = Right(42)
405
+ ```
406
+
407
+ ## Unsupported Shapes
408
+
409
+ Three shapes have no encoding, and one of them is a trap.
410
+
411
+ **Nested records** are rejected during derivation, because a flat namespace of names has no way to express a field whose value is itself a record. Use a flat case class, or extract the nested part separately.
412
+
413
+ **Top-level `Option`, `Map`, and `DynamicValue`** derive without complaint and then **throw when you encode**:
414
+
415
+ ```scala
416
+ import zio.blocks.schema.Schema
417
+ import zio.http.schema._
418
+
419
+ val optionCodec = Schema[Option[Int]].derive(DefaultQueryFormat)
420
+ ```
421
+
422
+ Derivation succeeds, so nothing warns you at the point where the mistake was made:
423
+
424
+ ```scala
425
+ scala.util.Try(optionCodec.encodeToQueryParams(Some(1))).isFailure
426
+ // res28: Boolean = true
427
+ ```
428
+
429
+ :::danger[Failure is deferred to the first encode]
430
+ `Schema[Option[A]]`, `Schema[Map[K, V]]`, and `Schema[DynamicValue]` all produce a codec that throws on use. Because derivation is where the error belongs and is not where it appears, a codec built at startup can look healthy until the first request that encodes through it. Derive and exercise these codecs in a test rather than trusting that construction succeeded.
431
+ :::
432
+
433
+ A `Map` field inside a record is likewise unsupported. Model the entries you expect as fields, or take the raw collection and read it with the field-at-a-time API.
434
+
435
+ ## Formats
436
+
437
+ A format is the pairing of a MIME type with a `Deriver`. `HeaderFormat` and `QueryFormat` are the base classes, and each provides an implicit `Derivable` so that `Schema#derive` accepts the format object directly:
438
+
439
+ ```scala
440
+ abstract class HeaderFormat[TC[A] <: HeaderCodec[A]](val mimeType: String, val deriver: Deriver[TC]) {
441
+ implicit def derivable: Derivable[this.type, TC]
442
+ }
443
+ ```
444
+
445
+ The two built-in formats are the only ones needed for the default wire shapes:
446
+
447
+ | Format | MIME type | Deriver |
448
+ | --- | --- | --- |
449
+ | `DefaultHeaderFormat` | `application/http-headers` | `HeaderCodecDeriver` |
450
+ | `DefaultQueryFormat` | `application/x-www-form-urlencoded` | `QueryCodecDeriver` |
451
+
452
+ Both are `case object`s extending a sealed abstract class, so they are singletons and can be matched on.
453
+
454
+ ### Defining a Custom Format
455
+
456
+ Subclass `QueryFormat` or `HeaderFormat` when you want a different MIME type reported for the same derivation strategy, or a different deriver entirely:
457
+
458
+ ```scala
459
+ import zio.http.schema._
460
+
461
+ case object FormUrlEncoded extends QueryFormat[QueryCodec]("application/x-www-form-urlencoded; charset=utf-8", QueryCodecDeriver)
462
+ ```
463
+
464
+ The `TC` parameter is the codec type the deriver produces, bounded by `QueryCodec` so that the convenience encoder stays available on whatever it derives.
465
+
466
+ ## Overriding a Single Type
467
+
468
+ `HeaderCodecDeriver` and `QueryCodecDeriver` are ordinary `Deriver` instances, so the schema module's override mechanism applies. Supply a hand-written codec for one type and let derivation handle the rest:
469
+
470
+ ```scala
471
+ import zio.blocks.schema.{Schema, SchemaError}
472
+ import zio.blocks.typeid.TypeId
473
+ import zio.http.schema._
474
+ import zio.http.{QueryParams, QueryParamsBuilder}
475
+
476
+ val prefixedInt = new QueryCodec[Int] {
477
+ def encode(value: Int, output: QueryParamsBuilder): Unit =
478
+ output.add("value", s"custom-$value")
479
+
480
+ def decode(input: QueryParams): Either[SchemaError, Int] =
481
+ input.getFirst("value") match {
482
+ case Some(s) if s.startsWith("custom-") => Right(s.stripPrefix("custom-").toInt)
483
+ case other => Left(SchemaError(s"Expected custom- prefix, got: $other"))
484
+ }
485
+ }
486
+
487
+ val codec = Schema[Int].deriving(QueryCodecDeriver).instance(TypeId.int, prefixedInt).derive
488
+ ```
489
+
490
+ The override applies wherever that type appears, so the custom rendering is used on both sides:
491
+
492
+ ```scala
493
+ codec.encodeToQueryParams(42)
494
+ // res31: QueryParams = QueryParams((value,custom-42))
495
+ codec.decode(codec.encodeToQueryParams(42))
496
+ // res32: Either[SchemaError, Int] = Right(42)
497
+ ```
498
+
499
+ Note that `Codec#encode` writes into the supplied builder rather than returning a collection — that is the method to implement, and the convenience encoder is derived from it.
500
+
501
+ ## Choosing Between the Two APIs
502
+
503
+ Both this page's codecs and [the schema page](./schema.md)'s extension classes read the same collections. They differ in granularity:
504
+
505
+ | | Codecs | Extension classes |
506
+ | --- | --- | --- |
507
+ | Unit of work | Whole value | One field |
508
+ | Result | `Either[SchemaError, A]` | `Either[QueryParamError, T]` / `Either[HeaderError, T]` |
509
+ | Encoding | Yes, symmetric with decoding | Decoding only |
510
+ | Names | Fixed by the naming rule | You pass the name |
511
+ | Nested records | Unsupported | Not applicable |
512
+ | Best for | A request or response that *is* a value | Pulling two or three values out |
513
+
514
+ Use a codec when the collection maps onto a type you already have. Use the extension classes when it does not, when you need a name the codec cannot produce, or when you want per-field error handling.
515
+
516
+ ## Integration Points
517
+
518
+ `HeaderCodec` and `QueryCodec` extend `Codec[Out, Builder, A]` from `zio-blocks-schema`, so they participate in the same derivation machinery as every other codec in the library — the `Deriver`, `Derivable`, and instance-override APIs are the schema module's, not this module's.
519
+
520
+ On the HTTP side they produce and consume `Headers`, `HeadersBuilder`, `QueryParams`, and `QueryParamsBuilder` from `zio-blocks-http-model`; see [the HTTP model](./model.md) for those collections and [Header](./headers.md) for the typed single-header model, which is a different mechanism from the whole-record codecs here.
521
+
522
+ Errors are `SchemaError` from the schema module rather than this module's `HeaderError` and `QueryParamError`, which belong to the field-at-a-time API on [the schema page](./schema.md).