@zio.dev/zio-blocks 0.0.33 → 0.0.51

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/guides/compile-time-resource-safety-with-scope.md +16 -17
  2. package/guides/getting-started-with-mux.md +1507 -0
  3. package/guides/query-dsl-extending.md +161 -102
  4. package/guides/query-dsl-fluent-builder.md +217 -157
  5. package/guides/query-dsl-reified-optics.md +12 -10
  6. package/guides/query-dsl-sql.md +246 -165
  7. package/guides/telemetry-guide.md +1069 -0
  8. package/guides/zio-schema-migration.md +29 -22
  9. package/index.md +292 -50
  10. package/package.json +1 -1
  11. package/plans/config-follow-up-prs.md +188 -0
  12. package/plans/config-pr-assessment-roadmap.md +310 -0
  13. package/reference/MuxDataFlow.jsx +250 -0
  14. package/reference/async.md +651 -0
  15. package/reference/chunk.md +3533 -308
  16. package/reference/codegen/case-class.md +436 -0
  17. package/reference/codegen/emitter-config.md +383 -0
  18. package/reference/codegen/examples.md +664 -0
  19. package/reference/codegen/field.md +316 -0
  20. package/reference/codegen/index.md +317 -0
  21. package/reference/codegen/scala-emitter.md +392 -0
  22. package/reference/codegen/scala-file.md +276 -0
  23. package/reference/codegen/sealed-trait.md +408 -0
  24. package/reference/codegen/type-definition.md +340 -0
  25. package/reference/codegen/type-ref.md +201 -0
  26. package/reference/combinators.md +347 -117
  27. package/reference/config.md +158 -0
  28. package/reference/context.md +4 -4
  29. package/reference/datastar.md +346 -0
  30. package/reference/docs.md +1461 -345
  31. package/reference/endpoint/auth-type.md +146 -0
  32. package/reference/endpoint/endpoint.md +297 -0
  33. package/reference/endpoint/http-codec.md +249 -0
  34. package/reference/endpoint/index.md +825 -0
  35. package/reference/endpoint/path-codec.md +237 -0
  36. package/reference/endpoint/route-pattern.md +196 -0
  37. package/reference/endpoint/route-tree.md +111 -0
  38. package/reference/endpoint/segment-codec.md +212 -0
  39. package/reference/html.md +1120 -0
  40. package/reference/htmx/attribute-values.md +359 -0
  41. package/reference/htmx/hx-encoding.md +111 -0
  42. package/reference/htmx/hx-params.md +204 -0
  43. package/reference/htmx/hx-swap.md +276 -0
  44. package/reference/htmx/hx-sync.md +251 -0
  45. package/reference/htmx/hx-target.md +314 -0
  46. package/reference/htmx/hx-trigger.md +457 -0
  47. package/reference/htmx/hx-url-update.md +239 -0
  48. package/reference/htmx/index.md +855 -0
  49. package/reference/http-model/index.md +47 -0
  50. package/reference/http-model/model.md +1481 -0
  51. package/reference/http-model/schema.md +747 -0
  52. package/reference/maybe.md +826 -0
  53. package/reference/media-type.md +2 -2
  54. package/reference/mux.mdx +823 -0
  55. package/reference/openapi.md +1351 -0
  56. package/reference/resource-management/defer-handle.md +1 -1
  57. package/reference/resource-management/resource.md +31 -2
  58. package/reference/resource-management/scope.md +28 -12
  59. package/reference/resource-management/wire.md +3 -7
  60. package/reference/ringbuffer/MpmcDiagram.jsx +717 -0
  61. package/reference/ringbuffer/MpscDiagram.jsx +618 -0
  62. package/reference/ringbuffer/SpmcDiagram.jsx +680 -0
  63. package/reference/ringbuffer/SpscDiagram.jsx +677 -0
  64. package/reference/ringbuffer/advanced.mdx +109 -0
  65. package/reference/ringbuffer/index.mdx +145 -0
  66. package/reference/ringbuffer/mpmc.mdx +151 -0
  67. package/reference/ringbuffer/mpsc.mdx +132 -0
  68. package/reference/ringbuffer/spmc.mdx +108 -0
  69. package/reference/ringbuffer/spsc.mdx +344 -0
  70. package/reference/{allows.md → schema/allows.md} +4 -4
  71. package/reference/{binding-resolver.md → schema/binding-resolver.md} +1 -1
  72. package/reference/{binding.md → schema/binding.md} +2 -3
  73. package/reference/schema/built-in-codecs/avro.md +451 -0
  74. package/reference/schema/built-in-codecs/bson.md +480 -0
  75. package/reference/schema/built-in-codecs/csv.md +564 -0
  76. package/reference/schema/built-in-codecs/index.md +77 -0
  77. package/reference/schema/built-in-codecs/json/index.md +295 -0
  78. package/reference/schema/built-in-codecs/json/json-config.md +217 -0
  79. package/reference/{json-patch.md → schema/built-in-codecs/json/json-patch.md} +5 -5
  80. package/reference/{json-schema.md → schema/built-in-codecs/json/json-schema.md} +14 -47
  81. package/reference/schema/built-in-codecs/json/json-selection.md +322 -0
  82. package/reference/{json.md → schema/built-in-codecs/json/json.md} +32 -64
  83. package/reference/schema/built-in-codecs/messagepack.md +508 -0
  84. package/reference/schema/built-in-codecs/thrift.md +433 -0
  85. package/reference/schema/built-in-codecs/toon.md +1078 -0
  86. package/reference/{xml.md → schema/built-in-codecs/xml.md} +13 -9
  87. package/reference/schema/built-in-codecs/yaml.md +552 -0
  88. package/reference/{codec.md → schema/codec.md} +10 -10
  89. package/reference/{dynamic-optic.md → schema/dynamic-optic.md} +151 -5
  90. package/reference/{dynamic-schema.md → schema/dynamic-schema.md} +8 -8
  91. package/reference/schema/format.md +92 -0
  92. package/reference/schema/index.md +50 -0
  93. package/reference/schema/migration.md +297 -0
  94. package/reference/{modifier.md → schema/modifier.md} +58 -7
  95. package/reference/{optics.md → schema/optics.md} +2 -2
  96. package/reference/{patch.md → schema/patch.md} +1 -1
  97. package/{path-interpolator.md → reference/schema/path-interpolator.md} +165 -72
  98. package/reference/{schema-evolution → schema/schema-evolution}/as.md +8 -8
  99. package/reference/{schema-evolution → schema/schema-evolution}/index.md +2 -2
  100. package/reference/{schema-evolution → schema/schema-evolution}/into.md +8 -8
  101. package/reference/{schema-expr.md → schema/schema-expr.md} +110 -175
  102. package/reference/{schema.md → schema/schema.md} +12 -0
  103. package/reference/{structural-types.md → schema/structural-types.md} +1 -1
  104. package/reference/{type-class-derivation.md → schema/type-class-derivation.md} +63 -1
  105. package/reference/smithy.md +533 -0
  106. package/reference/sql/db-codec-deriver.md +71 -0
  107. package/reference/sql/db-codec.md +687 -0
  108. package/reference/sql/db-con.md +271 -0
  109. package/reference/sql/db-connection.md +153 -0
  110. package/reference/sql/db-param-writer.md +77 -0
  111. package/reference/sql/db-param.md +66 -0
  112. package/reference/sql/db-result-reader.md +146 -0
  113. package/reference/sql/db-tx.md +82 -0
  114. package/reference/sql/db-value.md +41 -0
  115. package/reference/sql/ddl.md +85 -0
  116. package/reference/sql/frag.md +254 -0
  117. package/reference/sql/index.md +341 -0
  118. package/reference/sql/repo.md +600 -0
  119. package/reference/sql/sql-dialect.md +73 -0
  120. package/reference/sql/sql-logger.md +62 -0
  121. package/reference/sql/sql-name-mapper.md +70 -0
  122. package/reference/sql/table-metadata.md +134 -0
  123. package/reference/sql/table.md +448 -0
  124. package/reference/sql/transactor-zio.md +399 -0
  125. package/reference/sql/transactor.md +353 -0
  126. package/reference/sql-zio.md +112 -0
  127. package/reference/streams/concurrent-operators.md +106 -0
  128. package/reference/streams/index.md +653 -0
  129. package/reference/streams/pipeline.md +718 -0
  130. package/reference/streams/reader.md +1284 -0
  131. package/reference/streams/scala-2-compatibility.md +55 -0
  132. package/reference/streams/sink.md +1426 -0
  133. package/reference/streams/stream.md +2526 -0
  134. package/reference/streams/writer.md +1045 -0
  135. package/reference/streams/zero-boxing.md +275 -0
  136. package/reference/telemetry.md +693 -0
  137. package/reference/typeid.md +5 -19
  138. package/sidebars.js +238 -43
  139. package/reference/formats.md +0 -694
  140. package/reference/http-model.md +0 -1716
  141. package/reference/streams.md +0 -989
  142. package/ringbuffer.md +0 -249
  143. /package/reference/{json-differ.md → schema/built-in-codecs/json/json-differ.md} +0 -0
  144. /package/reference/{dynamic-value.md → schema/dynamic-value.md} +0 -0
  145. /package/reference/{lazy.md → schema/lazy.md} +0 -0
  146. /package/reference/{reflect.md → schema/reflect.md} +0 -0
  147. /package/reference/{registers.md → schema/registers.md} +0 -0
  148. /package/reference/{schema-error.md → schema/schema-error.md} +0 -0
  149. /package/reference/{syntax.md → schema/syntax.md} +0 -0
  150. /package/reference/{validation.md → schema/validation.md} +0 -0
