@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.
- package/guides/compile-time-resource-safety-with-scope.md +16 -17
- package/guides/getting-started-with-mux.md +1507 -0
- package/guides/query-dsl-extending.md +161 -102
- package/guides/query-dsl-fluent-builder.md +217 -157
- package/guides/query-dsl-reified-optics.md +12 -10
- package/guides/query-dsl-sql.md +246 -165
- package/guides/telemetry-guide.md +1069 -0
- package/guides/zio-schema-migration.md +29 -22
- package/index.md +292 -50
- package/package.json +1 -1
- package/plans/config-follow-up-prs.md +188 -0
- package/plans/config-pr-assessment-roadmap.md +310 -0
- package/reference/MuxDataFlow.jsx +250 -0
- package/reference/async.md +651 -0
- package/reference/chunk.md +3533 -308
- package/reference/codegen/case-class.md +436 -0
- package/reference/codegen/emitter-config.md +383 -0
- package/reference/codegen/examples.md +664 -0
- package/reference/codegen/field.md +316 -0
- package/reference/codegen/index.md +317 -0
- package/reference/codegen/scala-emitter.md +392 -0
- package/reference/codegen/scala-file.md +276 -0
- package/reference/codegen/sealed-trait.md +408 -0
- package/reference/codegen/type-definition.md +340 -0
- package/reference/codegen/type-ref.md +201 -0
- package/reference/combinators.md +347 -117
- package/reference/config.md +158 -0
- package/reference/context.md +4 -4
- package/reference/datastar.md +346 -0
- package/reference/docs.md +1461 -345
- package/reference/endpoint/auth-type.md +146 -0
- package/reference/endpoint/endpoint.md +297 -0
- package/reference/endpoint/http-codec.md +249 -0
- package/reference/endpoint/index.md +825 -0
- package/reference/endpoint/path-codec.md +237 -0
- package/reference/endpoint/route-pattern.md +196 -0
- package/reference/endpoint/route-tree.md +111 -0
- package/reference/endpoint/segment-codec.md +212 -0
- package/reference/html.md +1120 -0
- package/reference/htmx/attribute-values.md +359 -0
- package/reference/htmx/hx-encoding.md +111 -0
- package/reference/htmx/hx-params.md +204 -0
- package/reference/htmx/hx-swap.md +276 -0
- package/reference/htmx/hx-sync.md +251 -0
- package/reference/htmx/hx-target.md +314 -0
- package/reference/htmx/hx-trigger.md +457 -0
- package/reference/htmx/hx-url-update.md +239 -0
- package/reference/htmx/index.md +855 -0
- package/reference/http-model/index.md +47 -0
- package/reference/http-model/model.md +1481 -0
- package/reference/http-model/schema.md +747 -0
- package/reference/maybe.md +826 -0
- package/reference/media-type.md +2 -2
- package/reference/mux.mdx +823 -0
- package/reference/openapi.md +1351 -0
- package/reference/resource-management/defer-handle.md +1 -1
- package/reference/resource-management/resource.md +31 -2
- package/reference/resource-management/scope.md +28 -12
- package/reference/resource-management/wire.md +3 -7
- package/reference/ringbuffer/MpmcDiagram.jsx +717 -0
- package/reference/ringbuffer/MpscDiagram.jsx +618 -0
- package/reference/ringbuffer/SpmcDiagram.jsx +680 -0
- package/reference/ringbuffer/SpscDiagram.jsx +677 -0
- package/reference/ringbuffer/advanced.mdx +109 -0
- package/reference/ringbuffer/index.mdx +145 -0
- package/reference/ringbuffer/mpmc.mdx +151 -0
- package/reference/ringbuffer/mpsc.mdx +132 -0
- package/reference/ringbuffer/spmc.mdx +108 -0
- package/reference/ringbuffer/spsc.mdx +344 -0
- package/reference/{allows.md → schema/allows.md} +4 -4
- package/reference/{binding-resolver.md → schema/binding-resolver.md} +1 -1
- package/reference/{binding.md → schema/binding.md} +2 -3
- package/reference/schema/built-in-codecs/avro.md +451 -0
- package/reference/schema/built-in-codecs/bson.md +480 -0
- package/reference/schema/built-in-codecs/csv.md +564 -0
- package/reference/schema/built-in-codecs/index.md +77 -0
- package/reference/schema/built-in-codecs/json/index.md +295 -0
- package/reference/schema/built-in-codecs/json/json-config.md +217 -0
- package/reference/{json-patch.md → schema/built-in-codecs/json/json-patch.md} +5 -5
- package/reference/{json-schema.md → schema/built-in-codecs/json/json-schema.md} +14 -47
- package/reference/schema/built-in-codecs/json/json-selection.md +322 -0
- package/reference/{json.md → schema/built-in-codecs/json/json.md} +32 -64
- package/reference/schema/built-in-codecs/messagepack.md +508 -0
- package/reference/schema/built-in-codecs/thrift.md +433 -0
- package/reference/schema/built-in-codecs/toon.md +1078 -0
- package/reference/{xml.md → schema/built-in-codecs/xml.md} +13 -9
- package/reference/schema/built-in-codecs/yaml.md +552 -0
- package/reference/{codec.md → schema/codec.md} +10 -10
- package/reference/{dynamic-optic.md → schema/dynamic-optic.md} +151 -5
- package/reference/{dynamic-schema.md → schema/dynamic-schema.md} +8 -8
- package/reference/schema/format.md +92 -0
- package/reference/schema/index.md +50 -0
- package/reference/schema/migration.md +297 -0
- package/reference/{modifier.md → schema/modifier.md} +58 -7
- package/reference/{optics.md → schema/optics.md} +2 -2
- package/reference/{patch.md → schema/patch.md} +1 -1
- package/{path-interpolator.md → reference/schema/path-interpolator.md} +165 -72
- package/reference/{schema-evolution → schema/schema-evolution}/as.md +8 -8
- package/reference/{schema-evolution → schema/schema-evolution}/index.md +2 -2
- package/reference/{schema-evolution → schema/schema-evolution}/into.md +8 -8
- package/reference/{schema-expr.md → schema/schema-expr.md} +110 -175
- package/reference/{schema.md → schema/schema.md} +12 -0
- package/reference/{structural-types.md → schema/structural-types.md} +1 -1
- package/reference/{type-class-derivation.md → schema/type-class-derivation.md} +63 -1
- package/reference/smithy.md +533 -0
- package/reference/sql/db-codec-deriver.md +71 -0
- package/reference/sql/db-codec.md +687 -0
- package/reference/sql/db-con.md +271 -0
- package/reference/sql/db-connection.md +153 -0
- package/reference/sql/db-param-writer.md +77 -0
- package/reference/sql/db-param.md +66 -0
- package/reference/sql/db-result-reader.md +146 -0
- package/reference/sql/db-tx.md +82 -0
- package/reference/sql/db-value.md +41 -0
- package/reference/sql/ddl.md +85 -0
- package/reference/sql/frag.md +254 -0
- package/reference/sql/index.md +341 -0
- package/reference/sql/repo.md +600 -0
- package/reference/sql/sql-dialect.md +73 -0
- package/reference/sql/sql-logger.md +62 -0
- package/reference/sql/sql-name-mapper.md +70 -0
- package/reference/sql/table-metadata.md +134 -0
- package/reference/sql/table.md +448 -0
- package/reference/sql/transactor-zio.md +399 -0
- package/reference/sql/transactor.md +353 -0
- package/reference/sql-zio.md +112 -0
- package/reference/streams/concurrent-operators.md +106 -0
- package/reference/streams/index.md +653 -0
- package/reference/streams/pipeline.md +718 -0
- package/reference/streams/reader.md +1284 -0
- package/reference/streams/scala-2-compatibility.md +55 -0
- package/reference/streams/sink.md +1426 -0
- package/reference/streams/stream.md +2526 -0
- package/reference/streams/writer.md +1045 -0
- package/reference/streams/zero-boxing.md +275 -0
- package/reference/telemetry.md +693 -0
- package/reference/typeid.md +5 -19
- package/sidebars.js +238 -43
- package/reference/formats.md +0 -694
- package/reference/http-model.md +0 -1716
- package/reference/streams.md +0 -989
- package/ringbuffer.md +0 -249
- /package/reference/{json-differ.md → schema/built-in-codecs/json/json-differ.md} +0 -0
- /package/reference/{dynamic-value.md → schema/dynamic-value.md} +0 -0
- /package/reference/{lazy.md → schema/lazy.md} +0 -0
- /package/reference/{reflect.md → schema/reflect.md} +0 -0
- /package/reference/{registers.md → schema/registers.md} +0 -0
- /package/reference/{schema-error.md → schema/schema-error.md} +0 -0
- /package/reference/{syntax.md → schema/syntax.md} +0 -0
- /package/reference/{validation.md → schema/validation.md} +0 -0
package/reference/http-model.md
DELETED
|
@@ -1,1716 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
id: http-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 requests, responses, headers, URLs, paths, query parameters, and all HTTP primitives.
|
|
7
|
-
|
|
8
|
-
The module is designed as a pure data layer:
|
|
9
|
-
|
|
10
|
-
- **Zero effects**: no streaming, no I/O, no mutable state (except monotonic lazy-parse caches in `Headers`)
|
|
11
|
-
- **Zero ZIO dependency**: uses `zio.blocks.chunk.Chunk`, not `zio.Chunk`
|
|
12
|
-
- **Single encoding contract**: `Path` and `QueryParams` store values decoded internally, encode only on output
|
|
13
|
-
- **Cross-platform**: JVM and Scala.js support
|
|
14
|
-
- **Cross-version**: Scala 2.13 and 3.x support
|
|
15
|
-
|
|
16
|
-
```scala
|
|
17
|
-
package zio.http
|
|
18
|
-
|
|
19
|
-
// Core request/response types
|
|
20
|
-
final case class Request(method: Method, url: URL, headers: Headers, body: Body, version: Version)
|
|
21
|
-
final case class Response(status: Status, headers: Headers, body: Body, version: Version)
|
|
22
|
-
|
|
23
|
-
// URL structure
|
|
24
|
-
final case class URL(scheme: Option[Scheme], host: Option[String], port: Option[Int],
|
|
25
|
-
path: Path, queryParams: QueryParams, fragment: Option[String])
|
|
26
|
-
|
|
27
|
-
final case class Path(segments: Chunk[String], hasLeadingSlash: Boolean, trailingSlash: Boolean)
|
|
28
|
-
final class QueryParams private[http] (...)
|
|
29
|
-
|
|
30
|
-
// HTTP primitives
|
|
31
|
-
sealed abstract class Method(val name: String, val ordinal: Int)
|
|
32
|
-
opaque type Status = Int // Scala 3
|
|
33
|
-
sealed abstract class Version(val major: Int, val minor: Int)
|
|
34
|
-
sealed trait Scheme
|
|
35
|
-
|
|
36
|
-
// Headers and body
|
|
37
|
-
final class Headers private[http] (...)
|
|
38
|
-
sealed trait Header
|
|
39
|
-
final class Body private (val data: Chunk[Byte], val contentType: ContentType)
|
|
40
|
-
|
|
41
|
-
// Supporting types
|
|
42
|
-
final case class ContentType(mediaType: MediaType, boundary: Option[Boundary], charset: Option[Charset])
|
|
43
|
-
final case class ResponseCookie(...), RequestCookie(name: String, value: String)
|
|
44
|
-
final case class Form(entries: Chunk[(String, String)])
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
## Motivation
|
|
48
|
-
|
|
49
|
-
HTTP libraries often couple protocol concerns with effects and streaming, making it difficult to:
|
|
50
|
-
|
|
51
|
-
- Share data structures across client and server implementations
|
|
52
|
-
- Serialize requests/responses for caching or testing
|
|
53
|
-
- Work with HTTP primitives without committing to a specific effect system
|
|
54
|
-
|
|
55
|
-
`zio-http-model` solves this by providing pure data types that:
|
|
56
|
-
|
|
57
|
-
- Represent all HTTP concepts as immutable values
|
|
58
|
-
- Encode only on output (decode on input, store decoded)
|
|
59
|
-
- Parse incrementally with lazy caching (`Headers` parses typed headers on first access and caches the result)
|
|
60
|
-
- Work with any effect system or none at all
|
|
61
|
-
|
|
62
|
-
## Installation
|
|
63
|
-
|
|
64
|
-
Add the following to your `build.sbt`:
|
|
65
|
-
|
|
66
|
-
```scala
|
|
67
|
-
libraryDependencies += "dev.zio" %% "zio-http-model" % "<version>"
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
For cross-platform projects (Scala.js):
|
|
71
|
-
|
|
72
|
-
```scala
|
|
73
|
-
libraryDependencies += "dev.zio" %%% "zio-http-model" % "<version>"
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
Supported Scala versions: 2.13.x and 3.x
|
|
77
|
-
|
|
78
|
-
## Quick Start
|
|
79
|
-
|
|
80
|
-
Creating a request with query parameters:
|
|
81
|
-
|
|
82
|
-
```scala
|
|
83
|
-
import zio.http._
|
|
84
|
-
|
|
85
|
-
val url = URL.parse("https://api.example.com/users?active=true").toOption.get
|
|
86
|
-
val request = Request.get(url)
|
|
87
|
-
|
|
88
|
-
val withHeader = request.addHeader("authorization", "Bearer token123")
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
Creating a JSON response:
|
|
92
|
-
|
|
93
|
-
```scala
|
|
94
|
-
import zio.http._
|
|
95
|
-
|
|
96
|
-
val jsonBody = Body.fromString("""{"message":"ok"}""", Charset.UTF8)
|
|
97
|
-
val response = Response(
|
|
98
|
-
status = Status.Ok,
|
|
99
|
-
body = jsonBody,
|
|
100
|
-
headers = Headers("content-type" -> "application/json")
|
|
101
|
-
)
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
## Method
|
|
105
|
-
|
|
106
|
-
`Method` represents standard HTTP methods as case objects:
|
|
107
|
-
|
|
108
|
-
```scala
|
|
109
|
-
sealed abstract class Method(val name: String, val ordinal: Int)
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
### Predefined Methods
|
|
113
|
-
|
|
114
|
-
```scala
|
|
115
|
-
import zio.http.Method
|
|
116
|
-
|
|
117
|
-
val get = Method.GET
|
|
118
|
-
val post = Method.POST
|
|
119
|
-
val put = Method.PUT
|
|
120
|
-
val delete = Method.DELETE
|
|
121
|
-
val patch = Method.PATCH
|
|
122
|
-
val head = Method.HEAD
|
|
123
|
-
val options = Method.OPTIONS
|
|
124
|
-
val trace = Method.TRACE
|
|
125
|
-
val connect = Method.CONNECT
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
### Parsing
|
|
129
|
-
|
|
130
|
-
```scala
|
|
131
|
-
import zio.http.Method
|
|
132
|
-
|
|
133
|
-
Method.fromString("GET") // Some(Method.GET)
|
|
134
|
-
Method.fromString("POST") // Some(Method.POST)
|
|
135
|
-
Method.fromString("CUSTOM") // None (unknown method)
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
### Rendering
|
|
139
|
-
|
|
140
|
-
```scala
|
|
141
|
-
import zio.http.Method
|
|
142
|
-
|
|
143
|
-
Method.render(Method.GET) // "GET"
|
|
144
|
-
Method.GET.name // "GET"
|
|
145
|
-
Method.GET.toString // "GET"
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
## Status
|
|
149
|
-
|
|
150
|
-
`Status` is an opaque type alias for `Int` in Scala 3 (AnyVal wrapper in Scala 2.13), providing zero-allocation status codes with predefined constants.
|
|
151
|
-
|
|
152
|
-
```scala
|
|
153
|
-
opaque type Status = Int // Scala 3
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
### Predefined Status Codes
|
|
157
|
-
|
|
158
|
-
Status codes are organized by category:
|
|
159
|
-
|
|
160
|
-
```scala
|
|
161
|
-
import zio.http.Status
|
|
162
|
-
|
|
163
|
-
// 1xx Informational
|
|
164
|
-
Status.Continue // 100
|
|
165
|
-
Status.SwitchingProtocols // 101
|
|
166
|
-
|
|
167
|
-
// 2xx Success
|
|
168
|
-
Status.Ok // 200
|
|
169
|
-
Status.Created // 201
|
|
170
|
-
Status.NoContent // 204
|
|
171
|
-
|
|
172
|
-
// 3xx Redirection
|
|
173
|
-
Status.MovedPermanently // 301
|
|
174
|
-
Status.Found // 302
|
|
175
|
-
Status.SeeOther // 303
|
|
176
|
-
Status.NotModified // 304
|
|
177
|
-
|
|
178
|
-
// 4xx Client Errors
|
|
179
|
-
Status.BadRequest // 400
|
|
180
|
-
Status.Unauthorized // 401
|
|
181
|
-
Status.Forbidden // 403
|
|
182
|
-
Status.NotFound // 404
|
|
183
|
-
|
|
184
|
-
// 5xx Server Errors
|
|
185
|
-
Status.InternalServerError // 500
|
|
186
|
-
Status.BadGateway // 502
|
|
187
|
-
Status.ServiceUnavailable // 503
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
### Creating Status Codes
|
|
191
|
-
|
|
192
|
-
```scala
|
|
193
|
-
import zio.http.Status
|
|
194
|
-
|
|
195
|
-
val custom = Status(418) // I'm a teapot
|
|
196
|
-
val ok = Status.fromInt(200) // Status.Ok
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
### Status Code Operations
|
|
200
|
-
|
|
201
|
-
```scala
|
|
202
|
-
import zio.http.Status
|
|
203
|
-
|
|
204
|
-
val status = Status.Ok
|
|
205
|
-
|
|
206
|
-
status.code // 200
|
|
207
|
-
status.text // "OK"
|
|
208
|
-
status.isSuccess // true (2xx)
|
|
209
|
-
status.isInformational // false (1xx)
|
|
210
|
-
status.isRedirection // false (3xx)
|
|
211
|
-
status.isClientError // false (4xx)
|
|
212
|
-
status.isServerError // false (5xx)
|
|
213
|
-
status.isError // false (4xx or 5xx)
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
## Version
|
|
217
|
-
|
|
218
|
-
`Version` represents HTTP protocol versions:
|
|
219
|
-
|
|
220
|
-
```scala
|
|
221
|
-
sealed abstract class Version(val major: Int, val minor: Int)
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
### Predefined Versions
|
|
225
|
-
|
|
226
|
-
```scala
|
|
227
|
-
import zio.http.Version
|
|
228
|
-
|
|
229
|
-
val v10 = Version.`HTTP/1.0`
|
|
230
|
-
val v11 = Version.`HTTP/1.1`
|
|
231
|
-
val v20 = Version.`HTTP/2.0`
|
|
232
|
-
val v30 = Version.`HTTP/3.0`
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
### Parsing and Rendering
|
|
236
|
-
|
|
237
|
-
```scala
|
|
238
|
-
import zio.http.Version
|
|
239
|
-
|
|
240
|
-
Version.fromString("HTTP/1.1") // Some(Version.`HTTP/1.1`)
|
|
241
|
-
Version.fromString("HTTP/2") // Some(Version.`HTTP/2.0`)
|
|
242
|
-
Version.fromString("HTTP/3") // Some(Version.`HTTP/3.0`)
|
|
243
|
-
|
|
244
|
-
Version.render(Version.`HTTP/1.1`) // "HTTP/1.1"
|
|
245
|
-
Version.`HTTP/2.0`.text // "HTTP/2.0"
|
|
246
|
-
```
|
|
247
|
-
|
|
248
|
-
## Scheme
|
|
249
|
-
|
|
250
|
-
`Scheme` represents URL schemes with support for HTTP, HTTPS, WebSocket (WS, WSS), and custom schemes:
|
|
251
|
-
|
|
252
|
-
```scala
|
|
253
|
-
sealed trait Scheme {
|
|
254
|
-
def text: String
|
|
255
|
-
def defaultPort: Option[Int]
|
|
256
|
-
def isSecure: Boolean
|
|
257
|
-
def isWebSocket: Boolean
|
|
258
|
-
}
|
|
259
|
-
```
|
|
260
|
-
|
|
261
|
-
### Predefined Schemes
|
|
262
|
-
|
|
263
|
-
```scala
|
|
264
|
-
import zio.http.Scheme
|
|
265
|
-
|
|
266
|
-
val http = Scheme.HTTP // http://, port 80
|
|
267
|
-
val https = Scheme.HTTPS // https://, port 443
|
|
268
|
-
val ws = Scheme.WS // ws://, port 80
|
|
269
|
-
val wss = Scheme.WSS // wss://, port 443
|
|
270
|
-
```
|
|
271
|
-
|
|
272
|
-
### Scheme Properties
|
|
273
|
-
|
|
274
|
-
```scala
|
|
275
|
-
import zio.http.Scheme
|
|
276
|
-
|
|
277
|
-
Scheme.HTTPS.isSecure // true
|
|
278
|
-
Scheme.WS.isWebSocket // true
|
|
279
|
-
Scheme.HTTP.defaultPort // Some(80)
|
|
280
|
-
```
|
|
281
|
-
|
|
282
|
-
### Custom Schemes
|
|
283
|
-
|
|
284
|
-
```scala
|
|
285
|
-
import zio.http.Scheme
|
|
286
|
-
|
|
287
|
-
val custom = Scheme.Custom("git+ssh")
|
|
288
|
-
custom.text // "git+ssh"
|
|
289
|
-
custom.defaultPort // None
|
|
290
|
-
```
|
|
291
|
-
|
|
292
|
-
### Parsing
|
|
293
|
-
|
|
294
|
-
```scala
|
|
295
|
-
import zio.http.Scheme
|
|
296
|
-
|
|
297
|
-
Scheme.fromString("https") // Scheme.HTTPS
|
|
298
|
-
Scheme.fromString("wss") // Scheme.WSS
|
|
299
|
-
Scheme.fromString("custom") // Scheme.Custom("custom")
|
|
300
|
-
```
|
|
301
|
-
|
|
302
|
-
## Charset
|
|
303
|
-
|
|
304
|
-
`Charset` represents character encodings with JVM-only conversion to `java.nio.charset.Charset`:
|
|
305
|
-
|
|
306
|
-
```scala
|
|
307
|
-
sealed abstract class Charset(val name: String)
|
|
308
|
-
```
|
|
309
|
-
|
|
310
|
-
### Predefined Charsets
|
|
311
|
-
|
|
312
|
-
```scala
|
|
313
|
-
import zio.http.Charset
|
|
314
|
-
|
|
315
|
-
val utf8 = Charset.UTF8 // "UTF-8"
|
|
316
|
-
val ascii = Charset.ASCII // "US-ASCII"
|
|
317
|
-
val iso88591 = Charset.ISO_8859_1 // "ISO-8859-1"
|
|
318
|
-
val utf16 = Charset.UTF16 // "UTF-16"
|
|
319
|
-
val utf16be = Charset.UTF16BE // "UTF-16BE"
|
|
320
|
-
val utf16le = Charset.UTF16LE // "UTF-16LE"
|
|
321
|
-
```
|
|
322
|
-
|
|
323
|
-
### Parsing
|
|
324
|
-
|
|
325
|
-
```scala
|
|
326
|
-
import zio.http.Charset
|
|
327
|
-
|
|
328
|
-
Charset.fromString("UTF-8") // Some(Charset.UTF8)
|
|
329
|
-
Charset.fromString("utf8") // Some(Charset.UTF8) (case-insensitive)
|
|
330
|
-
Charset.fromString("ISO-8859-1") // Some(Charset.ISO_8859_1)
|
|
331
|
-
Charset.fromString("LATIN1") // Some(Charset.ISO_8859_1) (alias)
|
|
332
|
-
```
|
|
333
|
-
|
|
334
|
-
## Boundary
|
|
335
|
-
|
|
336
|
-
`Boundary` represents multipart form-data boundaries:
|
|
337
|
-
|
|
338
|
-
```scala
|
|
339
|
-
import zio.http.Boundary
|
|
340
|
-
|
|
341
|
-
val boundary = Boundary("----WebKitFormBoundary7MA4YWxkTrZu0gW")
|
|
342
|
-
boundary.value // "----WebKitFormBoundary7MA4YWxkTrZu0gW"
|
|
343
|
-
boundary.toString // "----WebKitFormBoundary7MA4YWxkTrZu0gW"
|
|
344
|
-
```
|
|
345
|
-
|
|
346
|
-
### Generating Boundaries
|
|
347
|
-
|
|
348
|
-
```scala
|
|
349
|
-
import zio.http.Boundary
|
|
350
|
-
|
|
351
|
-
val generated = Boundary.generate // random 24-character alphanumeric string
|
|
352
|
-
```
|
|
353
|
-
|
|
354
|
-
## PercentEncoder
|
|
355
|
-
|
|
356
|
-
`PercentEncoder` provides RFC 3986 percent-encoding for URL components. Each URL component has different encoding rules:
|
|
357
|
-
|
|
358
|
-
```scala
|
|
359
|
-
import zio.http.PercentEncoder
|
|
360
|
-
import zio.http.PercentEncoder.ComponentType
|
|
361
|
-
|
|
362
|
-
val segment = PercentEncoder.encode("hello world", ComponentType.PathSegment)
|
|
363
|
-
// "hello%20world"
|
|
364
|
-
|
|
365
|
-
val queryKey = PercentEncoder.encode("filter[name]", ComponentType.QueryKey)
|
|
366
|
-
// "filter%5Bname%5D"
|
|
367
|
-
|
|
368
|
-
val decoded = PercentEncoder.decode("hello%20world")
|
|
369
|
-
// "hello world"
|
|
370
|
-
```
|
|
371
|
-
|
|
372
|
-
### Component Types
|
|
373
|
-
|
|
374
|
-
The encoder recognizes these component types:
|
|
375
|
-
|
|
376
|
-
- `PathSegment`: path segments between `/`
|
|
377
|
-
- `QueryKey`: query parameter names
|
|
378
|
-
- `QueryValue`: query parameter values
|
|
379
|
-
- `Fragment`: fragment identifiers after `#`
|
|
380
|
-
- `UserInfo`: userinfo in authority
|
|
381
|
-
|
|
382
|
-
Each type has specific rules for which characters must be percent-encoded.
|
|
383
|
-
|
|
384
|
-
## ContentType
|
|
385
|
-
|
|
386
|
-
`ContentType` combines a media type with optional charset and boundary parameters:
|
|
387
|
-
|
|
388
|
-
```scala
|
|
389
|
-
import zio.http.{ContentType, Charset, Boundary}
|
|
390
|
-
import zio.blocks.mediatype.MediaTypes
|
|
391
|
-
|
|
392
|
-
val json = ContentType(MediaTypes.application.`json`)
|
|
393
|
-
|
|
394
|
-
val htmlUtf8 = ContentType(
|
|
395
|
-
mediaType = MediaTypes.text.`html`,
|
|
396
|
-
charset = Some(Charset.UTF8)
|
|
397
|
-
)
|
|
398
|
-
|
|
399
|
-
val multipart = ContentType(
|
|
400
|
-
mediaType = MediaTypes.multipart.`form-data`,
|
|
401
|
-
boundary = Some(Boundary("----boundary"))
|
|
402
|
-
)
|
|
403
|
-
```
|
|
404
|
-
|
|
405
|
-
### Parsing
|
|
406
|
-
|
|
407
|
-
```scala
|
|
408
|
-
import zio.http.ContentType
|
|
409
|
-
|
|
410
|
-
ContentType.parse("application/json")
|
|
411
|
-
// Right(ContentType(MediaType("application", "json")))
|
|
412
|
-
|
|
413
|
-
ContentType.parse("text/html; charset=utf-8")
|
|
414
|
-
// Right(ContentType(..., charset = Some(Charset.UTF8)))
|
|
415
|
-
|
|
416
|
-
ContentType.parse("multipart/form-data; boundary=abc123")
|
|
417
|
-
// Right(ContentType(..., boundary = Some(Boundary("abc123"))))
|
|
418
|
-
|
|
419
|
-
ContentType.parse("")
|
|
420
|
-
// Left("Invalid content type: cannot be empty")
|
|
421
|
-
```
|
|
422
|
-
|
|
423
|
-
### Rendering
|
|
424
|
-
|
|
425
|
-
```scala
|
|
426
|
-
import zio.http.{ContentType, Charset}
|
|
427
|
-
import zio.blocks.mediatype.MediaTypes
|
|
428
|
-
|
|
429
|
-
val ct = ContentType(
|
|
430
|
-
MediaTypes.text.`plain`,
|
|
431
|
-
charset = Some(Charset.UTF8)
|
|
432
|
-
)
|
|
433
|
-
|
|
434
|
-
ct.render // "text/plain; charset=UTF-8"
|
|
435
|
-
```
|
|
436
|
-
|
|
437
|
-
### Predefined Content Types
|
|
438
|
-
|
|
439
|
-
```scala
|
|
440
|
-
import zio.http.ContentType
|
|
441
|
-
|
|
442
|
-
val json = ContentType.`application/json`
|
|
443
|
-
val plain = ContentType.`text/plain`
|
|
444
|
-
val html = ContentType.`text/html`
|
|
445
|
-
val binary = ContentType.`application/octet-stream`
|
|
446
|
-
```
|
|
447
|
-
|
|
448
|
-
## Path
|
|
449
|
-
|
|
450
|
-
`Path` represents URL paths with decoded segments stored internally:
|
|
451
|
-
|
|
452
|
-
```scala
|
|
453
|
-
final case class Path(
|
|
454
|
-
segments: Chunk[String],
|
|
455
|
-
hasLeadingSlash: Boolean,
|
|
456
|
-
trailingSlash: Boolean
|
|
457
|
-
)
|
|
458
|
-
```
|
|
459
|
-
|
|
460
|
-
Paths use a **single encoding contract**: decode on input, store decoded, encode on output.
|
|
461
|
-
|
|
462
|
-
### Creating Paths
|
|
463
|
-
|
|
464
|
-
```scala
|
|
465
|
-
import zio.http.Path
|
|
466
|
-
|
|
467
|
-
val empty = Path.empty // ""
|
|
468
|
-
val root = Path.root // "/"
|
|
469
|
-
val users = Path("/users") // segments: ["users"], leading slash: true
|
|
470
|
-
val api = Path("api/v1/users/") // segments: ["api", "v1", "users"], trailing slash: true
|
|
471
|
-
```
|
|
472
|
-
|
|
473
|
-
### Parsing Encoded Paths
|
|
474
|
-
|
|
475
|
-
`Path.fromEncoded` decodes percent-encoded segments:
|
|
476
|
-
|
|
477
|
-
```scala
|
|
478
|
-
import zio.http.Path
|
|
479
|
-
|
|
480
|
-
val path = Path.fromEncoded("/hello%20world/foo%2Fbar")
|
|
481
|
-
// Path(Chunk("hello world", "foo/bar"), hasLeadingSlash = true, trailingSlash = false)
|
|
482
|
-
|
|
483
|
-
path.segments(0) // "hello world" (decoded)
|
|
484
|
-
path.segments(1) // "foo/bar" (decoded)
|
|
485
|
-
```
|
|
486
|
-
|
|
487
|
-
### Building Paths
|
|
488
|
-
|
|
489
|
-
```scala
|
|
490
|
-
import zio.http.Path
|
|
491
|
-
|
|
492
|
-
val base = Path("/api")
|
|
493
|
-
val extended = base / "users" / "123" // "/api/users/123"
|
|
494
|
-
|
|
495
|
-
val combined = Path("/api") ++ Path("v1/users") // "/api/v1/users"
|
|
496
|
-
```
|
|
497
|
-
|
|
498
|
-
### Encoding Paths
|
|
499
|
-
|
|
500
|
-
```scala
|
|
501
|
-
import zio.http.Path
|
|
502
|
-
import zio.blocks.chunk.Chunk
|
|
503
|
-
|
|
504
|
-
val path = Path(Chunk("hello world", "foo/bar"), hasLeadingSlash = true, trailingSlash = false)
|
|
505
|
-
|
|
506
|
-
path.encode // "/hello%20world/foo%2Fbar" (percent-encoded)
|
|
507
|
-
path.render // "/hello world/foo/bar" (decoded for display)
|
|
508
|
-
```
|
|
509
|
-
|
|
510
|
-
### Path Properties
|
|
511
|
-
|
|
512
|
-
```scala
|
|
513
|
-
import zio.http.Path
|
|
514
|
-
|
|
515
|
-
val path = Path("/api/v1/users/")
|
|
516
|
-
|
|
517
|
-
path.isEmpty // false
|
|
518
|
-
path.nonEmpty // true
|
|
519
|
-
path.length // 3 (number of segments)
|
|
520
|
-
path.hasLeadingSlash // true
|
|
521
|
-
path.trailingSlash // true
|
|
522
|
-
```
|
|
523
|
-
|
|
524
|
-
### Path Navigation
|
|
525
|
-
|
|
526
|
-
```scala
|
|
527
|
-
import zio.http.Path
|
|
528
|
-
|
|
529
|
-
val path = Path("/api/v1/users")
|
|
530
|
-
|
|
531
|
-
// Slash manipulation
|
|
532
|
-
path.addLeadingSlash // same (already has one)
|
|
533
|
-
path.dropLeadingSlash // "api/v1/users" (relative)
|
|
534
|
-
path.addTrailingSlash // "/api/v1/users/"
|
|
535
|
-
path.dropTrailingSlash // same (already no trailing slash)
|
|
536
|
-
|
|
537
|
-
// Inspecting
|
|
538
|
-
path.isRoot // false (root is "/" with no segments)
|
|
539
|
-
Path.root.isRoot // true
|
|
540
|
-
|
|
541
|
-
// Prefix checking
|
|
542
|
-
path.startsWith(Path("api/v1")) // true
|
|
543
|
-
|
|
544
|
-
// Slicing
|
|
545
|
-
path.drop(1) // "/v1/users" (drop first segment)
|
|
546
|
-
path.take(2) // "/api/v1" (take first 2 segments)
|
|
547
|
-
path.dropRight(1) // "/api/v1" (drop last segment)
|
|
548
|
-
path.initial // "/api/v1" (all but last)
|
|
549
|
-
path.last // Some("users")
|
|
550
|
-
path.reverse // "/users/v1/api"
|
|
551
|
-
```
|
|
552
|
-
|
|
553
|
-
## QueryParams
|
|
554
|
-
|
|
555
|
-
`QueryParams` stores query parameters with multiple values per key:
|
|
556
|
-
|
|
557
|
-
```scala
|
|
558
|
-
final class QueryParams private[http] (
|
|
559
|
-
private val keys: Array[String],
|
|
560
|
-
private val vals: Array[Chunk[String]],
|
|
561
|
-
val size: Int
|
|
562
|
-
)
|
|
563
|
-
```
|
|
564
|
-
|
|
565
|
-
Like `Path`, query parameters store decoded values internally and encode only on output.
|
|
566
|
-
|
|
567
|
-
### Creating QueryParams
|
|
568
|
-
|
|
569
|
-
```scala
|
|
570
|
-
import zio.http.QueryParams
|
|
571
|
-
|
|
572
|
-
val empty = QueryParams.empty
|
|
573
|
-
|
|
574
|
-
val params = QueryParams(
|
|
575
|
-
"name" -> "Alice",
|
|
576
|
-
"age" -> "30",
|
|
577
|
-
"active" -> "true"
|
|
578
|
-
)
|
|
579
|
-
```
|
|
580
|
-
|
|
581
|
-
### Parsing Encoded Query Strings
|
|
582
|
-
|
|
583
|
-
```scala
|
|
584
|
-
import zio.http.QueryParams
|
|
585
|
-
|
|
586
|
-
val params = QueryParams.fromEncoded("name=Alice%20Smith&age=30&active=true")
|
|
587
|
-
|
|
588
|
-
params.getFirst("name") // Some("Alice Smith") (decoded)
|
|
589
|
-
params.getFirst("age") // Some("30")
|
|
590
|
-
params.getFirst("active") // Some("true")
|
|
591
|
-
```
|
|
592
|
-
|
|
593
|
-
### Accessing Values
|
|
594
|
-
|
|
595
|
-
```scala
|
|
596
|
-
import zio.http.QueryParams
|
|
597
|
-
|
|
598
|
-
val params = QueryParams(
|
|
599
|
-
"color" -> "red",
|
|
600
|
-
"color" -> "blue",
|
|
601
|
-
"size" -> "large"
|
|
602
|
-
)
|
|
603
|
-
|
|
604
|
-
params.get("color") // Some(Chunk("red", "blue"))
|
|
605
|
-
params.getFirst("color") // Some("red")
|
|
606
|
-
params.getFirst("size") // Some("large")
|
|
607
|
-
params.getFirst("other") // None
|
|
608
|
-
params.has("color") // true
|
|
609
|
-
```
|
|
610
|
-
|
|
611
|
-
### Modifying QueryParams
|
|
612
|
-
|
|
613
|
-
```scala
|
|
614
|
-
import zio.http.QueryParams
|
|
615
|
-
|
|
616
|
-
val params = QueryParams("a" -> "1", "b" -> "2")
|
|
617
|
-
|
|
618
|
-
val added = params.add("c", "3") // adds "c=3"
|
|
619
|
-
val set = params.set("a", "100") // replaces all "a" values
|
|
620
|
-
val removed = params.remove("b") // removes all "b" entries
|
|
621
|
-
```
|
|
622
|
-
|
|
623
|
-
### Encoding
|
|
624
|
-
|
|
625
|
-
```scala
|
|
626
|
-
import zio.http.QueryParams
|
|
627
|
-
|
|
628
|
-
val params = QueryParams(
|
|
629
|
-
"name" -> "Alice Smith",
|
|
630
|
-
"filter[status]" -> "active"
|
|
631
|
-
)
|
|
632
|
-
|
|
633
|
-
params.encode // "name=Alice%20Smith&filter%5Bstatus%5D=active"
|
|
634
|
-
```
|
|
635
|
-
|
|
636
|
-
### Converting to List
|
|
637
|
-
|
|
638
|
-
```scala
|
|
639
|
-
import zio.http.QueryParams
|
|
640
|
-
|
|
641
|
-
val params = QueryParams("a" -> "1", "a" -> "2", "b" -> "3")
|
|
642
|
-
params.toList // List(("a", "1"), ("a", "2"), ("b", "3"))
|
|
643
|
-
```
|
|
644
|
-
|
|
645
|
-
## URL
|
|
646
|
-
|
|
647
|
-
`URL` combines scheme, host, port, path, query parameters, and fragment:
|
|
648
|
-
|
|
649
|
-
```scala
|
|
650
|
-
final case class URL(
|
|
651
|
-
scheme: Option[Scheme],
|
|
652
|
-
host: Option[String],
|
|
653
|
-
port: Option[Int],
|
|
654
|
-
path: Path,
|
|
655
|
-
queryParams: QueryParams,
|
|
656
|
-
fragment: Option[String]
|
|
657
|
-
)
|
|
658
|
-
```
|
|
659
|
-
|
|
660
|
-
### Parsing URLs
|
|
661
|
-
|
|
662
|
-
```scala
|
|
663
|
-
import zio.http.URL
|
|
664
|
-
|
|
665
|
-
val absolute = URL.parse("https://api.example.com:8080/users?active=true#results")
|
|
666
|
-
// Right(URL(
|
|
667
|
-
// scheme = Some(Scheme.HTTPS),
|
|
668
|
-
// host = Some("api.example.com"),
|
|
669
|
-
// port = Some(8080),
|
|
670
|
-
// path = Path("/users"),
|
|
671
|
-
// queryParams = QueryParams("active" -> "true"),
|
|
672
|
-
// fragment = Some("results")
|
|
673
|
-
// ))
|
|
674
|
-
|
|
675
|
-
val relative = URL.parse("/api/users?page=2")
|
|
676
|
-
// Right(URL(
|
|
677
|
-
// scheme = None,
|
|
678
|
-
// host = None,
|
|
679
|
-
// port = None,
|
|
680
|
-
// path = Path("/api/users"),
|
|
681
|
-
// queryParams = QueryParams("page" -> "2"),
|
|
682
|
-
// fragment = None
|
|
683
|
-
// ))
|
|
684
|
-
```
|
|
685
|
-
|
|
686
|
-
The parser handles:
|
|
687
|
-
|
|
688
|
-
- IPv6 hosts in brackets: `http://[::1]:8080/`
|
|
689
|
-
- Userinfo: `https://user:pass@example.com/` (userinfo is skipped)
|
|
690
|
-
- Relative URLs: `/path?query`
|
|
691
|
-
- Fragment identifiers: `#section`
|
|
692
|
-
|
|
693
|
-
### Building URLs
|
|
694
|
-
|
|
695
|
-
```scala
|
|
696
|
-
import zio.http.{URL, Path, Scheme}
|
|
697
|
-
|
|
698
|
-
val base = URL.root // http://localhost/
|
|
699
|
-
|
|
700
|
-
val extended = base / "api" / "users" // adds path segments
|
|
701
|
-
|
|
702
|
-
val withQuery = extended ?? ("active", "true") ?? ("page", "1")
|
|
703
|
-
// http://localhost/api/users?active=true&page=1
|
|
704
|
-
```
|
|
705
|
-
|
|
706
|
-
### URL from Path
|
|
707
|
-
|
|
708
|
-
```scala
|
|
709
|
-
import zio.http.{URL, Path}
|
|
710
|
-
|
|
711
|
-
val path = Path("/api/users")
|
|
712
|
-
val url = URL.fromPath(path) // relative URL with just path
|
|
713
|
-
```
|
|
714
|
-
|
|
715
|
-
### Encoding URLs
|
|
716
|
-
|
|
717
|
-
```scala
|
|
718
|
-
import zio.http.URL
|
|
719
|
-
|
|
720
|
-
val url = URL.parse("https://example.com/hello world?name=Alice Smith").toOption.get
|
|
721
|
-
|
|
722
|
-
url.encode // "https://example.com/hello%20world?name=Alice%20Smith"
|
|
723
|
-
url.toString // same as encode
|
|
724
|
-
```
|
|
725
|
-
|
|
726
|
-
### URL Properties
|
|
727
|
-
|
|
728
|
-
```scala
|
|
729
|
-
import zio.http.URL
|
|
730
|
-
|
|
731
|
-
val absolute = URL.parse("https://example.com/").toOption.get
|
|
732
|
-
val relative = URL.parse("/api/users").toOption.get
|
|
733
|
-
|
|
734
|
-
absolute.isAbsolute // true (has scheme)
|
|
735
|
-
relative.isRelative // true (no scheme)
|
|
736
|
-
```
|
|
737
|
-
|
|
738
|
-
### URL Transformation
|
|
739
|
-
|
|
740
|
-
```scala
|
|
741
|
-
import zio.http.{URL, Path, Scheme, QueryParams}
|
|
742
|
-
|
|
743
|
-
val url = URL.parse("https://api.example.com:8080/users?page=1").toOption.get
|
|
744
|
-
|
|
745
|
-
// Setting components
|
|
746
|
-
url.host("other.com") // changes host
|
|
747
|
-
url.port(9090) // changes port
|
|
748
|
-
url.scheme(Scheme.HTTP) // changes scheme
|
|
749
|
-
url.path(Path("/v2/users")) // replaces path
|
|
750
|
-
url.fragment("top") // sets fragment
|
|
751
|
-
|
|
752
|
-
// Adding paths
|
|
753
|
-
url.addPath("123") // appends segment: /users/123
|
|
754
|
-
url.addPath(Path("v2/items")) // appends multi-segment path
|
|
755
|
-
|
|
756
|
-
// Query manipulation
|
|
757
|
-
url.addQueryParams(QueryParams("sort" -> "name"))
|
|
758
|
-
url.updateQueryParams(_.remove("page"))
|
|
759
|
-
|
|
760
|
-
// Slash manipulation (delegates to path)
|
|
761
|
-
url.addLeadingSlash
|
|
762
|
-
url.dropTrailingSlash
|
|
763
|
-
|
|
764
|
-
// Derived properties
|
|
765
|
-
url.hostPort // Some("api.example.com:8080")
|
|
766
|
-
url.relative // strips scheme/host/port, keeps path and query
|
|
767
|
-
```
|
|
768
|
-
|
|
769
|
-
## Header
|
|
770
|
-
|
|
771
|
-
`Header` is a trait representing typed HTTP headers:
|
|
772
|
-
|
|
773
|
-
```scala
|
|
774
|
-
trait Header {
|
|
775
|
-
def headerName: String
|
|
776
|
-
def renderedValue: String
|
|
777
|
-
}
|
|
778
|
-
```
|
|
779
|
-
|
|
780
|
-
Each header type has a companion object implementing `Header.Typed[H]` for parsing and rendering.
|
|
781
|
-
|
|
782
|
-
### Predefined Header Types
|
|
783
|
-
|
|
784
|
-
```scala
|
|
785
|
-
import zio.http.{Header => _, *}
|
|
786
|
-
import zio.http.headers
|
|
787
|
-
|
|
788
|
-
val contentType = headers.ContentType
|
|
789
|
-
val accept = headers.Accept
|
|
790
|
-
val authorization = headers.Authorization
|
|
791
|
-
val host = headers.Host
|
|
792
|
-
val userAgent = headers.UserAgent
|
|
793
|
-
val cacheControl = headers.CacheControl
|
|
794
|
-
val contentLength = headers.ContentLength
|
|
795
|
-
val location = headers.Location
|
|
796
|
-
val setCookie = headers.SetCookieHeader
|
|
797
|
-
val cookie = headers.CookieHeader
|
|
798
|
-
```
|
|
799
|
-
|
|
800
|
-
### Creating Typed Headers
|
|
801
|
-
|
|
802
|
-
```scala
|
|
803
|
-
import zio.http.{Header => _, ContentType, Charset, *}
|
|
804
|
-
import zio.http.headers
|
|
805
|
-
import zio.blocks.mediatype.MediaTypes
|
|
806
|
-
|
|
807
|
-
val ct = headers.ContentType(
|
|
808
|
-
ContentType(MediaTypes.application.`json`, charset = Some(Charset.UTF8))
|
|
809
|
-
)
|
|
810
|
-
|
|
811
|
-
val auth = headers.Authorization.Bearer("token123")
|
|
812
|
-
|
|
813
|
-
val host = headers.Host("api.example.com", Some(8080))
|
|
814
|
-
```
|
|
815
|
-
|
|
816
|
-
### Parsing Headers
|
|
817
|
-
|
|
818
|
-
```scala
|
|
819
|
-
import zio.http.{Header => _, *}
|
|
820
|
-
import zio.http.headers
|
|
821
|
-
|
|
822
|
-
headers.ContentType.parse("application/json; charset=utf-8")
|
|
823
|
-
// Right(headers.ContentType(...))
|
|
824
|
-
|
|
825
|
-
headers.Host.parse("example.com:443")
|
|
826
|
-
// Right(headers.Host("example.com", Some(443)))
|
|
827
|
-
|
|
828
|
-
headers.ContentLength.parse("1024")
|
|
829
|
-
// Right(headers.ContentLength(1024))
|
|
830
|
-
|
|
831
|
-
headers.ContentLength.parse("-1")
|
|
832
|
-
// Left("Invalid content-length: -1")
|
|
833
|
-
```
|
|
834
|
-
|
|
835
|
-
### Custom Headers
|
|
836
|
-
|
|
837
|
-
```scala
|
|
838
|
-
import zio.http.Header
|
|
839
|
-
|
|
840
|
-
val custom = Header.Custom("x-request-id", "abc-123")
|
|
841
|
-
custom.headerName // "x-request-id"
|
|
842
|
-
custom.renderedValue // "abc-123"
|
|
843
|
-
```
|
|
844
|
-
|
|
845
|
-
## Headers
|
|
846
|
-
|
|
847
|
-
`Headers` is a flat array-based collection with lazy monotonic parsing:
|
|
848
|
-
|
|
849
|
-
```scala
|
|
850
|
-
final class Headers private[http] (
|
|
851
|
-
private val names: Array[String],
|
|
852
|
-
private val rawValues: Array[String],
|
|
853
|
-
private val parsed: Array[AnyRef], // null -> unparsed, value -> cached
|
|
854
|
-
val size: Int
|
|
855
|
-
)
|
|
856
|
-
```
|
|
857
|
-
|
|
858
|
-
When you call `get[H]`, the header is parsed once and cached. Subsequent calls return the cached result.
|
|
859
|
-
|
|
860
|
-
### Creating Headers
|
|
861
|
-
|
|
862
|
-
```scala
|
|
863
|
-
import zio.http.Headers
|
|
864
|
-
|
|
865
|
-
val empty = Headers.empty
|
|
866
|
-
|
|
867
|
-
val headers = Headers(
|
|
868
|
-
"content-type" -> "application/json",
|
|
869
|
-
"authorization" -> "Bearer token",
|
|
870
|
-
"x-request-id" -> "abc-123"
|
|
871
|
-
)
|
|
872
|
-
```
|
|
873
|
-
|
|
874
|
-
### Getting Typed Headers
|
|
875
|
-
|
|
876
|
-
```scala
|
|
877
|
-
import zio.http.{Headers, *}
|
|
878
|
-
import zio.http.{headers => h}
|
|
879
|
-
|
|
880
|
-
val headers = Headers(
|
|
881
|
-
"content-type" -> "application/json",
|
|
882
|
-
"content-length" -> "1024"
|
|
883
|
-
)
|
|
884
|
-
|
|
885
|
-
val ct = headers.get(h.ContentType)
|
|
886
|
-
// Some(h.ContentType(...)) (parsed and cached)
|
|
887
|
-
|
|
888
|
-
val cl = headers.get(h.ContentLength)
|
|
889
|
-
// Some(h.ContentLength(1024)) (parsed and cached)
|
|
890
|
-
|
|
891
|
-
val auth = headers.get(h.Authorization)
|
|
892
|
-
// None (not present)
|
|
893
|
-
```
|
|
894
|
-
|
|
895
|
-
### Getting Raw Values
|
|
896
|
-
|
|
897
|
-
```scala
|
|
898
|
-
import zio.http.Headers
|
|
899
|
-
|
|
900
|
-
val headers = Headers("x-custom" -> "value")
|
|
901
|
-
|
|
902
|
-
headers.rawGet("x-custom") // Some("value")
|
|
903
|
-
headers.rawGet("missing") // None
|
|
904
|
-
```
|
|
905
|
-
|
|
906
|
-
### Getting All Headers of a Type
|
|
907
|
-
|
|
908
|
-
Some headers can appear multiple times (like `Set-Cookie`):
|
|
909
|
-
|
|
910
|
-
```scala
|
|
911
|
-
import zio.http.{Headers, *}
|
|
912
|
-
import zio.http.{headers => h}
|
|
913
|
-
|
|
914
|
-
val headers = Headers(
|
|
915
|
-
"set-cookie" -> "session=abc",
|
|
916
|
-
"set-cookie" -> "preference=dark"
|
|
917
|
-
)
|
|
918
|
-
|
|
919
|
-
val cookies = headers.getAll(h.SetCookieHeader)
|
|
920
|
-
// Chunk(h.SetCookieHeader(...), h.SetCookieHeader(...))
|
|
921
|
-
```
|
|
922
|
-
|
|
923
|
-
### Modifying Headers
|
|
924
|
-
|
|
925
|
-
```scala
|
|
926
|
-
import zio.http.Headers
|
|
927
|
-
|
|
928
|
-
val headers = Headers("a" -> "1", "b" -> "2")
|
|
929
|
-
|
|
930
|
-
val added = headers.add("c", "3") // adds "c: 3"
|
|
931
|
-
val set = headers.set("a", "100") // replaces all "a" values
|
|
932
|
-
val removed = headers.remove("b") // removes all "b" entries
|
|
933
|
-
val has = headers.has("a") // true
|
|
934
|
-
```
|
|
935
|
-
|
|
936
|
-
### Converting to List
|
|
937
|
-
|
|
938
|
-
```scala
|
|
939
|
-
import zio.http.Headers
|
|
940
|
-
|
|
941
|
-
val headers = Headers("a" -> "1", "b" -> "2")
|
|
942
|
-
headers.toList // List(("a", "1"), ("b", "2"))
|
|
943
|
-
```
|
|
944
|
-
|
|
945
|
-
### Combining Headers
|
|
946
|
-
|
|
947
|
-
```scala
|
|
948
|
-
import zio.http.Headers
|
|
949
|
-
|
|
950
|
-
val auth = Headers("authorization" -> "Bearer token")
|
|
951
|
-
val cors = Headers("access-control-allow-origin" -> "*")
|
|
952
|
-
|
|
953
|
-
val combined = auth ++ cors // Headers(authorization: ..., access-control-allow-origin: ...)
|
|
954
|
-
|
|
955
|
-
combined.contains("authorization") // true (alias for `has`)
|
|
956
|
-
combined.toChunk // Chunk(("authorization", ...), ("access-control-allow-origin", ...))
|
|
957
|
-
```
|
|
958
|
-
|
|
959
|
-
## Body
|
|
960
|
-
|
|
961
|
-
`Body` wraps a materialized `Chunk[Byte]` with a content type:
|
|
962
|
-
|
|
963
|
-
```scala
|
|
964
|
-
final class Body private (
|
|
965
|
-
val data: Chunk[Byte],
|
|
966
|
-
val contentType: ContentType
|
|
967
|
-
)
|
|
968
|
-
```
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
### Creating Bodies
|
|
972
|
-
|
|
973
|
-
`Body.empty` provides an empty body with default `application/octet-stream` content type:
|
|
974
|
-
|
|
975
|
-
```scala
|
|
976
|
-
import zio.http.Body
|
|
977
|
-
|
|
978
|
-
val empty = Body.empty
|
|
979
|
-
// Body(data = Chunk.empty, contentType = application/octet-stream)
|
|
980
|
-
```
|
|
981
|
-
|
|
982
|
-
`Body.fromString` creates a body with `text/plain` content type:
|
|
983
|
-
|
|
984
|
-
```scala
|
|
985
|
-
import zio.http.{Body, Charset}
|
|
986
|
-
|
|
987
|
-
val fromString = Body.fromString("Hello, World!", Charset.UTF8)
|
|
988
|
-
// Content-Type: text/plain; charset=UTF-8
|
|
989
|
-
```
|
|
990
|
-
|
|
991
|
-
`Body.fromArray` creates a body with default `application/octet-stream` content type:
|
|
992
|
-
|
|
993
|
-
```scala
|
|
994
|
-
import zio.http.Body
|
|
995
|
-
|
|
996
|
-
val fromBytes = Body.fromArray(Array[Byte](1, 2, 3))
|
|
997
|
-
// Content-Type: application/octet-stream
|
|
998
|
-
```
|
|
999
|
-
|
|
1000
|
-
`Body.fromChunk` creates a body from a `Chunk[Byte]` with optional content type:
|
|
1001
|
-
|
|
1002
|
-
```scala
|
|
1003
|
-
import zio.http.{Body, ContentType}
|
|
1004
|
-
import zio.blocks.chunk.Chunk
|
|
1005
|
-
import zio.blocks.mediatype.MediaTypes
|
|
1006
|
-
|
|
1007
|
-
val chunk = Chunk[Byte](1, 2, 3, 4, 5)
|
|
1008
|
-
val body = Body.fromChunk(chunk)
|
|
1009
|
-
// Content-Type: application/octet-stream (default)
|
|
1010
|
-
|
|
1011
|
-
val jsonBody = Body.fromChunk(chunk, ContentType(MediaTypes.application.`json`))
|
|
1012
|
-
// Content-Type: application/json
|
|
1013
|
-
```
|
|
1014
|
-
|
|
1015
|
-
### Reading Bodies
|
|
1016
|
-
|
|
1017
|
-
`Body` provides direct access to data and content type:
|
|
1018
|
-
|
|
1019
|
-
```scala
|
|
1020
|
-
import zio.http.{Body, Charset}
|
|
1021
|
-
|
|
1022
|
-
val body = Body.fromString("Hello!", Charset.UTF8)
|
|
1023
|
-
|
|
1024
|
-
body.length // 6
|
|
1025
|
-
body.isEmpty // false
|
|
1026
|
-
body.nonEmpty // true
|
|
1027
|
-
body.asString() // "Hello!" (UTF-8 default)
|
|
1028
|
-
body.asString(Charset.ASCII) // "Hello!" (explicit charset)
|
|
1029
|
-
body.data // Chunk[Byte](72, 101, 108, 108, 111, 33)
|
|
1030
|
-
body.contentType // ContentType(text/plain; charset=UTF-8)
|
|
1031
|
-
```
|
|
1032
|
-
|
|
1033
|
-
## Cookie
|
|
1034
|
-
|
|
1035
|
-
Cookies are split into `RequestCookie` and `ResponseCookie` with different structures:
|
|
1036
|
-
|
|
1037
|
-
```scala
|
|
1038
|
-
final case class RequestCookie(name: String, value: String)
|
|
1039
|
-
|
|
1040
|
-
final case class ResponseCookie(
|
|
1041
|
-
name: String,
|
|
1042
|
-
value: String,
|
|
1043
|
-
domain: Option[String],
|
|
1044
|
-
path: Option[Path],
|
|
1045
|
-
maxAge: Option[Long],
|
|
1046
|
-
isSecure: Boolean,
|
|
1047
|
-
isHttpOnly: Boolean,
|
|
1048
|
-
sameSite: Option[SameSite]
|
|
1049
|
-
)
|
|
1050
|
-
```
|
|
1051
|
-
|
|
1052
|
-
### SameSite
|
|
1053
|
-
|
|
1054
|
-
```scala
|
|
1055
|
-
import zio.http.SameSite
|
|
1056
|
-
|
|
1057
|
-
val strict = SameSite.Strict
|
|
1058
|
-
val lax = SameSite.Lax
|
|
1059
|
-
val none = SameSite.None_ // underscore avoids conflict with scala.None
|
|
1060
|
-
```
|
|
1061
|
-
|
|
1062
|
-
### Parsing Request Cookies
|
|
1063
|
-
|
|
1064
|
-
```scala
|
|
1065
|
-
import zio.http.Cookie
|
|
1066
|
-
|
|
1067
|
-
val cookies = Cookie.parseRequest("session=abc123; preference=dark")
|
|
1068
|
-
// Chunk(RequestCookie("session", "abc123"), RequestCookie("preference", "dark"))
|
|
1069
|
-
```
|
|
1070
|
-
|
|
1071
|
-
### Parsing Response Cookies
|
|
1072
|
-
|
|
1073
|
-
```scala
|
|
1074
|
-
import zio.http.Cookie
|
|
1075
|
-
|
|
1076
|
-
val cookie = Cookie.parseResponse("session=abc; Domain=example.com; Path=/; Secure; HttpOnly; SameSite=Strict")
|
|
1077
|
-
// Right(ResponseCookie(
|
|
1078
|
-
// name = "session",
|
|
1079
|
-
// value = "abc",
|
|
1080
|
-
// domain = Some("example.com"),
|
|
1081
|
-
// path = Some(Path("/")),
|
|
1082
|
-
// isSecure = true,
|
|
1083
|
-
// isHttpOnly = true,
|
|
1084
|
-
// sameSite = Some(SameSite.Strict)
|
|
1085
|
-
// ))
|
|
1086
|
-
```
|
|
1087
|
-
|
|
1088
|
-
### Rendering Cookies
|
|
1089
|
-
|
|
1090
|
-
```scala
|
|
1091
|
-
import zio.http.{Cookie, RequestCookie, ResponseCookie, SameSite, Path}
|
|
1092
|
-
import zio.blocks.chunk.Chunk
|
|
1093
|
-
|
|
1094
|
-
val requestCookies = Chunk(
|
|
1095
|
-
RequestCookie("session", "abc"),
|
|
1096
|
-
RequestCookie("theme", "dark")
|
|
1097
|
-
)
|
|
1098
|
-
Cookie.renderRequest(requestCookies)
|
|
1099
|
-
// "session=abc; theme=dark"
|
|
1100
|
-
|
|
1101
|
-
val responseCookie = ResponseCookie(
|
|
1102
|
-
name = "session",
|
|
1103
|
-
value = "xyz",
|
|
1104
|
-
domain = Some("example.com"),
|
|
1105
|
-
path = Some(Path("/")),
|
|
1106
|
-
maxAge = Some(3600),
|
|
1107
|
-
isSecure = true,
|
|
1108
|
-
isHttpOnly = true,
|
|
1109
|
-
sameSite = Some(SameSite.Strict)
|
|
1110
|
-
)
|
|
1111
|
-
Cookie.renderResponse(responseCookie)
|
|
1112
|
-
// "session=xyz; Domain=example.com; Path=/; Max-Age=3600; Secure; HttpOnly; SameSite=Strict"
|
|
1113
|
-
```
|
|
1114
|
-
|
|
1115
|
-
## Form
|
|
1116
|
-
|
|
1117
|
-
`Form` represents URL-encoded form data:
|
|
1118
|
-
|
|
1119
|
-
```scala
|
|
1120
|
-
final case class Form(entries: Chunk[(String, String)])
|
|
1121
|
-
```
|
|
1122
|
-
|
|
1123
|
-
### Creating Forms
|
|
1124
|
-
|
|
1125
|
-
```scala
|
|
1126
|
-
import zio.http.Form
|
|
1127
|
-
|
|
1128
|
-
val empty = Form.empty
|
|
1129
|
-
|
|
1130
|
-
val form = Form(
|
|
1131
|
-
"username" -> "alice",
|
|
1132
|
-
"password" -> "secret",
|
|
1133
|
-
"remember" -> "true"
|
|
1134
|
-
)
|
|
1135
|
-
```
|
|
1136
|
-
|
|
1137
|
-
### Accessing Form Data
|
|
1138
|
-
|
|
1139
|
-
```scala
|
|
1140
|
-
import zio.http.Form
|
|
1141
|
-
|
|
1142
|
-
val form = Form(
|
|
1143
|
-
"tag" -> "scala",
|
|
1144
|
-
"tag" -> "functional",
|
|
1145
|
-
"page" -> "1"
|
|
1146
|
-
)
|
|
1147
|
-
|
|
1148
|
-
form.get("tag") // Some("scala") (first value)
|
|
1149
|
-
form.getAll("tag") // Chunk("scala", "functional")
|
|
1150
|
-
form.get("page") // Some("1")
|
|
1151
|
-
form.get("missing") // None
|
|
1152
|
-
```
|
|
1153
|
-
|
|
1154
|
-
### Modifying Forms
|
|
1155
|
-
|
|
1156
|
-
```scala
|
|
1157
|
-
import zio.http.Form
|
|
1158
|
-
|
|
1159
|
-
val form = Form("a" -> "1")
|
|
1160
|
-
val added = form.add("b", "2") // Form(("a", "1"), ("b", "2"))
|
|
1161
|
-
```
|
|
1162
|
-
|
|
1163
|
-
### Encoding and Parsing
|
|
1164
|
-
|
|
1165
|
-
```scala
|
|
1166
|
-
import zio.http.Form
|
|
1167
|
-
|
|
1168
|
-
val form = Form(
|
|
1169
|
-
"name" -> "Alice Smith",
|
|
1170
|
-
"email" -> "alice@example.com"
|
|
1171
|
-
)
|
|
1172
|
-
|
|
1173
|
-
val encoded = form.encode
|
|
1174
|
-
// "name=Alice%20Smith&email=alice%40example.com"
|
|
1175
|
-
|
|
1176
|
-
val parsed = Form.fromString(encoded)
|
|
1177
|
-
// Form with decoded entries
|
|
1178
|
-
```
|
|
1179
|
-
|
|
1180
|
-
## FormField
|
|
1181
|
-
|
|
1182
|
-
`FormField` is a sealed trait for multipart form fields, supporting simple key-value pairs, text parts with optional metadata, and binary parts:
|
|
1183
|
-
|
|
1184
|
-
```scala
|
|
1185
|
-
import zio.http._
|
|
1186
|
-
import zio.blocks.chunk.Chunk
|
|
1187
|
-
import zio.blocks.mediatype.MediaTypes
|
|
1188
|
-
|
|
1189
|
-
// Simple key-value field
|
|
1190
|
-
val simple = FormField.Simple("username", "alice")
|
|
1191
|
-
|
|
1192
|
-
// Text field with optional content type and filename
|
|
1193
|
-
val text = FormField.Text(
|
|
1194
|
-
name = "bio",
|
|
1195
|
-
value = "Hello world",
|
|
1196
|
-
contentType = Some(ContentType(MediaTypes.text.`plain`)),
|
|
1197
|
-
filename = None
|
|
1198
|
-
)
|
|
1199
|
-
|
|
1200
|
-
// Binary field with content type and optional filename
|
|
1201
|
-
val binary = FormField.Binary(
|
|
1202
|
-
name = "avatar",
|
|
1203
|
-
data = Chunk.fromArray(Array[Byte](1, 2, 3)),
|
|
1204
|
-
contentType = ContentType(MediaTypes.image.`png`),
|
|
1205
|
-
filename = Some("avatar.png")
|
|
1206
|
-
)
|
|
1207
|
-
|
|
1208
|
-
// All variants share a common `name` accessor
|
|
1209
|
-
simple.name // "username"
|
|
1210
|
-
text.name // "bio"
|
|
1211
|
-
binary.name // "avatar"
|
|
1212
|
-
```
|
|
1213
|
-
|
|
1214
|
-
## Request
|
|
1215
|
-
|
|
1216
|
-
`Request` combines all HTTP request components:
|
|
1217
|
-
|
|
1218
|
-
```scala
|
|
1219
|
-
final case class Request(
|
|
1220
|
-
method: Method,
|
|
1221
|
-
url: URL,
|
|
1222
|
-
headers: Headers,
|
|
1223
|
-
body: Body,
|
|
1224
|
-
version: Version
|
|
1225
|
-
)
|
|
1226
|
-
```
|
|
1227
|
-
|
|
1228
|
-
### Creating Requests
|
|
1229
|
-
|
|
1230
|
-
```scala
|
|
1231
|
-
import zio.http._
|
|
1232
|
-
|
|
1233
|
-
val url = URL.parse("https://api.example.com/users").toOption.get
|
|
1234
|
-
val getRequest = Request.get(url)
|
|
1235
|
-
|
|
1236
|
-
val jsonBody = Body.fromString("""{"name":"Alice"}""", Charset.UTF8)
|
|
1237
|
-
val postRequest = Request.post(url, jsonBody)
|
|
1238
|
-
```
|
|
1239
|
-
|
|
1240
|
-
### Request Factory Methods
|
|
1241
|
-
|
|
1242
|
-
```scala
|
|
1243
|
-
import zio.http._
|
|
1244
|
-
|
|
1245
|
-
val url = URL.parse("https://api.example.com/resource/1").toOption.get
|
|
1246
|
-
val body = Body.fromString("""{"name":"updated"}""")
|
|
1247
|
-
|
|
1248
|
-
Request.get(url) // GET with empty body
|
|
1249
|
-
Request.post(url, body) // POST with body
|
|
1250
|
-
Request.put(url, body) // PUT with body
|
|
1251
|
-
Request.patch(url, body) // PATCH with body
|
|
1252
|
-
Request.delete(url) // DELETE with empty body
|
|
1253
|
-
Request.head(url) // HEAD with empty body
|
|
1254
|
-
Request.options(url) // OPTIONS with empty body
|
|
1255
|
-
```
|
|
1256
|
-
|
|
1257
|
-
### Modifying Requests
|
|
1258
|
-
|
|
1259
|
-
All modification methods return a new `Request`—the original is unchanged:
|
|
1260
|
-
|
|
1261
|
-
```scala
|
|
1262
|
-
import zio.http._
|
|
1263
|
-
|
|
1264
|
-
val request = Request.get(URL.parse("https://api.example.com/users").toOption.get)
|
|
1265
|
-
|
|
1266
|
-
// Adding/modifying headers
|
|
1267
|
-
request.addHeader("Accept", "application/json")
|
|
1268
|
-
request.addHeaders(Headers("X-A" -> "1", "X-B" -> "2"))
|
|
1269
|
-
request.setHeader("Accept", "text/html") // replaces existing
|
|
1270
|
-
request.removeHeader("Accept")
|
|
1271
|
-
|
|
1272
|
-
// Replacing components
|
|
1273
|
-
request.body(Body.fromString("data"))
|
|
1274
|
-
request.url(URL.parse("/other").toOption.get)
|
|
1275
|
-
request.method(Method.POST)
|
|
1276
|
-
request.version(Version.`HTTP/2.0`)
|
|
1277
|
-
// Functional updates
|
|
1278
|
-
request.updateHeaders(_.add("X-Custom", "value"))
|
|
1279
|
-
request.updateUrl(_ / "123") // appends path segment
|
|
1280
|
-
```
|
|
1281
|
-
|
|
1282
|
-
Full control:
|
|
1283
|
-
|
|
1284
|
-
```scala
|
|
1285
|
-
import zio.http._
|
|
1286
|
-
|
|
1287
|
-
val url = URL.parse("https://api.example.com/users").toOption.get
|
|
1288
|
-
val body = Body.fromString("""{"name":"Alice"}""", Charset.UTF8)
|
|
1289
|
-
|
|
1290
|
-
val request = Request(
|
|
1291
|
-
method = Method.POST,
|
|
1292
|
-
url = url,
|
|
1293
|
-
headers = Headers(
|
|
1294
|
-
"content-type" -> "application/json",
|
|
1295
|
-
"authorization" -> "Bearer token123"
|
|
1296
|
-
),
|
|
1297
|
-
body = body,
|
|
1298
|
-
version = Version.`HTTP/1.1`
|
|
1299
|
-
)
|
|
1300
|
-
```
|
|
1301
|
-
|
|
1302
|
-
### Accessing Request Data
|
|
1303
|
-
|
|
1304
|
-
```scala
|
|
1305
|
-
import zio.http._
|
|
1306
|
-
import zio.http.{headers => h}
|
|
1307
|
-
|
|
1308
|
-
val request = Request.get(URL.parse("/api/users?page=1").toOption.get)
|
|
1309
|
-
|
|
1310
|
-
request.path // Path("/api/users")
|
|
1311
|
-
request.queryParams // QueryParams("page" -> "1")
|
|
1312
|
-
request.contentType // Option[ContentType]
|
|
1313
|
-
request.header(h.Authorization) // Option[h.Authorization]
|
|
1314
|
-
```
|
|
1315
|
-
|
|
1316
|
-
## Response
|
|
1317
|
-
|
|
1318
|
-
`Response` represents HTTP responses:
|
|
1319
|
-
|
|
1320
|
-
```scala
|
|
1321
|
-
final case class Response(
|
|
1322
|
-
status: Status,
|
|
1323
|
-
headers: Headers,
|
|
1324
|
-
body: Body,
|
|
1325
|
-
version: Version
|
|
1326
|
-
)
|
|
1327
|
-
```
|
|
1328
|
-
|
|
1329
|
-
### Creating Responses
|
|
1330
|
-
|
|
1331
|
-
```scala
|
|
1332
|
-
import zio.http._
|
|
1333
|
-
|
|
1334
|
-
val ok = Response.ok // 200 OK, empty body
|
|
1335
|
-
|
|
1336
|
-
val notFound = Response.notFound // 404 Not Found, empty body
|
|
1337
|
-
```
|
|
1338
|
-
|
|
1339
|
-
### Response Factory Methods
|
|
1340
|
-
|
|
1341
|
-
```scala
|
|
1342
|
-
import zio.http._
|
|
1343
|
-
|
|
1344
|
-
// Status-only responses
|
|
1345
|
-
Response.ok // 200
|
|
1346
|
-
Response.notFound // 404
|
|
1347
|
-
Response.badRequest // 400
|
|
1348
|
-
Response.unauthorized // 401
|
|
1349
|
-
Response.forbidden // 403
|
|
1350
|
-
Response.internalServerError // 500
|
|
1351
|
-
Response.serviceUnavailable // 503
|
|
1352
|
-
|
|
1353
|
-
// Responses with bodies
|
|
1354
|
-
Response.text("Hello, World!") // 200 with text/plain body
|
|
1355
|
-
Response.json("""{'ok':true}""") // 200 with application/json in headers and body
|
|
1356
|
-
// Redirects
|
|
1357
|
-
Response.redirect("/new-location") // 307 Temporary Redirect
|
|
1358
|
-
Response.redirect("/new-location", isPermanent = true) // 308 Permanent Redirect
|
|
1359
|
-
Response.seeOther("/other") // 303 See Other
|
|
1360
|
-
```
|
|
1361
|
-
|
|
1362
|
-
Note that `Response.json` creates bodies with `application/json` content-type on the `Body` itself, not just in the headers.
|
|
1363
|
-
|
|
1364
|
-
### Modifying Responses
|
|
1365
|
-
|
|
1366
|
-
```scala
|
|
1367
|
-
import zio.http._
|
|
1368
|
-
|
|
1369
|
-
val response = Response.ok
|
|
1370
|
-
|
|
1371
|
-
// Adding/modifying headers
|
|
1372
|
-
response.addHeader("X-Custom", "value")
|
|
1373
|
-
response.setHeader("X-Custom", "new-value")
|
|
1374
|
-
response.removeHeader("X-Custom")
|
|
1375
|
-
|
|
1376
|
-
// Replacing components
|
|
1377
|
-
response.body(Body.fromString("data"))
|
|
1378
|
-
response.status(Status.Created)
|
|
1379
|
-
response.version(Version.`HTTP/2.0`)
|
|
1380
|
-
// Functional update
|
|
1381
|
-
response.updateHeaders(_.add("X-Request-Id", "abc"))
|
|
1382
|
-
|
|
1383
|
-
// Add a Set-Cookie header
|
|
1384
|
-
response.addCookie(ResponseCookie("session", "abc123"))
|
|
1385
|
-
```
|
|
1386
|
-
|
|
1387
|
-
Full control:
|
|
1388
|
-
|
|
1389
|
-
```scala
|
|
1390
|
-
import zio.http._
|
|
1391
|
-
|
|
1392
|
-
val jsonBody = Body.fromString("""{"message":"created"}""", Charset.UTF8)
|
|
1393
|
-
|
|
1394
|
-
val response = Response(
|
|
1395
|
-
status = Status.Created,
|
|
1396
|
-
headers = Headers(
|
|
1397
|
-
"content-type" -> "application/json",
|
|
1398
|
-
"location" -> "/users/123"
|
|
1399
|
-
),
|
|
1400
|
-
body = jsonBody,
|
|
1401
|
-
version = Version.`HTTP/1.1`
|
|
1402
|
-
)
|
|
1403
|
-
```
|
|
1404
|
-
|
|
1405
|
-
### Accessing Response Data
|
|
1406
|
-
|
|
1407
|
-
```scala
|
|
1408
|
-
import zio.http._
|
|
1409
|
-
import zio.http.{headers => h}
|
|
1410
|
-
|
|
1411
|
-
val response = Response.ok
|
|
1412
|
-
|
|
1413
|
-
response.status.code // 200
|
|
1414
|
-
response.status.isSuccess // true
|
|
1415
|
-
response.contentType // Option[ContentType]
|
|
1416
|
-
response.header(h.ContentType) // Option[h.ContentType]
|
|
1417
|
-
response.header(h.Location) // Option[h.Location]
|
|
1418
|
-
```
|
|
1419
|
-
|
|
1420
|
-
## Advanced Usage
|
|
1421
|
-
|
|
1422
|
-
### Building a Complete HTTP Exchange
|
|
1423
|
-
|
|
1424
|
-
```scala
|
|
1425
|
-
import zio.http._
|
|
1426
|
-
|
|
1427
|
-
// Build request
|
|
1428
|
-
val url = URL.parse("https://api.example.com/users").toOption.get
|
|
1429
|
-
val requestBody = Body.fromString("""{"name":"Alice","age":30}""", Charset.UTF8)
|
|
1430
|
-
|
|
1431
|
-
val request = Request(
|
|
1432
|
-
method = Method.POST,
|
|
1433
|
-
url = url,
|
|
1434
|
-
headers = Headers(
|
|
1435
|
-
"content-type" -> "application/json",
|
|
1436
|
-
"authorization" -> "Bearer abc123",
|
|
1437
|
-
"user-agent" -> "MyClient/1.0"
|
|
1438
|
-
),
|
|
1439
|
-
body = requestBody,
|
|
1440
|
-
version = Version.`HTTP/1.1`
|
|
1441
|
-
)
|
|
1442
|
-
|
|
1443
|
-
// Build response
|
|
1444
|
-
val responseBody = Body.fromString("""{"id":123,"name":"Alice","age":30}""", Charset.UTF8)
|
|
1445
|
-
|
|
1446
|
-
val response = Response(
|
|
1447
|
-
status = Status.Created,
|
|
1448
|
-
headers = Headers(
|
|
1449
|
-
"content-type" -> "application/json",
|
|
1450
|
-
"location" -> "/users/123"
|
|
1451
|
-
),
|
|
1452
|
-
body = responseBody,
|
|
1453
|
-
version = Version.`HTTP/1.1`
|
|
1454
|
-
)
|
|
1455
|
-
```
|
|
1456
|
-
|
|
1457
|
-
### URL Building with Fluent API
|
|
1458
|
-
|
|
1459
|
-
```scala
|
|
1460
|
-
import zio.http._
|
|
1461
|
-
|
|
1462
|
-
val url = URL.parse("https://api.example.com").toOption.get
|
|
1463
|
-
|
|
1464
|
-
val extended = (url / "v1" / "users" / "123") ?? ("include", "profile") ?? ("include", "posts")
|
|
1465
|
-
|
|
1466
|
-
extended.encode
|
|
1467
|
-
// "https://api.example.com/v1/users/123?include=profile&include=posts"
|
|
1468
|
-
```
|
|
1469
|
-
|
|
1470
|
-
### Typed Header Access
|
|
1471
|
-
|
|
1472
|
-
```scala
|
|
1473
|
-
import zio.http._
|
|
1474
|
-
import zio.http.{headers => h}
|
|
1475
|
-
|
|
1476
|
-
val headers = Headers(
|
|
1477
|
-
"content-type" -> "application/json; charset=utf-8",
|
|
1478
|
-
"content-length" -> "1024",
|
|
1479
|
-
"authorization" -> "Bearer token"
|
|
1480
|
-
)
|
|
1481
|
-
|
|
1482
|
-
// Type-safe header access with parsing
|
|
1483
|
-
val ct = headers.get(h.ContentType)
|
|
1484
|
-
ct.map(_.value.charset) // Some(Some(Charset.UTF8))
|
|
1485
|
-
|
|
1486
|
-
val cl = headers.get(h.ContentLength)
|
|
1487
|
-
cl.map(_.length) // Some(1024)
|
|
1488
|
-
|
|
1489
|
-
// Raw access
|
|
1490
|
-
headers.rawGet("authorization") // Some("Bearer token")
|
|
1491
|
-
```
|
|
1492
|
-
|
|
1493
|
-
### Cookie Management
|
|
1494
|
-
|
|
1495
|
-
```scala
|
|
1496
|
-
import zio.http._
|
|
1497
|
-
import zio.blocks.chunk.Chunk
|
|
1498
|
-
|
|
1499
|
-
// Parse cookies from request header
|
|
1500
|
-
val cookieHeader = "session=abc; theme=dark"
|
|
1501
|
-
val requestCookies = Cookie.parseRequest(cookieHeader)
|
|
1502
|
-
|
|
1503
|
-
// Create response with Set-Cookie headers
|
|
1504
|
-
val sessionCookie = ResponseCookie(
|
|
1505
|
-
name = "session",
|
|
1506
|
-
value = "xyz123",
|
|
1507
|
-
path = Some(Path("/")),
|
|
1508
|
-
maxAge = Some(3600),
|
|
1509
|
-
isSecure = true,
|
|
1510
|
-
isHttpOnly = true,
|
|
1511
|
-
sameSite = Some(SameSite.Strict)
|
|
1512
|
-
)
|
|
1513
|
-
|
|
1514
|
-
val response = Response(
|
|
1515
|
-
status = Status.Ok,
|
|
1516
|
-
headers = Headers(
|
|
1517
|
-
"set-cookie" -> Cookie.renderResponse(sessionCookie)
|
|
1518
|
-
),
|
|
1519
|
-
body = Body.empty
|
|
1520
|
-
)
|
|
1521
|
-
```
|
|
1522
|
-
|
|
1523
|
-
### Form Submission
|
|
1524
|
-
|
|
1525
|
-
```scala
|
|
1526
|
-
import zio.http._
|
|
1527
|
-
|
|
1528
|
-
val form = Form(
|
|
1529
|
-
"username" -> "alice",
|
|
1530
|
-
"password" -> "secret",
|
|
1531
|
-
"remember" -> "true"
|
|
1532
|
-
)
|
|
1533
|
-
|
|
1534
|
-
val formBody = Body.fromString(form.encode, Charset.UTF8)
|
|
1535
|
-
|
|
1536
|
-
val request = Request(
|
|
1537
|
-
method = Method.POST,
|
|
1538
|
-
url = URL.parse("/login").toOption.get,
|
|
1539
|
-
headers = Headers(
|
|
1540
|
-
"content-type" -> "application/x-www-form-urlencoded"
|
|
1541
|
-
),
|
|
1542
|
-
body = formBody,
|
|
1543
|
-
version = Version.`HTTP/1.1`
|
|
1544
|
-
)
|
|
1545
|
-
```
|
|
1546
|
-
|
|
1547
|
-
## Design Principles
|
|
1548
|
-
|
|
1549
|
-
### Single Encoding Contract
|
|
1550
|
-
|
|
1551
|
-
`Path` and `QueryParams` store decoded values internally. Encoding happens only at output boundaries:
|
|
1552
|
-
|
|
1553
|
-
- `Path.fromEncoded(s)` decodes, stores decoded segments
|
|
1554
|
-
- `Path.encode` encodes segments for transmission
|
|
1555
|
-
- `QueryParams.fromEncoded(s)` decodes, stores decoded key-value pairs
|
|
1556
|
-
- `QueryParams.encode` encodes for transmission
|
|
1557
|
-
|
|
1558
|
-
This eliminates double-encoding bugs and clarifies responsibilities.
|
|
1559
|
-
|
|
1560
|
-
### Lazy Header Parsing
|
|
1561
|
-
|
|
1562
|
-
`Headers` stores raw string values and parses typed headers on first access. Parsed results are cached in a parallel `Array[AnyRef]` for O(1) subsequent lookups. This design:
|
|
1563
|
-
|
|
1564
|
-
- Avoids parsing headers that are never accessed
|
|
1565
|
-
- Avoids re-parsing the same header multiple times
|
|
1566
|
-
- Supports unknown/custom headers without parsing failures
|
|
1567
|
-
|
|
1568
|
-
### No Streaming
|
|
1569
|
-
|
|
1570
|
-
Bodies are fully materialized `Chunk[Byte]`. Streaming is left to higher-level HTTP libraries that compose with this data model. This keeps the model simple and effect-free.
|
|
1571
|
-
|
|
1572
|
-
### Zero ZIO Dependency
|
|
1573
|
-
|
|
1574
|
-
The module uses `zio.blocks.chunk.Chunk` instead of `zio.Chunk`, making it usable in any Scala project without ZIO.
|
|
1575
|
-
|
|
1576
|
-
## Schema-Based Typed Access (zio-http-model-schema)
|
|
1577
|
-
|
|
1578
|
-
The `zio-http-model-schema` module provides schema-based extraction of query parameters and headers with automatic decoding and validation.
|
|
1579
|
-
|
|
1580
|
-
### Installation
|
|
1581
|
-
|
|
1582
|
-
Add the following to your `build.sbt`:
|
|
1583
|
-
|
|
1584
|
-
```scala
|
|
1585
|
-
libraryDependencies += "dev.zio" %% "zio-blocks-http-model-schema" % "<version>"
|
|
1586
|
-
```
|
|
1587
|
-
|
|
1588
|
-
For cross-platform projects (Scala.js):
|
|
1589
|
-
|
|
1590
|
-
```scala
|
|
1591
|
-
libraryDependencies += "dev.zio" %%% "zio-blocks-http-model-schema" % "<version>"
|
|
1592
|
-
```
|
|
1593
|
-
|
|
1594
|
-
### Imports
|
|
1595
|
-
|
|
1596
|
-
Import the schema module to enable extension methods:
|
|
1597
|
-
|
|
1598
|
-
```scala
|
|
1599
|
-
import zio.http.schema._
|
|
1600
|
-
import zio.blocks.schema.Schema
|
|
1601
|
-
```
|
|
1602
|
-
|
|
1603
|
-
### Query Parameter Extraction
|
|
1604
|
-
|
|
1605
|
-
`QueryParams` gains schema-based extraction methods via implicit conversions:
|
|
1606
|
-
|
|
1607
|
-
```scala
|
|
1608
|
-
import zio.http.{QueryParams, URL}
|
|
1609
|
-
import zio.http.schema._
|
|
1610
|
-
import zio.blocks.schema.Schema
|
|
1611
|
-
|
|
1612
|
-
val url = URL.parse("/api/users?page=2&tag=scala&tag=fp").toOption.get
|
|
1613
|
-
val params = url.queryParams
|
|
1614
|
-
|
|
1615
|
-
// Extract single value with automatic decoding
|
|
1616
|
-
params.query[Int]("page")
|
|
1617
|
-
// Right(2)
|
|
1618
|
-
|
|
1619
|
-
// Extract all values for a key
|
|
1620
|
-
params.queryAll[String]("tag")
|
|
1621
|
-
// Right(Chunk("scala", "fp"))
|
|
1622
|
-
|
|
1623
|
-
// Extract with default fallback
|
|
1624
|
-
params.queryOrElse[Int]("limit", 10)
|
|
1625
|
-
// 10 - uses default since "limit" not present
|
|
1626
|
-
```
|
|
1627
|
-
|
|
1628
|
-
### Header Extraction
|
|
1629
|
-
|
|
1630
|
-
`Headers` gains schema-based extraction methods:
|
|
1631
|
-
|
|
1632
|
-
```scala
|
|
1633
|
-
import zio.http.{Headers, Request, URL}
|
|
1634
|
-
import zio.http.schema._
|
|
1635
|
-
import zio.blocks.schema.Schema
|
|
1636
|
-
|
|
1637
|
-
val request = Request.get(URL.parse("/").toOption.get)
|
|
1638
|
-
.addHeader("x-page", "5")
|
|
1639
|
-
.addHeader("x-tag", "scala")
|
|
1640
|
-
.addHeader("x-tag", "functional")
|
|
1641
|
-
|
|
1642
|
-
val headers = request.headers
|
|
1643
|
-
|
|
1644
|
-
// Extract single header value
|
|
1645
|
-
headers.header[Int]("x-page")
|
|
1646
|
-
// Right(5)
|
|
1647
|
-
|
|
1648
|
-
// Extract all header values
|
|
1649
|
-
headers.headerAll[String]("x-tag")
|
|
1650
|
-
// Right(Chunk("scala", "functional"))
|
|
1651
|
-
|
|
1652
|
-
// Extract with default fallback
|
|
1653
|
-
headers.headerOrElse[Int]("x-limit", 100)
|
|
1654
|
-
// 100
|
|
1655
|
-
```
|
|
1656
|
-
|
|
1657
|
-
### Request and Response Extensions
|
|
1658
|
-
|
|
1659
|
-
`Request` and `Response` gain schema-based extraction methods that delegate to their query parameters and headers:
|
|
1660
|
-
|
|
1661
|
-
```scala
|
|
1662
|
-
import zio.http.{Request, Response, URL}
|
|
1663
|
-
import zio.http.schema._
|
|
1664
|
-
import zio.blocks.schema.Schema
|
|
1665
|
-
|
|
1666
|
-
// Request query parameter extraction
|
|
1667
|
-
val request = Request.get(URL.parse("/search?q=zio&limit=20").toOption.get)
|
|
1668
|
-
|
|
1669
|
-
request.query[String]("q")
|
|
1670
|
-
// Right("zio")
|
|
1671
|
-
|
|
1672
|
-
request.query[Int]("limit")
|
|
1673
|
-
// Right(20)
|
|
1674
|
-
|
|
1675
|
-
// Response header extraction via schema
|
|
1676
|
-
val response = Response.ok.addHeader("x-correlation-id", "abc-123")
|
|
1677
|
-
|
|
1678
|
-
val responseOps = new ResponseSchemaOps(response)
|
|
1679
|
-
responseOps.header[String]("x-correlation-id")
|
|
1680
|
-
```
|
|
1681
|
-
|
|
1682
|
-
### Error Handling
|
|
1683
|
-
|
|
1684
|
-
Schema-based extraction returns `Either[QueryParamError, A]` or `Either[HeaderError, A]` for explicit error handling:
|
|
1685
|
-
|
|
1686
|
-
```scala
|
|
1687
|
-
import zio.http.{QueryParams, URL}
|
|
1688
|
-
import zio.http.schema._
|
|
1689
|
-
import zio.blocks.schema.Schema
|
|
1690
|
-
|
|
1691
|
-
val params = QueryParams("name" -> "Alice", "age" -> "invalid")
|
|
1692
|
-
|
|
1693
|
-
params.query[String]("name") match {
|
|
1694
|
-
case Right(name) => println(s"Name: $name")
|
|
1695
|
-
case Left(QueryParamError.Missing(key)) => println(s"Missing key: $key")
|
|
1696
|
-
case Left(QueryParamError.Malformed(key, value, cause)) =>
|
|
1697
|
-
println(s"Failed to parse $key=$value: $cause")
|
|
1698
|
-
}
|
|
1699
|
-
|
|
1700
|
-
params.query[Int]("age") match {
|
|
1701
|
-
case Right(age) => println(s"Age: $age")
|
|
1702
|
-
case Left(QueryParamError.Missing(key)) => println(s"Missing key: $key")
|
|
1703
|
-
case Left(QueryParamError.Malformed(key, value, cause)) =>
|
|
1704
|
-
println(s"Failed to parse $key=$value: $cause")
|
|
1705
|
-
}
|
|
1706
|
-
```
|
|
1707
|
-
|
|
1708
|
-
### Supported Types
|
|
1709
|
-
|
|
1710
|
-
The schema module provides built-in `Schema` instances for common types. Any type with a `Schema[T]` can be extracted:
|
|
1711
|
-
|
|
1712
|
-
- **Primitives**: `String`, `Int`, `Long`, `Boolean`, `Double`, `Float`, `Short`, `Byte`, `Char`
|
|
1713
|
-
- **Big Numbers**: `BigInt`, `BigDecimal`
|
|
1714
|
-
- **UUID**: `java.util.UUID`
|
|
1715
|
-
|
|
1716
|
-
For custom types, define a `Schema[T]` instance using schema derivation or manual construction.
|