@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,1517 @@
1
+ ---
2
+ id: model
3
+ title: "HTTP Model"
4
+ ---
5
+
6
+ `zio-http-model` is a **runtime-independent HTTP data model** for building HTTP clients and servers. It provides immutable types representing all HTTP concepts: requests, responses, headers, URLs, paths, query parameters, methods, status codes, versions, cookies, and forms.
7
+
8
+ Core types: `Request`, `Response`, `URL`, `Headers`, `Body`, `Method`, `Status`, `Version`, `Scheme`, `Path`, `QueryParams`, `ContentType`, `RequestCookie`, `ResponseCookie`, `Form`.
9
+
10
+ Here is a high-level overview of the core types and their relationships:
11
+
12
+ ```scala
13
+ // Core request/response types
14
+ final case class Request(method: Method, url: URL, headers: Headers, body: Body, version: Version)
15
+ final case class Response(status: Status, headers: Headers, body: Body, version: Version)
16
+
17
+ // URL structure
18
+ final case class URL(scheme: Option[Scheme], host: Option[String], port: Option[Int],
19
+ path: Path, queryParams: QueryParams, fragment: Option[String])
20
+ final case class Path(segments: Chunk[String], hasLeadingSlash: Boolean, trailingSlash: Boolean)
21
+
22
+ // HTTP primitives
23
+ sealed abstract class Method(val name: String, val ordinal: Int)
24
+ opaque type Status = Int
25
+ sealed abstract class Version(val major: Int, val minor: Int)
26
+ sealed trait Scheme
27
+
28
+ // Headers and body
29
+ final class Headers(...)
30
+ sealed trait Header
31
+ final class Body(val stream: Stream[Nothing, Byte], val contentType: ContentType)
32
+ ```
33
+
34
+ ## Motivation
35
+
36
+ Building modern distributed systems requires HTTP clients and servers, but most HTTP libraries bake effects (I/O, streaming, async) directly into their data types. This creates coupling problems when sharing types across layers or testing without pulling in entire runtimes.
37
+
38
+ ### The Problem: Protocol, Effects, and Coupling
39
+
40
+ Imagine building a distributed system where you need an HTTP client to call external APIs and an HTTP server to handle incoming requests. Your first instinct is to reach for a popular HTTP library. But here's the trouble: most HTTP libraries bake "effects" (I/O operations, streaming, async) directly into their data structures.
41
+
42
+ This creates a coupling problem:
43
+
44
+ **Scenario 1: Sharing Types Across Layers**
45
+ You want your client request logic (building a request to send) to use the same types as your server request handling (receiving and parsing a request). But your HTTP library makes this difficult — the `Request` type is tied to a particular runtime, file-stream implementation, or async framework. Sharing becomes messy.
46
+
47
+ **Scenario 2: Testing Without an HTTP Runtime**
48
+ You're writing unit tests for your request-building logic. You want to serialize a request to JSON for snapshots, or cache requests for debugging. But your `Request` type requires pulling in an HTTP runtime or other baggage you don't need in tests. A simple unit test becomes a production-grade setup.
49
+
50
+ **Scenario 3: Lock-In**
51
+ You've built your entire API client around ZIO's HTTP library, but your team decides to use Akka for one microservice. Now your request/response types aren't portable — they're coupled to ZIO. Refactoring is painful.
52
+
53
+ ### The Solution: Runtime-Independent HTTP Data
54
+
55
+ `zio-http-model` separates **protocol concerns** (representing HTTP messages) from **effect concerns** (actually sending/receiving them). It provides:
56
+
57
+ - **Immutable message values** — `Request`, `Response`, `URL`, and `Headers` are immutable data. `Body` wraps a pull-based byte stream; materializing the complete body is represented by the lightweight `Async` abstraction.
58
+
59
+ - **No dependency on an HTTP runtime** — Not coupled to ZIO HTTP, Akka, or a server/client implementation. The module depends on ZIO Blocks primitives such as Chunk, MediaType, and Stream.
60
+
61
+ - **Incremental, lazy parsing** — Headers are parsed on first access and cached as an optimization. You pay only for what you use.
62
+
63
+ - **Efficient, composable** — Headers and QueryParams use parallel array-backed collections for fast lookups. Types compose naturally without forcing a single architectural path.
64
+
65
+ This separation is powerful: you can build, manipulate, serialize, and test HTTP messages using pure data, then hand them off to any HTTP client/server library (ZIO, Akka, Play, etc.) for the actual I/O work. Your domain logic stays portable and testable.
66
+
67
+
68
+ ## Installation
69
+
70
+ Add the following to your `build.sbt`:
71
+
72
+ ```scala
73
+ libraryDependencies += "dev.zio" %% "zio-http-model" % "0.0.55"
74
+ ```
75
+
76
+ For cross-platform projects (Scala.js):
77
+
78
+ ```scala
79
+ libraryDependencies += "dev.zio" %%% "zio-http-model" % "0.0.55"
80
+ ```
81
+
82
+ Supported Scala versions: Scala 2.13 and Scala 3.x. The artifacts are cross-platform for JVM and Scala.js.
83
+
84
+ ## How They Work Together
85
+
86
+ The HTTP model consists of **request/response messages** at the center, composed of smaller types that handle specific concerns:
87
+
88
+ ```
89
+ Request ────────────────────────────────────────────────────
90
+ ├─ method: Method (HTTP verb: GET, POST, PUT, DELETE, etc.)
91
+ ├─ url: URL (target endpoint)
92
+ │ ├─ scheme: Option[Scheme] (HTTP, HTTPS, WS, WSS, Custom)
93
+ │ ├─ host: Option[String] (domain name)
94
+ │ ├─ port: Option[Int] (port number)
95
+ │ ├─ path: Path (URL path segments)
96
+ │ ├─ queryParams: QueryParams (query string parameters)
97
+ │ └─ fragment: Option[String] (anchor)
98
+ ├─ headers: Headers (HTTP headers, lazily parsed for typed access)
99
+ │ └─ Header (individual headers: Authorization, Content-Type, etc.)
100
+ ├─ body: Body (message content)
101
+ │ └─ contentType: ContentType (media type, charset, boundary)
102
+ └─ version: Version (HTTP/1.0, HTTP/1.1, HTTP/2.0, HTTP/3.0)
103
+
104
+ Response ────────────────────────────────────────────────────
105
+ ├─ status: Status (HTTP status code: 200, 404, 500, etc.)
106
+ ├─ headers: Headers (same as Request)
107
+ ├─ body: Body (response content)
108
+ └─ version: Version (HTTP protocol version)
109
+ ```
110
+
111
+ These types don't exist in isolation — they work together as you build requests and handle responses. The hierarchy above shows the structure, but understanding the actual workflow reveals why each piece is important. Here is the typical flow when using the HTTP model in a client or server:
112
+
113
+ 1. **Build URL** — Parse or construct a URL with path segments and query parameters
114
+ 2. **Create Request** — Combine method, URL, headers, and body into a Request
115
+ 3. **Send** — Transmit the request (handled by HTTP client/server library, not http-model)
116
+ 4. **Receive Response** — Parse incoming Response with status, headers, and body
117
+ 5. **Access Data** — Extract typed headers, cookies, content type from Headers
118
+ 6. **Parse Content** — Body contains raw bytes; higher-level libraries deserialize based on content type
119
+
120
+ ## Common Patterns
121
+
122
+ ### Building Requests with Headers and Query Parameters
123
+
124
+ Create a request with headers and query parameters:
125
+
126
+ ```scala
127
+ import zio.http._
128
+
129
+ val url = URL.parse("https://api.example.com/users?role=admin").toOption.get
130
+ val req = Request
131
+ .get(url)
132
+ .addHeader("authorization", "Bearer token123")
133
+ .addHeader("content-type", "application/json")
134
+ ```
135
+
136
+ ### Extracting and Manipulating Headers
137
+
138
+ Access headers by name or retrieve all headers:
139
+
140
+ ```scala
141
+ import zio.http._
142
+
143
+ // Accessing headers in a request
144
+ val url = URL.parse("https://example.com").toOption.get
145
+ val req = Request.get(url).addHeader("content-type", "application/json")
146
+
147
+ val allHeaders = req.headers.toList // All headers as List[(String, String)]
148
+ val headerCount = allHeaders.length // Count headers
149
+ val contentType = req.headers.rawGet("content-type") // Get raw string header value (case-insensitive lookup)
150
+ ```
151
+
152
+ Custom typed headers do not need their own `Header` subtype. Define a `Header.Codec[A]` and reuse it with `Headers`, `Request`, and `Response`:
153
+
154
+ ```scala
155
+ import zio.http._
156
+
157
+ object TraceIdHeader extends Header.Codec[String] {
158
+ def name: String = "x-trace-id"
159
+ def parse(value: String): Either[String, String] =
160
+ if (value.startsWith("trace-")) Right(value) else Left("trace id must start with trace-")
161
+ def render(value: String): String = value
162
+ }
163
+
164
+ val request = Request
165
+ .get(URL.parse("https://example.com").toOption.get)
166
+ .addHeader("x-trace-id", "trace-123")
167
+
168
+ val traceId = request.header(TraceIdHeader)
169
+ ```
170
+
171
+ ### Creating Responses with Status and Content
172
+
173
+ Build a JSON response:
174
+
175
+ ```scala
176
+ import zio.http._
177
+
178
+ val body = Body.fromString("""{"message":"ok"}""", Charset.UTF8)
179
+ val response = Response(
180
+ status = Status.Ok,
181
+ body = body,
182
+ headers = Headers("content-type" -> "application/json"),
183
+ version = Version.`HTTP/1.1`
184
+ )
185
+ ```
186
+
187
+ ### URL Manipulation
188
+
189
+ Parse URLs and extract components:
190
+
191
+ ```scala
192
+ import zio.http._
193
+
194
+ val url = URL.parse("https://example.com/api/v1/users?page=1&limit=10").toOption.get
195
+
196
+ url.scheme // Some(Scheme.HTTPS)
197
+ url.host // Some("example.com")
198
+ url.port // None (defaults to 443 for HTTPS)
199
+ url.path.segments // Chunk("api", "v1", "users")
200
+ url.queryParams.getFirst("page") // Some("1") (first value for key)
201
+ url.??("filter", "active") // Add parameter using ?? operator
202
+ ```
203
+
204
+ ### Form Data Submission
205
+
206
+ Submit a form with key-value pairs:
207
+
208
+ ```scala
209
+ import zio.http._
210
+
211
+ val url = URL.parse("https://example.com/login").toOption.get
212
+ val form = Form("username" -> "alice", "password" -> "secret123", "remember" -> "true")
213
+ val body = Body.fromString(form.encode, Charset.UTF8)
214
+ val req = Request.post(url, body)
215
+ .addHeader("content-type", "application/x-www-form-urlencoded")
216
+ ```
217
+
218
+ ### Cookies in Requests and Responses
219
+
220
+ Send cookies to server and receive cookies from server:
221
+
222
+ ```scala
223
+ import zio.http._
224
+
225
+ val url = URL.parse("https://example.com").toOption.get
226
+
227
+ // Request: send cookies to server
228
+ val req = Request.get(url)
229
+ .addHeader("cookie", "sessionId=abc123; userId=456")
230
+
231
+ // Response: server sets cookies for client to store
232
+ val response = Response.ok
233
+ .addHeader("set-cookie", "sessionId=abc123; Max-Age=3600; Secure; HttpOnly")
234
+ ```
235
+
236
+ ---
237
+
238
+ ## Method
239
+
240
+ `Method` represents the HTTP verb (request method) as sealed case objects:
241
+
242
+ ```scala
243
+ sealed abstract class Method(val name: String, val ordinal: Int)
244
+ ```
245
+
246
+ ### Predefined Methods
247
+
248
+ The nine standard HTTP methods are predefined as case objects:
249
+
250
+ ```scala
251
+ import zio.http.Method
252
+
253
+ Method.GET // Retrieve a resource
254
+ Method.POST // Create a new resource
255
+ Method.PUT // Replace a resource entirely
256
+ Method.DELETE // Remove a resource
257
+ Method.PATCH // Partially update a resource
258
+ Method.HEAD // Retrieve headers only (no body)
259
+ Method.OPTIONS // Describe communication options
260
+ Method.TRACE // Echo the request back (for debugging)
261
+ Method.CONNECT // Establish a tunnel (for proxies)
262
+ ```
263
+
264
+ ### Parsing Methods
265
+
266
+ Parse HTTP method strings to `Method` objects:
267
+
268
+ ```scala
269
+ import zio.http.Method
270
+
271
+ Method.fromString("GET") // Some(Method.GET)
272
+ Method.fromString("POST") // Some(Method.POST)
273
+ Method.fromString("CUSTOM") // None (HTTP allows custom methods, but not predefined)
274
+ ```
275
+
276
+ ### Method Properties
277
+
278
+ Access properties of a `Method` object:
279
+
280
+ ```scala
281
+ import zio.http.Method
282
+
283
+ val m = Method.GET
284
+ m.name // "GET"
285
+ m.ordinal // ordinal position for sorting
286
+ m.toString // "GET"
287
+ ```
288
+
289
+ ---
290
+
291
+ ## Status
292
+
293
+ `Status` is an opaque type alias for `Int`, providing zero-allocation HTTP status codes with predefined constants for all standard codes (1xx–5xx).
294
+
295
+ ```scala
296
+ opaque type Status = Int // Scala 3
297
+ ```
298
+
299
+ ### Predefined Status Codes
300
+
301
+ Status codes organized by category:
302
+
303
+ ```scala
304
+ import zio.http.Status
305
+
306
+ // 1xx Informational
307
+ Status.Continue // 100
308
+ Status.SwitchingProtocols // 101
309
+
310
+ // 2xx Success
311
+ Status.Ok // 200
312
+ Status.Created // 201
313
+ Status.Accepted // 202
314
+ Status.NoContent // 204
315
+
316
+ // 3xx Redirection
317
+ Status.MovedPermanently // 301
318
+ Status.Found // 302 (temporary redirect)
319
+ Status.SeeOther // 303
320
+ Status.NotModified // 304 (response not modified since condition)
321
+ Status.TemporaryRedirect // 307
322
+ Status.PermanentRedirect // 308
323
+
324
+ // 4xx Client Errors
325
+ Status.BadRequest // 400 (malformed request)
326
+ Status.Unauthorized // 401 (authentication required)
327
+ Status.Forbidden // 403 (authenticated but not authorized)
328
+ Status.NotFound // 404 (resource not found)
329
+ Status.MethodNotAllowed // 405
330
+ Status.Conflict // 409
331
+ Status.Gone // 410 (resource permanently removed)
332
+ Status.UnprocessableEntity // 422
333
+ Status.TooManyRequests // 429 (rate limited)
334
+
335
+ // 5xx Server Errors
336
+ Status.InternalServerError // 500 (generic server error)
337
+ Status.BadGateway // 502
338
+ Status.ServiceUnavailable // 503 (server temporarily unavailable)
339
+ Status.GatewayTimeout // 504
340
+ ```
341
+
342
+ ### Creating Custom Status Codes
343
+
344
+ Create arbitrary status codes:
345
+
346
+ ```scala
347
+ import zio.http.Status
348
+
349
+ val custom = Status(418) // I'm a teapot (Easter egg)
350
+ val ok = Status.fromInt(200) // Parse from Int, returns Status
351
+ ```
352
+
353
+ ### Status Code Operations
354
+
355
+ Query status code properties:
356
+
357
+ ```scala
358
+ import zio.http.Status
359
+
360
+ val status = Status.Ok
361
+
362
+ status.code // 200 (the underlying Int)
363
+ status.text // "OK" (human-readable)
364
+ status.isSuccess // true (2xx status codes)
365
+ status.isInformational // false (1xx codes)
366
+ status.isRedirection // false (3xx codes)
367
+ status.isClientError // false (4xx codes)
368
+ status.isServerError // false (5xx codes)
369
+ status.isError // false (true for 4xx or 5xx)
370
+ ```
371
+
372
+ ---
373
+
374
+ ## Version
375
+
376
+ `Version` represents HTTP protocol versions:
377
+
378
+ ```scala
379
+ sealed abstract class Version(val major: Int, val minor: Int)
380
+ ```
381
+
382
+ ### Predefined Versions
383
+
384
+ The four standard HTTP versions are predefined:
385
+
386
+ ```scala
387
+ import zio.http.Version
388
+
389
+ Version.`HTTP/1.0` // HTTP/1.0 (1996)
390
+ Version.`HTTP/1.1` // HTTP/1.1 (1997, persistent connections)
391
+ Version.`HTTP/2.0` // HTTP/2.0 (2015, multiplexing)
392
+ Version.`HTTP/3.0` // HTTP/3.0 (2022, QUIC-based)
393
+ ```
394
+
395
+ ### Parsing and Rendering
396
+
397
+ Parse version strings and render to strings:
398
+
399
+ ```scala
400
+ import zio.http.Version
401
+
402
+ Version.fromString("HTTP/1.1") // Some(Version.`HTTP/1.1`)
403
+ Version.fromString("HTTP/2") // Some(Version.`HTTP/2.0`) (suffix optional)
404
+ Version.fromString("HTTP/3") // Some(Version.`HTTP/3.0`)
405
+
406
+ Version.render(Version.`HTTP/1.1`) // "HTTP/1.1"
407
+ Version.`HTTP/2.0`.text // "HTTP/2.0"
408
+ ```
409
+
410
+ ### Version Properties
411
+
412
+ Access version components:
413
+
414
+ ```scala
415
+ import zio.http.Version
416
+
417
+ val v = Version.`HTTP/2.0`
418
+ v.major // 2 (major version number)
419
+ v.minor // 0 (minor version number)
420
+ ```
421
+
422
+ ---
423
+
424
+ ## Scheme
425
+
426
+ `Scheme` represents URL schemes with support for HTTP, HTTPS, WebSocket protocols, and custom schemes:
427
+
428
+ ```scala
429
+ sealed trait Scheme {
430
+ def text: String
431
+ def defaultPort: Option[Int]
432
+ def isSecure: Boolean
433
+ def isWebSocket: Boolean
434
+ }
435
+ ```
436
+
437
+ ### Predefined Schemes
438
+
439
+ Standard schemes for HTTP, HTTPS, and WebSocket:
440
+
441
+ ```scala
442
+ import zio.http.Scheme
443
+
444
+ Scheme.HTTP // http://, default port 80 (unencrypted)
445
+ Scheme.HTTPS // https://, default port 443 (encrypted)
446
+ Scheme.WS // ws://, default port 80 (WebSocket, unencrypted)
447
+ Scheme.WSS // wss://, default port 443 (WebSocket, encrypted)
448
+ ```
449
+
450
+ ### Scheme Properties
451
+
452
+ ```scala
453
+ import zio.http.Scheme
454
+
455
+ Scheme.HTTPS.isSecure // true
456
+ Scheme.HTTP.isSecure // false
457
+ Scheme.WS.isWebSocket // true
458
+ Scheme.HTTP.isWebSocket // false
459
+
460
+ Scheme.HTTP.defaultPort // Some(80)
461
+ Scheme.HTTPS.defaultPort // Some(443)
462
+ Scheme.WS.defaultPort // Some(80)
463
+ Scheme.WSS.defaultPort // Some(443)
464
+
465
+ Scheme.HTTPS.text // "https"
466
+ ```
467
+
468
+ ### Custom Schemes
469
+
470
+ Create custom schemes (e.g., FTP):
471
+
472
+ ```scala
473
+ import zio.http.Scheme
474
+
475
+ val ftp = Scheme.Custom("ftp")
476
+ val text = ftp.text // "ftp"
477
+ val isSecure = ftp.isSecure // false (custom schemes assumed insecure)
478
+ ```
479
+
480
+ ---
481
+
482
+ ## URL
483
+
484
+ `URL` represents a complete Uniform Resource Locator with scheme, host, port, path, query parameters, and fragment:
485
+
486
+ ```scala
487
+ final case class URL(
488
+ scheme: Option[Scheme],
489
+ host: Option[String],
490
+ port: Option[Int],
491
+ path: Path,
492
+ queryParams: QueryParams,
493
+ fragment: Option[String]
494
+ )
495
+ ```
496
+
497
+ ### Parsing URLs
498
+
499
+ Parse URL strings into `URL` objects:
500
+
501
+ ```scala
502
+ import zio.http.URL
503
+
504
+ URL.parse("https://example.com/api/users?id=123&role=admin")
505
+ // Returns: Either[ParseError, URL]
506
+
507
+ URL.parse("https://api.example.com:8080/v1/users?page=1#results")
508
+ // Parses: scheme, host, port (explicit), path, query params, fragment
509
+ ```
510
+
511
+ ### Constructing URLs
512
+
513
+ Build URLs by parsing strings:
514
+
515
+ ```scala
516
+ import zio.http._
517
+
518
+ // Simplest approach: parse a URL string
519
+ val url1 = URL.parse("https://api.example.com/users/123?filter=active&sort=name").toOption.get
520
+
521
+ // Modify an existing URL by adding query parameters
522
+ val url2 = url1.??("page", "1")
523
+ ```
524
+
525
+ ### URL Operations
526
+
527
+ Access URL components and modify them:
528
+
529
+ ```scala
530
+ import zio.http.URL
531
+
532
+ val url = URL.parse("https://example.com:8080/api/v1/users?page=1#section").toOption.get
533
+
534
+ val scheme = url.scheme // Some(Scheme.HTTPS)
535
+ val host = url.host // Some("example.com")
536
+ val port = url.port // Some(8080)
537
+ val pathSegments = url.path.segments // Chunk("api", "v1", "users")
538
+ val pageParam = url.queryParams.getFirst("page") // Some("1") (first value for key)
539
+ val fragment = url.fragment // Some("section")
540
+
541
+ val urlWithSort = url.??("sort", "name") // Add query parameter using ?? operator
542
+ val urlWithNewFragment = url.copy(fragment = Some("top")) // Set fragment
543
+ ```
544
+
545
+ ---
546
+
547
+ ## Path
548
+
549
+ `Path` represents the URL path (e.g., `/api/v1/users`) stored as segments for efficient manipulation:
550
+
551
+ ```scala
552
+ final case class Path(
553
+ segments: Chunk[String],
554
+ hasLeadingSlash: Boolean,
555
+ trailingSlash: Boolean
556
+ )
557
+ ```
558
+
559
+ ### Creating Paths
560
+
561
+ Create paths from segments or parse from strings:
562
+
563
+ ```scala
564
+ import zio.http.Path
565
+
566
+ val path1 = Path.apply("/api/v1/users") // Parse from decoded path string
567
+ val path2 = Path.fromEncoded("%2Fapi%2Fv1%2Fusers") // Parse from percent-encoded string
568
+ val root = Path.root // Empty path "/"
569
+ ```
570
+
571
+ ### Path Operations
572
+
573
+ Access path segments and build modified paths:
574
+
575
+ ```scala
576
+ import zio.http.Path
577
+
578
+ val path = Path.apply("/api/v1/users")
579
+
580
+ val segments = path.segments // Chunk("api", "v1", "users")
581
+ val hasLeadingSlash = path.hasLeadingSlash // true
582
+ val hasTrailingSlash = path.trailingSlash // false
583
+ val encoded = path.encode // "/api/v1/users" (encoded string)
584
+ ```
585
+
586
+ ---
587
+
588
+ ## QueryParams
589
+
590
+ `QueryParams` is an immutable, case-insensitive multi-map for URL query parameters, optimized for performance:
591
+
592
+ ```scala
593
+ final class QueryParams private[http] (...)
594
+ ```
595
+
596
+ ### Creating QueryParams
597
+
598
+ Create query parameters from key-value pairs:
599
+
600
+ ```scala
601
+ import zio.http.QueryParams
602
+
603
+ val params1 = QueryParams("page" -> "1", "limit" -> "10")
604
+ val params2 = QueryParams.empty
605
+ val params3 = QueryParams.fromEncoded("page=1&limit=10") // Parse from encoded query string
606
+ ```
607
+
608
+ ### QueryParams Operations
609
+
610
+ Access and modify query parameters:
611
+
612
+ ```scala
613
+ import zio.http.QueryParams
614
+
615
+ val params = QueryParams("page" -> "1", "role" -> "admin", "role" -> "user")
616
+
617
+ val withSort = params.add("sort", "name") // Add parameter
618
+ val withoutPage = params.remove("page") // Remove all values for key
619
+ val encoded = params.encode // "page=1&role=admin&role=user"
620
+ ```
621
+
622
+ ### Multi-Value Parameters
623
+
624
+ `QueryParams` supports multiple values for the same key:
625
+
626
+ ```scala
627
+ import zio.http.QueryParams
628
+
629
+ val params = QueryParams("id" -> "1", "id" -> "2", "id" -> "3")
630
+ val firstId = params.getFirst("id") // Some("1") (first value)
631
+ val allIds = params.get("id") // Some(Chunk("1", "2", "3"))
632
+ ```
633
+
634
+ ---
635
+
636
+ ## Headers
637
+
638
+ `Headers` is an immutable, case-insensitive collection of HTTP header fields. It stores names pre-lowercased and raw values as strings, allows several entries with the same name, and parses a value into a typed `Header` only when a typed read asks for it:
639
+
640
+ ```scala
641
+ final class Headers private[http] (...) {
642
+ def size: Int
643
+ def get[A](headerCodec: Header.Codec[A]): Option[A]
644
+ def rawGet(name: String): Option[String]
645
+ def add(header: Header): Headers
646
+ def set(header: Header): Headers
647
+ def remove(name: String): Headers
648
+ }
649
+ ```
650
+
651
+ This section covers the collection itself. The 75 built-in typed headers, the codec type class behind them, the parse cache, and the validation rules all live on the [Header](./headers.md) page.
652
+
653
+ ### Creating Headers
654
+
655
+ `Headers.apply` takes name-value pairs, and `Headers.empty` is the starting point for a builder chain:
656
+
657
+ ```scala
658
+ import zio.http.Headers
659
+
660
+ val headers = Headers("content-type" -> "application/json", "cache-control" -> "no-cache")
661
+ ```
662
+
663
+ Names are lowercased as they are stored, so the collection reports them in canonical form regardless of how they were written:
664
+
665
+ ```scala
666
+ Headers("Content-Type" -> "application/json").toList
667
+ // res27: List[Tuple2[String, String]] = List(
668
+ // ("content-type", "application/json")
669
+ // )
670
+ ```
671
+
672
+ ### Reading Raw Values
673
+
674
+ Three methods read the stored string without parsing, differing in which entry they pick when a name repeats:
675
+
676
+ ```scala
677
+ headers.rawGet("Content-Type")
678
+ // res28: Option[String] = Some("application/json")
679
+ headers.rawGetAll("cache-control")
680
+ // res29: Chunk[String] = IndexedSeq("no-cache")
681
+ headers.has("content-type")
682
+ // res30: Boolean = true
683
+ ```
684
+
685
+ Lookups are case-insensitive, and `Headers#contains` is an alias for `Headers#has`. For typed reads — `Headers#get`, `Headers#getAll`, and `Headers#getLast`, which decode into a `Header` — see [Header](./headers.md).
686
+
687
+ ### Modifying Headers
688
+
689
+ `Headers#add` appends and `Headers#set` replaces every entry with the same name, which is the distinction that matters for multi-value headers:
690
+
691
+ ```scala
692
+ val twice = headers.add("set-cookie", "a=1").add("set-cookie", "b=2")
693
+ val replaced = twice.set("set-cookie", "c=3")
694
+ ```
695
+
696
+ Appending keeps both cookies while setting collapses them to one:
697
+
698
+ ```scala
699
+ twice.rawGetAll("set-cookie")
700
+ // res31: Chunk[String] = IndexedSeq("a=1", "b=2")
701
+ replaced.rawGetAll("set-cookie")
702
+ // res32: Chunk[String] = IndexedSeq("c=3")
703
+ ```
704
+
705
+ `Headers#remove` drops every entry with a name, `Headers#++` concatenates two collections without deduplicating, and `Headers#toList` and `Headers#toChunk` expose the pairs for iteration. Every operation returns a new `Headers`.
706
+
707
+ :::warning[Names and values are validated by throwing]
708
+ A header name must be an HTTP token and a value must not contain CR or LF — the latter is what prevents response splitting. The mutating methods enforce both with `IllegalArgumentException`, so check untrusted input with `Headers.validateName` or `Headers.validateValue` first. See [Header](./headers.md) for the rules and the safe pre-checks.
709
+ :::
710
+
711
+ ---
712
+
713
+ ## Body
714
+
715
+ `Body` wraps a `Stream[Nothing, Byte]` with a content type:
716
+
717
+ ```scala
718
+ final class Body private (
719
+ val stream: Stream[Nothing, Byte],
720
+ val contentType: ContentType
721
+ )
722
+ ```
723
+
724
+ ### Creating Bodies
725
+
726
+ `Body.empty` provides an empty body with default `application/octet-stream` content type:
727
+
728
+ ```scala
729
+ import zio.http.Body
730
+
731
+ val empty = Body.empty
732
+ // Body(stream = Stream.fromChunk(Chunk.empty[Byte]), contentType = application/octet-stream)
733
+ ```
734
+
735
+ `Body.fromString` creates a body with `text/plain` content type:
736
+
737
+ ```scala
738
+ import zio.http.{Body, Charset}
739
+
740
+ val fromString = Body.fromString("Hello, World!", Charset.UTF8)
741
+ // Content-Type: text/plain; charset=UTF-8
742
+ ```
743
+
744
+ `Body.fromArray` creates a body with default `application/octet-stream` content type without copying the supplied array. Later mutations to the array are visible through the body:
745
+
746
+ ```scala
747
+ import zio.http.Body
748
+
749
+ val fromBytes = Body.fromArray(Array[Byte](1, 2, 3))
750
+ // Content-Type: application/octet-stream
751
+ ```
752
+
753
+ `Body.fromChunk` creates a body from a `Chunk[Byte]` with optional content type:
754
+
755
+ ```scala
756
+ import zio.http.{Body, ContentType}
757
+ import zio.blocks.chunk.Chunk
758
+ import zio.blocks.mediatype.MediaTypes
759
+
760
+ val chunk = Chunk[Byte](1, 2, 3, 4, 5)
761
+ val body = Body.fromChunk(chunk)
762
+ // Content-Type: application/octet-stream (default)
763
+
764
+ val jsonBody = Body.fromChunk(chunk, ContentType(MediaTypes.application.`json`))
765
+ // Content-Type: application/json
766
+ ```
767
+
768
+ `Body.fromStream` creates a body from a `Stream[Nothing, Byte]` with optional content type:
769
+
770
+ ```scala
771
+ import zio.http.{Body, ContentType}
772
+ import zio.blocks.chunk.Chunk
773
+ import zio.blocks.streams.Stream
774
+ import zio.blocks.mediatype.MediaTypes
775
+
776
+ val stream = Stream.fromChunk(Chunk[Byte](1, 2, 3))
777
+ val body = Body.fromStream(stream, ContentType(MediaTypes.application.`octet-stream`))
778
+ // Content-Type: application/octet-stream
779
+ ```
780
+
781
+ ### Reading Bodies
782
+
783
+ `Body` provides access to the stream, content type, and convenience methods:
784
+
785
+ ```scala
786
+ import zio.http.{Body, Charset}
787
+
788
+ val body = Body.fromString("Hello!", Charset.UTF8)
789
+
790
+ body.length // Some(6)
791
+ body.isEmpty // false
792
+ body.nonEmpty // true
793
+ body.toStream // Stream[Nothing, Byte]
794
+ body.contentType // ContentType(text/plain; charset=UTF-8)
795
+
796
+ val utf8: zio.blocks.async.Async[String] = body.asStringAsync()
797
+ val ascii: zio.blocks.async.Async[String] = body.asStringAsync(Charset.ASCII)
798
+ val bytes: zio.blocks.async.Async[Array[Byte]] = body.toArrayAsync
799
+ val chunk: zio.blocks.async.Async[zio.blocks.chunk.Chunk[Byte]] = body.toChunkAsync
800
+ val fromContentType: zio.blocks.async.Async[String] = body.asStringFromContentTypeAsync
801
+ val text: zio.blocks.async.Async[String] = body.textAsync
802
+ ```
803
+
804
+ The five asynchronous materializers—`toChunkAsync`, `toArrayAsync`, `asStringAsync`, `asStringFromContentTypeAsync`, and the `textAsync` alias—are defined in shared sources and are available on both JVM and Scala.js. `asStringFromContentTypeAsync` and `textAsync` use the charset declared by the content type, falling back to UTF-8.
805
+
806
+ `Body` retains synchronous twins with the corresponding result types:
807
+
808
+ ```scala
809
+ import zio.http.{Body, Charset}
810
+ import zio.blocks.chunk.Chunk
811
+
812
+ val body = Body.fromString("Hello!", Charset.UTF8)
813
+
814
+ val chunk: Chunk[Byte] = body.toChunk
815
+ val bytes: Array[Byte] = body.toArray
816
+ val utf8: String = body.asString()
817
+ val ascii: String = body.asString(Charset.ASCII)
818
+ val fromContentType: String = body.asStringFromContentType
819
+ val text: String = body.text
820
+ ```
821
+
822
+ The synchronous methods remain available on both platforms for compatibility and for streams that complete synchronously. Prefer the asynchronous methods whenever a body can suspend; Scala.js cannot block while waiting for pending asynchronous work.
823
+
824
+ ---
825
+
826
+ ## ContentType
827
+
828
+ `ContentType` represents the MIME type, character encoding, and multipart boundary:
829
+
830
+ ```scala
831
+ final case class ContentType(
832
+ mediaType: MediaType,
833
+ boundary: Option[Boundary] = None,
834
+ charset: Option[Charset] = None
835
+ )
836
+ ```
837
+
838
+ ### Setting Content Types
839
+
840
+ Content types are specified as header values in requests and responses:
841
+
842
+ ```scala
843
+ import zio.http._
844
+
845
+ // Common content type headers
846
+ val jsonRequest = Request.post(
847
+ URL.parse("https://example.com/api").toOption.get,
848
+ Body.fromString("""{"key":"value"}""", Charset.UTF8)
849
+ ).addHeader("content-type", "application/json")
850
+
851
+ val htmlResponse = Response.ok
852
+ .addHeader("content-type", "text/html; charset=utf-8")
853
+
854
+ val xmlRequest = Request.post(
855
+ URL.parse("https://example.com/xml").toOption.get,
856
+ Body.fromString("<root/>", Charset.UTF8)
857
+ ).addHeader("content-type", "application/xml")
858
+
859
+ val formRequest = Request.post(
860
+ URL.parse("https://example.com/form").toOption.get,
861
+ Body.fromString("username=alice&password=secret", Charset.UTF8)
862
+ ).addHeader("content-type", "application/x-www-form-urlencoded")
863
+
864
+ val imageResponse = Response.ok
865
+ .addHeader("content-type", "image/png")
866
+ ```
867
+
868
+ ---
869
+
870
+ ## Charset
871
+
872
+ `Charset` represents character encoding for text content:
873
+
874
+ ```scala
875
+ sealed abstract class Charset(val name: String)
876
+ ```
877
+
878
+ ### Predefined Charsets
879
+
880
+ Standard character encodings:
881
+
882
+ ```scala
883
+ import zio.http.Charset
884
+
885
+ Charset.UTF8 // UTF-8 (Unicode, variable-length encoding)
886
+ Charset.ASCII // ASCII (7-bit encoding)
887
+ Charset.ISO_8859_1 // ISO-8859-1 (Latin-1)
888
+ Charset.UTF16 // UTF-16 (with BOM detection)
889
+ Charset.UTF16BE // UTF-16 Big-Endian
890
+ Charset.UTF16LE // UTF-16 Little-Endian
891
+ ```
892
+
893
+ ### Charset Properties
894
+
895
+ ```scala
896
+ import zio.http.Charset
897
+
898
+ Charset.UTF8.name // "UTF-8"
899
+ Charset.ISO_8859_1.name // "ISO-8859-1"
900
+ ```
901
+
902
+ ---
903
+
904
+ ## RequestCookie
905
+
906
+ `RequestCookie` represents a simple cookie sent from client to server in the Cookie header:
907
+
908
+ ```scala
909
+ final case class RequestCookie(name: String, value: String)
910
+ ```
911
+
912
+ ### Creating Request Cookies
913
+
914
+ ```scala
915
+ import zio.http.RequestCookie
916
+
917
+ val cookie = RequestCookie("sessionId", "abc123xyz")
918
+ val name = cookie.name // "sessionId"
919
+ val value = cookie.value // "abc123xyz"
920
+ ```
921
+
922
+ ### Sending Cookies
923
+
924
+ Include cookies in requests:
925
+
926
+ ```scala
927
+ import zio.http._
928
+
929
+ val url = URL.parse("https://example.com").toOption.get
930
+ val req = Request.get(url)
931
+ .addHeader("cookie", "sessionId=abc123; userId=456")
932
+
933
+ // Or build from RequestCookie objects
934
+ val cookies = zio.Chunk(
935
+ RequestCookie("sessionId", "abc123"),
936
+ RequestCookie("userId", "456")
937
+ )
938
+ // Render as Cookie header value (see Header operations)
939
+ ```
940
+
941
+ ---
942
+
943
+ ## ResponseCookie
944
+
945
+ `ResponseCookie` represents a cookie set by server for client storage, with full RFC 6265 attributes:
946
+
947
+ ```scala
948
+ final case class ResponseCookie(
949
+ name: String,
950
+ value: String,
951
+ expires: Option[String] = None,
952
+ domain: Option[String] = None,
953
+ path: Option[Path] = None,
954
+ maxAge: Option[Long] = None,
955
+ isSecure: Boolean = false,
956
+ isHttpOnly: Boolean = false,
957
+ sameSite: Option[SameSite] = None,
958
+ isPartitioned: Boolean = false,
959
+ priority: Option[CookiePriority] = None
960
+ )
961
+ ```
962
+
963
+ ### Creating Response Cookies
964
+
965
+ Create cookies with full control over attributes:
966
+
967
+ ```scala
968
+ import zio.http._
969
+
970
+ val sessionCookie = ResponseCookie(
971
+ name = "sessionId",
972
+ value = "abc123xyz",
973
+ path = Some(Path.apply("/")), // Apply to root path
974
+ maxAge = Some(3600), // 1 hour (in seconds)
975
+ isSecure = true, // HTTPS only (prevents transmission over HTTP)
976
+ isHttpOnly = true, // No JavaScript access (prevents XSS theft)
977
+ sameSite = Some(SameSite.Strict), // Prevent CSRF attacks
978
+ priority = Some(CookiePriority.High)
979
+ )
980
+ ```
981
+
982
+ ### Setting Cookies in Responses
983
+
984
+ Include cookies in responses by adding the Set-Cookie header:
985
+
986
+ ```scala
987
+ import zio.http._
988
+
989
+ val response = Response(Status.Ok)
990
+ .addHeader("set-cookie", "sessionId=abc123; Max-Age=3600; Secure; HttpOnly; SameSite=Strict")
991
+ ```
992
+
993
+ ### SameSite Attribute
994
+
995
+ Control cross-site request behavior:
996
+
997
+ ```scala
998
+ import zio.http.SameSite
999
+
1000
+ val strict = SameSite.Strict // Only same-site requests (default, safest)
1001
+ val lax = SameSite.Lax // Top-level navigations allowed (links, forms)
1002
+ val none = SameSite.None_ // Cross-site allowed; render requires Secure flag
1003
+ ```
1004
+
1005
+ ---
1006
+
1007
+ ## Form
1008
+
1009
+ `Form` represents `application/x-www-form-urlencoded` form data as key-value pairs. Spaces are encoded as `+`, and `+` decodes back to a space.
1010
+
1011
+ ```scala
1012
+ final case class Form(entries: Chunk[(String, String)])
1013
+ ```
1014
+
1015
+ ### Creating Forms
1016
+
1017
+ Create forms with key-value pairs:
1018
+
1019
+ ```scala
1020
+ import zio.http.Form
1021
+
1022
+ val form = Form(
1023
+ "username" -> "alice",
1024
+ "email" -> "alice@example.com",
1025
+ "subscribe" -> "true"
1026
+ )
1027
+ ```
1028
+
1029
+ ### Submitting Forms
1030
+
1031
+ Send forms as request body:
1032
+
1033
+ ```scala
1034
+ import zio.http._
1035
+
1036
+ val form = Form("username" -> "alice", "password" -> "secret")
1037
+ val body = Body.fromString(form.encode, Charset.UTF8)
1038
+
1039
+ val url = URL.parse("https://example.com/login").toOption.get
1040
+ val request = Request.post(url, body)
1041
+ .addHeader("content-type", "application/x-www-form-urlencoded")
1042
+ ```
1043
+
1044
+ ### Form Operations
1045
+
1046
+ Access and encode form data:
1047
+
1048
+ ```scala
1049
+ import zio.http.Form
1050
+
1051
+ val form = Form("key1" -> "value1", "key2" -> "value2")
1052
+
1053
+ val entries = form.entries // Chunk[(String, String)] of all entries
1054
+ val encoded = form.encode // "key1=value1&key2=value2" (form-url-encoded)
1055
+ ```
1056
+
1057
+ ---
1058
+
1059
+ ## FormField
1060
+
1061
+ `FormField` represents individual fields in multipart form data (used for file uploads):
1062
+
1063
+ ```scala
1064
+ sealed trait FormField
1065
+
1066
+ case class FormField.Simple(name: String, value: String)
1067
+ case class FormField.Text(name: String, filename: Option[String], value: String, contentType: Option[ContentType])
1068
+ case class FormField.Binary(name: String, filename: Option[String], data: Chunk[Byte], contentType: Option[ContentType])
1069
+ ```
1070
+
1071
+ ### Multipart Form Fields
1072
+
1073
+ Create fields for multipart form submissions:
1074
+
1075
+ ```scala
1076
+ import zio.http._
1077
+ import zio.blocks.chunk.Chunk
1078
+
1079
+ val textField = FormField.Simple("username", "alice")
1080
+
1081
+ val imageBytes = Chunk.fromArray("...image data...".getBytes)
1082
+ val fileField = FormField.Binary(
1083
+ name = "avatar",
1084
+ filename = Some("profile.png"),
1085
+ data = imageBytes,
1086
+ contentType = zio.http.ContentType.parse("image/png").toOption.get
1087
+ )
1088
+ ```
1089
+
1090
+ ---
1091
+
1092
+ ## Request
1093
+
1094
+ `Request` is the core type representing an HTTP request message with method, URL, headers, body, and version:
1095
+
1096
+ ```scala
1097
+ final case class Request(
1098
+ method: Method,
1099
+ url: URL,
1100
+ headers: Headers,
1101
+ body: Body,
1102
+ version: Version
1103
+ )
1104
+ ```
1105
+
1106
+ ### Creating Requests
1107
+
1108
+ Create requests using convenience methods:
1109
+
1110
+ ```scala
1111
+ import zio.http._
1112
+
1113
+ val url = URL.parse("https://api.example.com/users").toOption.get
1114
+ val body = Body.fromString("""{"name":"Alice"}""", Charset.UTF8)
1115
+
1116
+ // Using convenience methods (recommended)
1117
+ val getReq = Request.get(url)
1118
+ val postReq = Request.post(url, body)
1119
+ val putReq = Request.put(url, body)
1120
+ val deleteReq = Request.delete(url)
1121
+ val patchReq = Request.patch(url, body)
1122
+ val headReq = Request.head(url)
1123
+ val optionsReq = Request.options(url)
1124
+ ```
1125
+
1126
+ ### Request Operations
1127
+
1128
+ Access and modify request components:
1129
+
1130
+ ```scala
1131
+ import zio.http._
1132
+
1133
+ val url = URL.parse("https://api.example.com/users").toOption.get
1134
+ val req = Request.get(url)
1135
+
1136
+ val method = req.method // Method.GET
1137
+ val reqUrl = req.url // URL
1138
+ val headers = req.headers // Headers collection
1139
+ val body = req.body // Body
1140
+ val version = req.version // Version
1141
+
1142
+ val withAuth = req.addHeader("authorization", "Bearer token") // Add header
1143
+ val transformed = req.updateHeaders(_.add("x-custom", "value")) // Transform headers
1144
+ ```
1145
+
1146
+ ---
1147
+
1148
+ ## Response
1149
+
1150
+ `Response` is the core type representing an HTTP response message with status, headers, body, and version:
1151
+
1152
+ ```scala
1153
+ final case class Response(
1154
+ status: Status,
1155
+ headers: Headers,
1156
+ body: Body,
1157
+ version: Version
1158
+ )
1159
+ ```
1160
+
1161
+ ### Creating Responses
1162
+
1163
+ Create responses with status, headers, and body:
1164
+
1165
+ ```scala
1166
+ import zio.http._
1167
+
1168
+ Response(
1169
+ status = Status.Ok,
1170
+ headers = Headers("content-type" -> "application/json"),
1171
+ body = Body.fromString("""{"message":"ok"}""", Charset.UTF8),
1172
+ version = Version.`HTTP/1.1`
1173
+ )
1174
+ ```
1175
+
1176
+ ### Response Convenience Constructors
1177
+
1178
+ Quick constructors for common responses:
1179
+
1180
+ ```scala
1181
+ import zio.http._
1182
+
1183
+ Response.ok // 200 OK response value
1184
+ Response.ok.addHeader("content-type", "application/json") // With headers
1185
+ Response(Status.NotFound) // 404 Not Found
1186
+ Response(Status.InternalServerError) // 500 Internal Server Error
1187
+ ```
1188
+
1189
+ ### Response Operations
1190
+
1191
+ Access and modify response components:
1192
+
1193
+ ```scala
1194
+ import zio.http._
1195
+
1196
+ val body = Body.fromString("""{"data":"example"}""", Charset.UTF8)
1197
+ val resp = Response.ok.addHeader("content-type", "application/json")
1198
+
1199
+ resp.status // Status.Ok
1200
+ resp.headers // Headers
1201
+ resp.body // Body
1202
+ resp.version // Version
1203
+
1204
+ val modified = resp.copy(status = Status.Created) // Change status code
1205
+ modified.addHeader("cache-control", "no-cache") // Add header
1206
+ ```
1207
+
1208
+ ---
1209
+
1210
+ ## Integration Points
1211
+
1212
+ The HTTP model types integrate with each other in a natural composition hierarchy:
1213
+
1214
+ - **Request & Response** are the top-level message types, containing all other types
1215
+ - **URL** decomposes into Scheme, Host, Port, Path, QueryParams, and Fragment
1216
+ - **Headers** is a collection of individual Header objects (generic strings and typed headers)
1217
+ - **Body** carries ContentType metadata describing its content
1218
+ - **Method & Status** are enumerations representing HTTP verbs and response codes
1219
+ - **Version** describes the HTTP protocol version being used
1220
+ - **Cookies** (Request & Response variants) appear as header values in Cookie and Set-Cookie headers
1221
+ - **Form** is serialized as a Body with `application/x-www-form-urlencoded` content type
1222
+
1223
+ ## Advanced Usage
1224
+
1225
+ ### Building a Complete HTTP Exchange
1226
+
1227
+ Here's how to build a full request and response for a real scenario:
1228
+
1229
+ ```scala
1230
+ import zio.http._
1231
+
1232
+ // Build request
1233
+ val url = URL.parse("https://api.example.com/users").toOption.get
1234
+ val requestBody = Body.fromString("""{"name":"Alice","age":30}""", Charset.UTF8)
1235
+
1236
+ val request = Request(
1237
+ method = Method.POST,
1238
+ url = url,
1239
+ headers = Headers(
1240
+ "content-type" -> "application/json",
1241
+ "authorization" -> "Bearer abc123",
1242
+ "user-agent" -> "MyClient/1.0"
1243
+ ),
1244
+ body = requestBody,
1245
+ version = Version.`HTTP/1.1`
1246
+ )
1247
+
1248
+ // Build response
1249
+ val responseBody = Body.fromString("""{"id":123,"name":"Alice","age":30}""", Charset.UTF8)
1250
+
1251
+ val response = Response(
1252
+ status = Status.Created,
1253
+ headers = Headers(
1254
+ "content-type" -> "application/json",
1255
+ "location" -> "/users/123"
1256
+ ),
1257
+ body = responseBody,
1258
+ version = Version.`HTTP/1.1`
1259
+ )
1260
+ ```
1261
+
1262
+ ### URL Building with Fluent API
1263
+
1264
+ Compose complex URLs using operators:
1265
+
1266
+ ```scala
1267
+ import zio.http._
1268
+
1269
+ val url = URL.parse("https://api.example.com").toOption.get
1270
+
1271
+ val extended = (url / "v1" / "users" / "123") ?? ("include", "profile") ?? ("include", "posts")
1272
+
1273
+ extended.encode
1274
+ // "https://api.example.com/v1/users/123?include=profile&include=posts"
1275
+ ```
1276
+
1277
+ ### Cookie Management
1278
+
1279
+ Parse and render cookies in requests and responses:
1280
+
1281
+ ```scala
1282
+ import zio.http._
1283
+
1284
+ // Parse cookies from request header
1285
+ val cookieHeader = "session=abc; theme=dark"
1286
+ val requestCookies = Cookie.parseRequest(cookieHeader)
1287
+
1288
+ // Create response with Set-Cookie headers
1289
+ val sessionCookie = ResponseCookie(
1290
+ name = "session",
1291
+ value = "xyz123",
1292
+ path = Some(Path("/")),
1293
+ maxAge = Some(3600),
1294
+ isSecure = true,
1295
+ isHttpOnly = true,
1296
+ sameSite = Some(SameSite.Strict)
1297
+ )
1298
+
1299
+ val response = Response(
1300
+ status = Status.Ok,
1301
+ headers = Headers(
1302
+ "set-cookie" -> Cookie.renderResponse(sessionCookie)
1303
+ ),
1304
+ body = Body.empty
1305
+ )
1306
+ ```
1307
+
1308
+ ### Form Submission
1309
+
1310
+ Build and submit HTML forms:
1311
+
1312
+ ```scala
1313
+ import zio.http._
1314
+
1315
+ val form = Form(
1316
+ "username" -> "alice",
1317
+ "password" -> "secret",
1318
+ "remember" -> "true"
1319
+ )
1320
+
1321
+ val formBody = Body.fromString(form.encode, Charset.UTF8)
1322
+
1323
+ val request = Request(
1324
+ method = Method.POST,
1325
+ url = URL.parse("/login").toOption.get,
1326
+ headers = Headers(
1327
+ "content-type" -> "application/x-www-form-urlencoded"
1328
+ ),
1329
+ body = formBody,
1330
+ version = Version.`HTTP/1.1`
1331
+ )
1332
+ ```
1333
+
1334
+ ## Design Principles
1335
+
1336
+ ### Single Encoding Contract
1337
+
1338
+ `Path` and `QueryParams` store decoded values internally. Encoding happens only at output boundaries:
1339
+
1340
+ - `Path.fromEncoded(s)` decodes, stores decoded segments
1341
+ - `Path.encode` encodes segments for transmission
1342
+ - `QueryParams.fromEncoded(s)` decodes, stores decoded key-value pairs
1343
+ - `QueryParams.encode` encodes for transmission
1344
+
1345
+ This eliminates double-encoding bugs and clarifies responsibilities.
1346
+
1347
+ ### Lazy Header Parsing
1348
+
1349
+ Imagine you're building a high-traffic microservice that receives thousands of HTTP requests per second. Each request arrives with 20–30 headers: `content-type`, `authorization`, `cache-control`, `etag`, `x-request-id`, `x-trace-id`, custom headers your team added, and more.
1350
+
1351
+ Here's the problem: Your handler only *actually needs* three of those headers:
1352
+
1353
+ ```scala
1354
+ def handleRequest(request: Request): Response = {
1355
+ val authToken = request.headers.get("authorization")
1356
+ val contentType = request.headers.get("content-type")
1357
+ val requestId = request.headers.get("x-request-id")
1358
+
1359
+ // The other 17+ headers? Never touched.
1360
+ processRequest(authToken, contentType, requestId)
1361
+ }
1362
+ ```
1363
+
1364
+ If the `Headers` class eagerly parsed all 20+ headers the moment the request arrived, you'd be wasting CPU time parsing headers you don't care about. With 1,000 requests/second, that's parsing 20,000 unnecessary headers per second.
1365
+
1366
+ **http-model's solution: Parse headers only when you ask for them.**
1367
+
1368
+ Internally, `Headers` stores header names and values as raw strings. When you call `headers.get("authorization")` for the *first time*, it parses that specific header value into a typed structure (extracting charset, splitting directives, validating format, etc.), then *caches* the result. The second time you ask for the same header, it returns the cached parsed value instantly — no re-parsing.
1369
+
1370
+ ```scala
1371
+ val headers = Headers(
1372
+ "content-type" -> "application/json; charset=utf-8",
1373
+ "authorization" -> "Bearer abc123xyz",
1374
+ "cache-control" -> "no-cache, max-age=3600",
1375
+ // ... 17 more headers
1376
+ )
1377
+
1378
+ // First access: parses "content-type" string
1379
+ val ct1 = headers.get("content-type")
1380
+ // Internally parsed and cached as: ContentType(mediaType=ApplicationJson, charset=UTF8)
1381
+
1382
+ // Second access: returns cached parsed result (no parsing!)
1383
+ val ct2 = headers.get("content-type") // Instant — already cached
1384
+
1385
+ // Other headers never accessed? Never parsed. ✓
1386
+ ```
1387
+
1388
+ This design shines in three ways:
1389
+
1390
+ **Performance**: Only pay the cost for headers you actually use. With 20 headers and using 3, that's an ~85% reduction in parsing work. At scale (thousands of requests/second), this savings compounds dramatically.
1391
+
1392
+ **Robustness**: Custom or unknown headers don't cause parsing failures. If a header can't be parsed, it stays as a raw string, and your code can still access it as a plain value without crashing the handler.
1393
+
1394
+ **Simplicity**: Your code is clean — you just ask for headers by name, and http-model handles parsing transparently. No manual string manipulation or error handling on your end.
1395
+
1396
+ ### Stream-Backed Bodies
1397
+
1398
+ Let's say you're downloading a 500MB video file over HTTP. Should your `Body` object represent that as:
1399
+
1400
+ **Option A: A single `Chunk[Byte]` with all 500MB in memory?**
1401
+
1402
+ ```scala
1403
+ val body = Body.fromChunk(Chunk[Byte](/* 500MB of bytes */))
1404
+ // Everything loaded into RAM at once
1405
+ ```
1406
+
1407
+ **Option B: A Stream that yields bytes incrementally as they arrive?**
1408
+
1409
+ ```scala
1410
+ val byteStream: Stream[Nothing, Byte] = /* yields bytes as they download */
1411
+ val body = Body.fromStream(byteStream)
1412
+ // Only a small buffer in RAM; the rest comes from the network
1413
+ ```
1414
+
1415
+ Most HTTP libraries choose Option B for large files — streaming makes sense when you want to process data *as it arrives* without loading everything into memory first.
1416
+
1417
+ **http-model chooses a stream-backed body with runtime-independent asynchronous materialization.**
1418
+
1419
+ #### The Streaming Trade-off
1420
+
1421
+ Streaming saves memory and allows processing to begin before the complete payload arrives, but collecting the complete body may need to wait for asynchronous input. `Body` exposes that operation as `Async`, rather than coupling the HTTP model to ZIO, Cats Effect, Scala Futures, or a particular HTTP runtime.
1422
+
1423
+ #### http-model's Choice: Stream-Backed Bodies
1424
+
1425
+ http-model wraps a `Stream[Nothing, Byte]` — a pull-based stream with cross-platform asynchronous materialization and no required effect runtime:
1426
+
1427
+ ```scala
1428
+ final class Body private (
1429
+ val stream: Stream[Nothing, Byte],
1430
+ val contentType: ContentType
1431
+ )
1432
+ ```
1433
+
1434
+ Create bodies from chunks, arrays, strings, or streams:
1435
+
1436
+ ```scala
1437
+ val body = Body.fromChunk(
1438
+ Chunk.fromArray(myBytes),
1439
+ ContentType.`application/json`
1440
+ )
1441
+
1442
+ // Accessing the data on JVM and Scala.js:
1443
+ val chunk: Async[Chunk[Byte]] = body.toChunkAsync
1444
+ val bytes: Async[Array[Byte]] = body.toArrayAsync
1445
+ val decoded: Async[String] = body.asStringAsync(Charset.UTF8)
1446
+ val decodedFromContentType: Async[String] = body.asStringFromContentTypeAsync
1447
+ val text: Async[String] = body.textAsync
1448
+ val len: Option[Long] = body.length
1449
+ val raw: Stream[Nothing, Byte] = body.toStream
1450
+ ```
1451
+
1452
+ For chunk-backed bodies, materialization reuses the known chunk and `length` returns `Some(n)`. For a stream without known metadata, materialization consumes the stream and `length` returns `None`. The shared `Body.scala` defines both the five `Async` methods and their synchronous compatibility twins.
1453
+
1454
+ ```
1455
+ Your Application Code
1456
+ ↓
1457
+ (uses)
1458
+ ↓
1459
+ ┌─────────────────────┐
1460
+ │ HTTP Client │ (ZIO HTTP, Akka HTTP, etc.)
1461
+ │ (does streaming) │
1462
+ └─────────────────────┘
1463
+ ↓
1464
+ (wraps/unwraps)
1465
+ ↓
1466
+ ┌─────────────────────┐
1467
+ │ http-model Body │ (stream-backed, async materialization)
1468
+ │ (pull-based I/O) │
1469
+ └─────────────────────┘
1470
+ ```
1471
+
1472
+ Body remains independent of any HTTP or effect runtime, but materializing an opaque stream can be asynchronous. Prefer `toChunkAsync`, `toArrayAsync`, `asStringAsync`, `asStringFromContentTypeAsync`, or `textAsync` for streams that can suspend. The synchronous twins remain available on both platforms for compatibility and synchronously completing streams.
1473
+
1474
+ ## Running the Examples
1475
+
1476
+ All code from this guide is available as runnable examples in the `http-model-examples` module.
1477
+
1478
+ **1. Clone the repository and navigate to the project:**
1479
+
1480
+ ```bash
1481
+ git clone https://github.com/zio/zio-blocks.git
1482
+ cd zio-blocks
1483
+ ```
1484
+
1485
+ **2. Run individual examples with sbt:**
1486
+
1487
+ ### Basic HTTP Request/Response
1488
+
1489
+ Demonstrates creating HTTP requests and responses with URLs, methods, headers, and bodies. Shows how `Request`, `Response`, `Method`, `URL`, `Headers`, and `Body` types work together.
1490
+
1491
+ ```bash
1492
+ sbt "http-model-examples/runMain httpmodel.BasicHttpRequest"
1493
+ ```
1494
+
1495
+ ### Headers and Query Parameters
1496
+
1497
+ Shows how to work with headers and query parameters in URLs and requests. Demonstrates how `Headers`, `QueryParams`, and `URL` types compose for extracting and manipulating HTTP metadata.
1498
+
1499
+ ```bash
1500
+ sbt "http-model-examples/runMain httpmodel.HeadersAndQueryParams"
1501
+ ```
1502
+
1503
+ ### Form Submission and Cookies
1504
+
1505
+ Demonstrates handling form data and cookies using `Request`, `Response`, `Form`, and cookie types. Shows realistic form submission scenarios with proper content-type headers and cookie management.
1506
+
1507
+ ```bash
1508
+ sbt "http-model-examples/runMain httpmodel.FormAndCookies"
1509
+ ```
1510
+
1511
+ ### Complete HTTP Exchange
1512
+
1513
+ Shows a realistic HTTP exchange scenario: creating a request with multiple headers and query parameters, sending it, and receiving a response with status codes and headers. Demonstrates all core types working together in a practical scenario.
1514
+
1515
+ ```bash
1516
+ sbt "http-model-examples/runMain httpmodel.CompleteHttpExchange"
1517
+ ```