@@ -0,0 +1,1481 @@
1
+ ---
2
+ id: model
3
+ title: "HTTP Model"
4
+ ---
5
+
6
+ `zio-http-model` is a **pure, zero-dependency 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 async effects, file streams, or a specific Scala version's IO model. Sharing becomes messy.
46
+
47
+ **Scenario 2: Testing Without Effects**
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 async runtimes, streaming libraries, or other baggage you don't need in tests. A simple unit test becomes a production-grade effect 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: Pure HTTP Data
54
+
55
+ `zio-http-model` separates **protocol concerns** (representing HTTP messages) from **effect concerns** (actually sending/receiving them). It provides:
56
+
57
+ - **Pure immutable data types** — `Request`, `Response`, `URL`, `Headers`, and chunk-backed `Body` values are just data. Stream-backed bodies are still effect-free, but collecting them may consume the underlying stream.
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.51"
74
+ ```
75
+
76
+ For cross-platform projects (Scala.js):
77
+
78
+ ```scala
79
+ libraryDependencies += "dev.zio" %%% "zio-http-model" % "0.0.51"
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 headers with lazy parsing for typed header access:
639
+
640
+ ```scala
641
+ final class Headers private[http] (...)
642
+ ```
643
+
644
+ ### Creating Headers
645
+
646
+ Create header collections:
647
+
648
+ ```scala
649
+ import zio.http.Headers
650
+
651
+ val headers1 = Headers("content-type" -> "application/json", "authorization" -> "Bearer token123")
652
+ val headers2 = Headers.empty
653
+ ```
654
+
655
+ ### Headers Operations
656
+
657
+ Access and modify headers:
658
+
659
+ ```scala
660
+ import zio.http.Headers
661
+
662
+ val headers = Headers("content-type" -> "application/json", "cache-control" -> "no-cache")
663
+
664
+ // Headers can be queried and modified using methods
665
+ val withAuth = headers.add("authorization", "Bearer token") // Add header
666
+ val asList = headers.toList // All headers as List
667
+ ```
668
+
669
+ ---
670
+
671
+ ## Body
672
+
673
+ `Body` wraps a `Stream[Nothing, Byte]` with a content type:
674
+
675
+ ```scala
676
+ final class Body private (
677
+ val stream: Stream[Nothing, Byte],
678
+ val contentType: ContentType
679
+ )
680
+ ```
681
+
682
+ ### Creating Bodies
683
+
684
+ `Body.empty` provides an empty body with default `application/octet-stream` content type:
685
+
686
+ ```scala
687
+ import zio.http.Body
688
+
689
+ val empty = Body.empty
690
+ // Body(stream = Stream.fromChunk(Chunk.empty[Byte]), contentType = application/octet-stream)
691
+ ```
692
+
693
+ `Body.fromString` creates a body with `text/plain` content type:
694
+
695
+ ```scala
696
+ import zio.http.{Body, Charset}
697
+
698
+ val fromString = Body.fromString("Hello, World!", Charset.UTF8)
699
+ // Content-Type: text/plain; charset=UTF-8
700
+ ```
701
+
702
+ `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:
703
+
704
+ ```scala
705
+ import zio.http.Body
706
+
707
+ val fromBytes = Body.fromArray(Array[Byte](1, 2, 3))
708
+ // Content-Type: application/octet-stream
709
+ ```
710
+
711
+ `Body.fromChunk` creates a body from a `Chunk[Byte]` with optional content type:
712
+
713
+ ```scala
714
+ import zio.http.{Body, ContentType}
715
+ import zio.blocks.chunk.Chunk
716
+ import zio.blocks.mediatype.MediaTypes
717
+
718
+ val chunk = Chunk[Byte](1, 2, 3, 4, 5)
719
+ val body = Body.fromChunk(chunk)
720
+ // Content-Type: application/octet-stream (default)
721
+
722
+ val jsonBody = Body.fromChunk(chunk, ContentType(MediaTypes.application.`json`))
723
+ // Content-Type: application/json
724
+ ```
725
+
726
+ `Body.fromStream` creates a body from a `Stream[Nothing, Byte]` with optional content type:
727
+
728
+ ```scala
729
+ import zio.http.{Body, ContentType}
730
+ import zio.blocks.chunk.Chunk
731
+ import zio.blocks.streams.Stream
732
+ import zio.blocks.mediatype.MediaTypes
733
+
734
+ val stream = Stream.fromChunk(Chunk[Byte](1, 2, 3))
735
+ val body = Body.fromStream(stream, ContentType(MediaTypes.application.`octet-stream`))
736
+ // Content-Type: application/octet-stream
737
+ ```
738
+
739
+ ### Reading Bodies
740
+
741
+ `Body` provides access to the stream, content type, and convenience methods:
742
+
743
+ ```scala
744
+ import zio.http.{Body, Charset}
745
+
746
+ val body = Body.fromString("Hello!", Charset.UTF8)
747
+
748
+ body.length // Some(6)
749
+ body.isEmpty // false
750
+ body.nonEmpty // true
751
+ body.asString() // "Hello!" (UTF-8 default)
752
+ body.asString(Charset.ASCII) // "Hello!" (explicit charset)
753
+ body.toChunk // Chunk[Byte](72, 101, 108, 108, 111, 33)
754
+ body.toStream // Stream[Nothing, Byte]
755
+ body.toArray // Array[Byte](72, 101, 108, 108, 111, 33)
756
+ body.contentType // ContentType(text/plain; charset=UTF-8)
757
+ ```
758
+
759
+ ---
760
+
761
+ ## ContentType
762
+
763
+ `ContentType` represents the MIME type, character encoding, and multipart boundary:
764
+
765
+ ```scala
766
+ final case class ContentType(
767
+ mediaType: MediaType,
768
+ boundary: Option[Boundary] = None,
769
+ charset: Option[Charset] = None
770
+ )
771
+ ```
772
+
773
+ ### Setting Content Types
774
+
775
+ Content types are specified as header values in requests and responses:
776
+
777
+ ```scala
778
+ import zio.http._
779
+
780
+ // Common content type headers
781
+ val jsonRequest = Request.post(
782
+ URL.parse("https://example.com/api").toOption.get,
783
+ Body.fromString("""{"key":"value"}""", Charset.UTF8)
784
+ ).addHeader("content-type", "application/json")
785
+
786
+ val htmlResponse = Response.ok
787
+ .addHeader("content-type", "text/html; charset=utf-8")
788
+
789
+ val xmlRequest = Request.post(
790
+ URL.parse("https://example.com/xml").toOption.get,
791
+ Body.fromString("<root/>", Charset.UTF8)
792
+ ).addHeader("content-type", "application/xml")
793
+
794
+ val formRequest = Request.post(
795
+ URL.parse("https://example.com/form").toOption.get,
796
+ Body.fromString("username=alice&password=secret", Charset.UTF8)
797
+ ).addHeader("content-type", "application/x-www-form-urlencoded")
798
+
799
+ val imageResponse = Response.ok
800
+ .addHeader("content-type", "image/png")
801
+ ```
802
+
803
+ ---
804
+
805
+ ## Charset
806
+
807
+ `Charset` represents character encoding for text content:
808
+
809
+ ```scala
810
+ sealed abstract class Charset(val name: String)
811
+ ```
812
+
813
+ ### Predefined Charsets
814
+
815
+ Standard character encodings:
816
+
817
+ ```scala
818
+ import zio.http.Charset
819
+
820
+ Charset.UTF8 // UTF-8 (Unicode, variable-length encoding)
821
+ Charset.ASCII // ASCII (7-bit encoding)
822
+ Charset.ISO_8859_1 // ISO-8859-1 (Latin-1)
823
+ Charset.UTF16 // UTF-16 (with BOM detection)
824
+ Charset.UTF16BE // UTF-16 Big-Endian
825
+ Charset.UTF16LE // UTF-16 Little-Endian
826
+ ```
827
+
828
+ ### Charset Properties
829
+
830
+ ```scala
831
+ import zio.http.Charset
832
+
833
+ Charset.UTF8.name // "UTF-8"
834
+ Charset.ISO_8859_1.name // "ISO-8859-1"
835
+ ```
836
+
837
+ ---
838
+
839
+ ## RequestCookie
840
+
841
+ `RequestCookie` represents a simple cookie sent from client to server in the Cookie header:
842
+
843
+ ```scala
844
+ final case class RequestCookie(name: String, value: String)
845
+ ```
846
+
847
+ ### Creating Request Cookies
848
+
849
+ ```scala
850
+ import zio.http.RequestCookie
851
+
852
+ val cookie = RequestCookie("sessionId", "abc123xyz")
853
+ val name = cookie.name // "sessionId"
854
+ val value = cookie.value // "abc123xyz"
855
+ ```
856
+
857
+ ### Sending Cookies
858
+
859
+ Include cookies in requests:
860
+
861
+ ```scala
862
+ import zio.http._
863
+
864
+ val url = URL.parse("https://example.com").toOption.get
865
+ val req = Request.get(url)
866
+ .addHeader("cookie", "sessionId=abc123; userId=456")
867
+
868
+ // Or build from RequestCookie objects
869
+ val cookies = zio.Chunk(
870
+ RequestCookie("sessionId", "abc123"),
871
+ RequestCookie("userId", "456")
872
+ )
873
+ // Render as Cookie header value (see Header operations)
874
+ ```
875
+
876
+ ---
877
+
878
+ ## ResponseCookie
879
+
880
+ `ResponseCookie` represents a cookie set by server for client storage, with full RFC 6265 attributes:
881
+
882
+ ```scala
883
+ final case class ResponseCookie(
884
+ name: String,
885
+ value: String,
886
+ expires: Option[String] = None,
887
+ domain: Option[String] = None,
888
+ path: Option[Path] = None,
889
+ maxAge: Option[Long] = None,
890
+ isSecure: Boolean = false,
891
+ isHttpOnly: Boolean = false,
892
+ sameSite: Option[SameSite] = None,
893
+ isPartitioned: Boolean = false,
894
+ priority: Option[CookiePriority] = None
895
+ )
896
+ ```
897
+
898
+ ### Creating Response Cookies
899
+
900
+ Create cookies with full control over attributes:
901
+
902
+ ```scala
903
+ import zio.http._
904
+
905
+ val sessionCookie = ResponseCookie(
906
+ name = "sessionId",
907
+ value = "abc123xyz",
908
+ path = Some(Path.apply("/")), // Apply to root path
909
+ maxAge = Some(3600), // 1 hour (in seconds)
910
+ isSecure = true, // HTTPS only (prevents transmission over HTTP)
911
+ isHttpOnly = true, // No JavaScript access (prevents XSS theft)
912
+ sameSite = Some(SameSite.Strict), // Prevent CSRF attacks
913
+ priority = Some(CookiePriority.High)
914
+ )
915
+ ```
916
+
917
+ ### Setting Cookies in Responses
918
+
919
+ Include cookies in responses by adding the Set-Cookie header:
920
+
921
+ ```scala
922
+ import zio.http._
923
+
924
+ val response = Response(Status.Ok)
925
+ .addHeader("set-cookie", "sessionId=abc123; Max-Age=3600; Secure; HttpOnly; SameSite=Strict")
926
+ ```
927
+
928
+ ### SameSite Attribute
929
+
930
+ Control cross-site request behavior:
931
+
932
+ ```scala
933
+ import zio.http.SameSite
934
+
935
+ val strict = SameSite.Strict // Only same-site requests (default, safest)
936
+ val lax = SameSite.Lax // Top-level navigations allowed (links, forms)
937
+ val none = SameSite.None_ // Cross-site allowed; render requires Secure flag
938
+ ```
939
+
940
+ ---
941
+
942
+ ## Form
943
+
944
+ `Form` represents `application/x-www-form-urlencoded` form data as key-value pairs. Spaces are encoded as `+`, and `+` decodes back to a space.
945
+
946
+ ```scala
947
+ final case class Form(entries: Chunk[(String, String)])
948
+ ```
949
+
950
+ ### Creating Forms
951
+
952
+ Create forms with key-value pairs:
953
+
954
+ ```scala
955
+ import zio.http.Form
956
+
957
+ val form = Form(
958
+ "username" -> "alice",
959
+ "email" -> "alice@example.com",
960
+ "subscribe" -> "true"
961
+ )
962
+ ```
963
+
964
+ ### Submitting Forms
965
+
966
+ Send forms as request body:
967
+
968
+ ```scala
969
+ import zio.http._
970
+
971
+ val form = Form("username" -> "alice", "password" -> "secret")
972
+ val body = Body.fromString(form.encode, Charset.UTF8)
973
+
974
+ val url = URL.parse("https://example.com/login").toOption.get
975
+ val request = Request.post(url, body)
976
+ .addHeader("content-type", "application/x-www-form-urlencoded")
977
+ ```
978
+
979
+ ### Form Operations
980
+
981
+ Access and encode form data:
982
+
983
+ ```scala
984
+ import zio.http.Form
985
+
986
+ val form = Form("key1" -> "value1", "key2" -> "value2")
987
+
988
+ val entries = form.entries // Chunk[(String, String)] of all entries
989
+ val encoded = form.encode // "key1=value1&key2=value2" (form-url-encoded)
990
+ ```
991
+
992
+ ---
993
+
994
+ ## FormField
995
+
996
+ `FormField` represents individual fields in multipart form data (used for file uploads):
997
+
998
+ ```scala
999
+ sealed trait FormField
1000
+
1001
+ case class FormField.Simple(name: String, value: String)
1002
+ case class FormField.Text(name: String, filename: Option[String], value: String, contentType: Option[ContentType])
1003
+ case class FormField.Binary(name: String, filename: Option[String], data: Chunk[Byte], contentType: Option[ContentType])
1004
+ ```
1005
+
1006
+ ### Multipart Form Fields
1007
+
1008
+ Create fields for multipart form submissions:
1009
+
1010
+ ```scala
1011
+ import zio.http._
1012
+ import zio.blocks.chunk.Chunk
1013
+
1014
+ val textField = FormField.Simple("username", "alice")
1015
+
1016
+ val imageBytes = Chunk.fromArray("...image data...".getBytes)
1017
+ val fileField = FormField.Binary(
1018
+ name = "avatar",
1019
+ filename = Some("profile.png"),
1020
+ data = imageBytes,
1021
+ contentType = zio.http.ContentType.parse("image/png").toOption.get
1022
+ )
1023
+ ```
1024
+
1025
+ ---
1026
+
1027
+ ## Request
1028
+
1029
+ `Request` is the core type representing an HTTP request message with method, URL, headers, body, and version:
1030
+
1031
+ ```scala
1032
+ final case class Request(
1033
+ method: Method,
1034
+ url: URL,
1035
+ headers: Headers,
1036
+ body: Body,
1037
+ version: Version
1038
+ )
1039
+ ```
1040
+
1041
+ ### Creating Requests
1042
+
1043
+ Create requests using convenience methods:
1044
+
1045
+ ```scala
1046
+ import zio.http._
1047
+
1048
+ val url = URL.parse("https://api.example.com/users").toOption.get
1049
+ val body = Body.fromString("""{"name":"Alice"}""", Charset.UTF8)
1050
+
1051
+ // Using convenience methods (recommended)
1052
+ val getReq = Request.get(url)
1053
+ val postReq = Request.post(url, body)
1054
+ val putReq = Request.put(url, body)
1055
+ val deleteReq = Request.delete(url)
1056
+ val patchReq = Request.patch(url, body)
1057
+ val headReq = Request.head(url)
1058
+ val optionsReq = Request.options(url)
1059
+ ```
1060
+
1061
+ ### Request Operations
1062
+
1063
+ Access and modify request components:
1064
+
1065
+ ```scala
1066
+ import zio.http._
1067
+
1068
+ val url = URL.parse("https://api.example.com/users").toOption.get
1069
+ val req = Request.get(url)
1070
+
1071
+ val method = req.method // Method.GET
1072
+ val reqUrl = req.url // URL
1073
+ val headers = req.headers // Headers collection
1074
+ val body = req.body // Body
1075
+ val version = req.version // Version
1076
+
1077
+ val withAuth = req.addHeader("authorization", "Bearer token") // Add header
1078
+ val transformed = req.updateHeaders(_.add("x-custom", "value")) // Transform headers
1079
+ ```
1080
+
1081
+ ---
1082
+
1083
+ ## Response
1084
+
1085
+ `Response` is the core type representing an HTTP response message with status, headers, body, and version:
1086
+
1087
+ ```scala
1088
+ final case class Response(
1089
+ status: Status,
1090
+ headers: Headers,
1091
+ body: Body,
1092
+ version: Version
1093
+ )
1094
+ ```
1095
+
1096
+ ### Creating Responses
1097
+
1098
+ Create responses with status, headers, and body:
1099
+
1100
+ ```scala
1101
+ import zio.http._
1102
+
1103
+ Response(
1104
+ status = Status.Ok,
1105
+ headers = Headers("content-type" -> "application/json"),
1106
+ body = Body.fromString("""{"message":"ok"}""", Charset.UTF8),
1107
+ version = Version.`HTTP/1.1`
1108
+ )
1109
+ ```
1110
+
1111
+ ### Response Convenience Constructors
1112
+
1113
+ Quick constructors for common responses:
1114
+
1115
+ ```scala
1116
+ import zio.http._
1117
+
1118
+ Response.ok // 200 OK response value
1119
+ Response.ok.addHeader("content-type", "application/json") // With headers
1120
+ Response(Status.NotFound) // 404 Not Found
1121
+ Response(Status.InternalServerError) // 500 Internal Server Error
1122
+ ```
1123
+
1124
+ ### Response Operations
1125
+
1126
+ Access and modify response components:
1127
+
1128
+ ```scala
1129
+ import zio.http._
1130
+
1131
+ val body = Body.fromString("""{"data":"example"}""", Charset.UTF8)
1132
+ val resp = Response.ok.addHeader("content-type", "application/json")
1133
+
1134
+ resp.status // Status.Ok
1135
+ resp.headers // Headers
1136
+ resp.body // Body
1137
+ resp.version // Version
1138
+
1139
+ val modified = resp.copy(status = Status.Created) // Change status code
1140
+ modified.addHeader("cache-control", "no-cache") // Add header
1141
+ ```
1142
+
1143
+ ---
1144
+
1145
+ ## Integration Points
1146
+
1147
+ The HTTP model types integrate with each other in a natural composition hierarchy:
1148
+
1149
+ - **Request & Response** are the top-level message types, containing all other types
1150
+ - **URL** decomposes into Scheme, Host, Port, Path, QueryParams, and Fragment
1151
+ - **Headers** is a collection of individual Header objects (generic strings and typed headers)
1152
+ - **Body** carries ContentType metadata describing its content
1153
+ - **Method & Status** are enumerations representing HTTP verbs and response codes
1154
+ - **Version** describes the HTTP protocol version being used
1155
+ - **Cookies** (Request & Response variants) appear as header values in Cookie and Set-Cookie headers
1156
+ - **Form** is serialized as a Body with `application/x-www-form-urlencoded` content type
1157
+
1158
+ ## Advanced Usage
1159
+
1160
+ ### Building a Complete HTTP Exchange
1161
+
1162
+ Here's how to build a full request and response for a real scenario:
1163
+
1164
+ ```scala
1165
+ import zio.http._
1166
+
1167
+ // Build request
1168
+ val url = URL.parse("https://api.example.com/users").toOption.get
1169
+ val requestBody = Body.fromString("""{"name":"Alice","age":30}""", Charset.UTF8)
1170
+
1171
+ val request = Request(
1172
+ method = Method.POST,
1173
+ url = url,
1174
+ headers = Headers(
1175
+ "content-type" -> "application/json",
1176
+ "authorization" -> "Bearer abc123",
1177
+ "user-agent" -> "MyClient/1.0"
1178
+ ),
1179
+ body = requestBody,
1180
+ version = Version.`HTTP/1.1`
1181
+ )
1182
+
1183
+ // Build response
1184
+ val responseBody = Body.fromString("""{"id":123,"name":"Alice","age":30}""", Charset.UTF8)
1185
+
1186
+ val response = Response(
1187
+ status = Status.Created,
1188
+ headers = Headers(
1189
+ "content-type" -> "application/json",
1190
+ "location" -> "/users/123"
1191
+ ),
1192
+ body = responseBody,
1193
+ version = Version.`HTTP/1.1`
1194
+ )
1195
+ ```
1196
+
1197
+ ### URL Building with Fluent API
1198
+
1199
+ Compose complex URLs using operators:
1200
+
1201
+ ```scala
1202
+ import zio.http._
1203
+
1204
+ val url = URL.parse("https://api.example.com").toOption.get
1205
+
1206
+ val extended = (url / "v1" / "users" / "123") ?? ("include", "profile") ?? ("include", "posts")
1207
+
1208
+ extended.encode
1209
+ // "https://api.example.com/v1/users/123?include=profile&include=posts"
1210
+ ```
1211
+
1212
+ ### Cookie Management
1213
+
1214
+ Parse and render cookies in requests and responses:
1215
+
1216
+ ```scala
1217
+ import zio.http._
1218
+
1219
+ // Parse cookies from request header
1220
+ val cookieHeader = "session=abc; theme=dark"
1221
+ val requestCookies = Cookie.parseRequest(cookieHeader)
1222
+
1223
+ // Create response with Set-Cookie headers
1224
+ val sessionCookie = ResponseCookie(
1225
+ name = "session",
1226
+ value = "xyz123",
1227
+ path = Some(Path("/")),
1228
+ maxAge = Some(3600),
1229
+ isSecure = true,
1230
+ isHttpOnly = true,
1231
+ sameSite = Some(SameSite.Strict)
1232
+ )
1233
+
1234
+ val response = Response(
1235
+ status = Status.Ok,
1236
+ headers = Headers(
1237
+ "set-cookie" -> Cookie.renderResponse(sessionCookie)
1238
+ ),
1239
+ body = Body.empty
1240
+ )
1241
+ ```
1242
+
1243
+ ### Form Submission
1244
+
1245
+ Build and submit HTML forms:
1246
+
1247
+ ```scala
1248
+ import zio.http._
1249
+
1250
+ val form = Form(
1251
+ "username" -> "alice",
1252
+ "password" -> "secret",
1253
+ "remember" -> "true"
1254
+ )
1255
+
1256
+ val formBody = Body.fromString(form.encode, Charset.UTF8)
1257
+
1258
+ val request = Request(
1259
+ method = Method.POST,
1260
+ url = URL.parse("/login").toOption.get,
1261
+ headers = Headers(
1262
+ "content-type" -> "application/x-www-form-urlencoded"
1263
+ ),
1264
+ body = formBody,
1265
+ version = Version.`HTTP/1.1`
1266
+ )
1267
+ ```
1268
+
1269
+ ## Design Principles
1270
+
1271
+ ### Single Encoding Contract
1272
+
1273
+ `Path` and `QueryParams` store decoded values internally. Encoding happens only at output boundaries:
1274
+
1275
+ - `Path.fromEncoded(s)` decodes, stores decoded segments
1276
+ - `Path.encode` encodes segments for transmission
1277
+ - `QueryParams.fromEncoded(s)` decodes, stores decoded key-value pairs
1278
+ - `QueryParams.encode` encodes for transmission
1279
+
1280
+ This eliminates double-encoding bugs and clarifies responsibilities.
1281
+
1282
+ ### Lazy Header Parsing
1283
+
1284
+ 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.
1285
+
1286
+ Here's the problem: Your handler only *actually needs* three of those headers:
1287
+
1288
+ ```scala
1289
+ def handleRequest(request: Request): Response = {
1290
+ val authToken = request.headers.get("authorization")
1291
+ val contentType = request.headers.get("content-type")
1292
+ val requestId = request.headers.get("x-request-id")
1293
+
1294
+ // The other 17+ headers? Never touched.
1295
+ processRequest(authToken, contentType, requestId)
1296
+ }
1297
+ ```
1298
+
1299
+ 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.
1300
+
1301
+ **http-model's solution: Parse headers only when you ask for them.**
1302
+
1303
+ 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.
1304
+
1305
+ ```scala
1306
+ val headers = Headers(
1307
+ "content-type" -> "application/json; charset=utf-8",
1308
+ "authorization" -> "Bearer abc123xyz",
1309
+ "cache-control" -> "no-cache, max-age=3600",
1310
+ // ... 17 more headers
1311
+ )
1312
+
1313
+ // First access: parses "content-type" string
1314
+ val ct1 = headers.get("content-type")
1315
+ // Internally parsed and cached as: ContentType(mediaType=ApplicationJson, charset=UTF8)
1316
+
1317
+ // Second access: returns cached parsed result (no parsing!)
1318
+ val ct2 = headers.get("content-type") // Instant — already cached
1319
+
1320
+ // Other headers never accessed? Never parsed. ✓
1321
+ ```
1322
+
1323
+ This design shines in three ways:
1324
+
1325
+ **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.
1326
+
1327
+ **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.
1328
+
1329
+ **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.
1330
+
1331
+ ### No Streaming
1332
+
1333
+ Let's say you're downloading a 500MB video file over HTTP. Should your `Body` object represent that as:
1334
+
1335
+ **Option A: A single `Chunk[Byte]` with all 500MB in memory?**
1336
+
1337
+ ```scala
1338
+ val body = Body(data = Chunk[Byte](/* 500MB of bytes */))
1339
+ // Everything loaded into RAM at once
1340
+ ```
1341
+
1342
+ **Option B: A Stream that yields bytes incrementally as they arrive?**
1343
+
1344
+ ```scala
1345
+ val body = Body(data = Stream[Byte]) // Yields chunks as they download
1346
+ // Only a small buffer in RAM; the rest comes from the network
1347
+ ```
1348
+
1349
+ 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.
1350
+
1351
+ **http-model chooses an effect-free stream-backed body.** Here's why.
1352
+
1353
+ #### The Streaming Trade-off
1354
+
1355
+ Streaming sounds great on paper — save memory, start processing immediately — but it brings complexity:
1356
+
1357
+ **Streaming requires effects:**
1358
+
1359
+ ```scala
1360
+ // With streaming, reading a body becomes an effect:
1361
+ val body: Body = request.body
1362
+ val bytes: IO[Chunk[Byte]] = body.stream.runCollect()
1363
+ // Reading the body is now an IO operation, not a pure value!
1364
+ ```
1365
+
1366
+ This couples `Body` to a specific effect system (ZIO, Cats Effect, Scala Futures, etc.). Different effect systems have different streaming abstractions, and your `Body` type would need to know about all of them — or you'd lock users into one.
1367
+
1368
+ **Streaming requires error handling:**
1369
+ ```scala
1370
+ // With streaming, errors can happen mid-stream:
1371
+ body.stream.fold(
1372
+ error => handleNetworkFailure(error), // Network cut out!
1373
+ chunk => processChunk(chunk),
1374
+ () => done()
1375
+ )
1376
+ // You must handle errors at every chunk boundary
1377
+ ```
1378
+
1379
+ **Streaming complicates testing:**
1380
+
1381
+ ```scala
1382
+ // Testing code that consumes streams is verbose:
1383
+ val testStream = Stream(
1384
+ Chunk(1, 2, 3),
1385
+ Chunk(4, 5, 6),
1386
+ Chunk(7, 8, 9)
1387
+ ).flatMap(_.stream)
1388
+ // vs. just: Chunk(1, 2, 3, 4, 5, 6, 7, 8, 9)
1389
+ ```
1390
+
1391
+ #### http-model's Choice: Stream-Backed Bodies
1392
+
1393
+ http-model wraps a `Stream[Nothing, Byte]` — a synchronous, pull-based stream with no effect system:
1394
+
1395
+ ```scala
1396
+ final class Body private (
1397
+ val stream: Stream[Nothing, Byte],
1398
+ val contentType: ContentType
1399
+ )
1400
+ ```
1401
+
1402
+ Create bodies from chunks, arrays, strings, or streams:
1403
+
1404
+ ```scala
1405
+ val body = Body.fromChunk(
1406
+ Chunk.fromArray(myBytes),
1407
+ ContentType.`application/json`
1408
+ )
1409
+
1410
+ // Accessing the data:
1411
+ val bytes: Chunk[Byte] = body.toChunk // Materializes the stream (O(1) for chunk-backed bodies)
1412
+ val len: Option[Long] = body.length // Known length without materializing, if available
1413
+ val raw: Stream[Nothing, Byte] = body.toStream // Access the underlying stream directly
1414
+ ```
1415
+
1416
+ For chunk-backed bodies, `toChunk` is O(1) and `length` returns `Some(n)`. For stream-backed bodies, `toChunk` runs the stream to collect all bytes and `length` returns `None`.
1417
+
1418
+ ```
1419
+ Your Application Code
1420
+ ↓
1421
+ (uses)
1422
+ ↓
1423
+ ┌─────────────────────┐
1424
+ │ HTTP Client │ (ZIO HTTP, Akka HTTP, etc.)
1425
+ │ (does streaming) │
1426
+ └─────────────────────┘
1427
+ ↓
1428
+ (wraps/unwraps)
1429
+ ↓
1430
+ ┌─────────────────────┐
1431
+ │ http-model Body │ (stream-backed, synchronous, no effects)
1432
+ │ (pull-based I/O) │
1433
+ └─────────────────────┘
1434
+ ```
1435
+
1436
+ Body is synchronous and effect-free — it uses ZIO Blocks' pull-based `Stream`, not an effectful stream type. When the body wraps a known `Chunk`, access is pure and immediate. When it wraps an opaque stream, `toChunk` pulls all bytes on demand.
1437
+
1438
+ ## Running the Examples
1439
+
1440
+ All code from this guide is available as runnable examples in the `http-model-examples` module.
1441
+
1442
+ **1. Clone the repository and navigate to the project:**
1443
+
1444
+ ```bash
1445
+ git clone https://github.com/zio/zio-blocks.git
1446
+ cd zio-blocks
1447
+ ```
1448
+
1449
+ **2. Run individual examples with sbt:**
1450
+
1451
+ ### Basic HTTP Request/Response
1452
+
1453
+ Demonstrates creating HTTP requests and responses with URLs, methods, headers, and bodies. Shows how `Request`, `Response`, `Method`, `URL`, `Headers`, and `Body` types work together.
1454
+
1455
+ ```bash
1456
+ sbt "http-model-examples/runMain httpmodel.BasicHttpRequest"
1457
+ ```
1458
+
1459
+ ### Headers and Query Parameters
1460
+
1461
+ 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.
1462
+
1463
+ ```bash
1464
+ sbt "http-model-examples/runMain httpmodel.HeadersAndQueryParams"
1465
+ ```
1466
+
1467
+ ### Form Submission and Cookies
1468
+
1469
+ 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.
1470
+
1471
+ ```bash
1472
+ sbt "http-model-examples/runMain httpmodel.FormAndCookies"
1473
+ ```
1474
+
1475
+ ### Complete HTTP Exchange
1476
+
1477
+ 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.
1478
+
1479
+ ```bash
1480
+ sbt "http-model-examples/runMain httpmodel.CompleteHttpExchange"
1481
+ ```