@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
|
@@ -0,0 +1,825 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: index
|
|
3
|
+
title: "Endpoint (Module)"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
`zio-blocks-endpoint` is a **pure, type-safe HTTP endpoint descriptor** for building clients, servers, and API documentation from a single source of truth. It provides composable types that describe every part of an HTTP surface — routes, query parameters, headers, request bodies, response bodies, error shapes, and authentication — without committing to any particular server or client implementation.
|
|
7
|
+
|
|
8
|
+
Core types: `Endpoint`, `HttpCodec`, `RoutePattern`, `PathCodec`, `SegmentCodec`, `AuthType`, `RouteTree`. The top-level descriptor holds all of them together:
|
|
9
|
+
|
|
10
|
+
```scala
|
|
11
|
+
final case class Endpoint[PathInput, Input, Err, Output, Auth <: AuthType](
|
|
12
|
+
route: RoutePattern[PathInput],
|
|
13
|
+
input: HttpCodec[CodecKind.Request, Input],
|
|
14
|
+
error: HttpCodec[CodecKind.Response, Err],
|
|
15
|
+
output: HttpCodec[CodecKind.Response, Output],
|
|
16
|
+
auth: Auth,
|
|
17
|
+
doc: Doc
|
|
18
|
+
)
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Introduction
|
|
22
|
+
|
|
23
|
+
`zio-blocks-endpoint` separates the **description** of an HTTP surface from its **interpretation**. An `Endpoint` value is plain data — it can be handed to a ZIO HTTP server to generate routes, to a client generator to produce typed API calls, or to an OpenAPI renderer to produce specification documents. None of that interpretation code lives here; this module only describes what an endpoint looks like.
|
|
24
|
+
|
|
25
|
+
The DSL is designed to stay close to zio-http where that improves ergonomics, while adding precise types for error shapes, authentication, and content negotiation that zio-http does not encode directly.
|
|
26
|
+
|
|
27
|
+
## Motivation
|
|
28
|
+
|
|
29
|
+
Without a typed endpoint descriptor, HTTP surface definitions are scattered: routes in one place, request validation in another, error handling in a third. Adding a new endpoint means updating multiple layers by hand and hoping they stay consistent.
|
|
30
|
+
|
|
31
|
+
`zio-blocks-endpoint` solves this by encoding the full shape of an HTTP endpoint — including error variants, auth requirements, content types, and documentation — into a single composable value:
|
|
32
|
+
|
|
33
|
+
- **One source of truth**: change the endpoint descriptor and every interpreter (server, client, OpenAPI) updates automatically.
|
|
34
|
+
- **Type-safe error channels**: error types are encoded in the `Err` type parameter, not buried in `Either` chains or thrown exceptions.
|
|
35
|
+
- **Direction-checked codecs**: `HttpCodec[CodecKind.Request, A]` and `HttpCodec[CodecKind.Response, A]` are distinct types; the compiler prevents accidentally using a response codec where a request codec is expected.
|
|
36
|
+
- **Compile-time path validation**: path segment combinations (like `string ~ string`) that would be ambiguous to parse are rejected by the Scala 3 macro in `SegmentCodec` before the code compiles.
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
|
|
40
|
+
The endpoint module is a cross-platform library (JVM + Scala.js). Add the dependency to your build definition:
|
|
41
|
+
|
|
42
|
+
**JVM (Scala 3.x):**
|
|
43
|
+
```scala
|
|
44
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-endpoint" % "0.0.51"
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Scala.js (Scala 3.x):**
|
|
48
|
+
```scala
|
|
49
|
+
libraryDependencies += "dev.zio" %%% "zio-blocks-endpoint" % "0.0.51"
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**For Scala 3.7+**, the module name is rewritten to `zio-blocks-next-endpoint`:
|
|
53
|
+
```scala
|
|
54
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-next-endpoint" % "0.0.51" // JVM
|
|
55
|
+
libraryDependencies += "dev.zio" %%% "zio-blocks-next-endpoint" % "0.0.51" // Scala.js
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Supported Scala versions: 3.x (Scala 3 only — the endpoint module uses Scala 3-only DSL and macro code).
|
|
59
|
+
|
|
60
|
+
## Overview
|
|
61
|
+
|
|
62
|
+
These seven types form the complete endpoint DSL:
|
|
63
|
+
|
|
64
|
+
**[Endpoint](./endpoint.md)** is the top-level descriptor. It holds a route, three codec channels (input, error, output), an auth type, and documentation. Endpoint is pure data with no server or client logic.
|
|
65
|
+
|
|
66
|
+
**[HttpCodec](./http-codec.md)** is a composable typed descriptor for HTTP request and response parts. Query parameters, headers, bodies, and status codes are all `HttpCodec` nodes, combined with `++` (sequential) or `|` (alternative).
|
|
67
|
+
|
|
68
|
+
**[RoutePattern](./route-pattern.md)** pairs an HTTP method with a typed path pattern. The primary syntax is `Method.GET / "users" / PathCodec.int("id")`.
|
|
69
|
+
|
|
70
|
+
**[PathCodec](./path-codec.md)** is a composable path descriptor. Segments are combined with `/`, and literal alternatives with `orElse`. It supports bidirectional path conversion via `decode` and `format`.
|
|
71
|
+
|
|
72
|
+
**[SegmentCodec](./segment-codec.md)** describes a single URL path segment. It supports typed segment kinds (`SegmentCodec.bool`, `SegmentCodec.int`, `SegmentCodec.long`, `SegmentCodec.string`, `SegmentCodec.uuid`) and intra-segment composition via `~`, with ambiguous combinations rejected at compile time.
|
|
73
|
+
|
|
74
|
+
**[AuthType](./auth-type.md)** describes an authentication scheme as a first-class type parameter. Built-in variants include `None`, `Basic`, `Bearer`, and `Digest`; custom schemes and `Or` combinations are also supported.
|
|
75
|
+
|
|
76
|
+
**[RouteTree](./route-tree.md)** is a routing trie keyed by HTTP method and path. The trie matches literals first, then dynamic segments in priority order. Server-side interpreters use it to build efficient route dispatch tables.
|
|
77
|
+
|
|
78
|
+
## How They Work Together
|
|
79
|
+
|
|
80
|
+
A typical endpoint definition flows like this:
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
1. Define a RoutePattern Method.GET / "users" / PathCodec.int("id")
|
|
84
|
+
2. Create an Endpoint Endpoint(route)
|
|
85
|
+
3. Describe request input .query("verbose", Schema.boolean)
|
|
86
|
+
.header("X-Trace", Schema.string)
|
|
87
|
+
.in(Schema.string)
|
|
88
|
+
4. Describe success output .out(Schema.string)
|
|
89
|
+
.out(Status.Created, Schema.int)
|
|
90
|
+
5. Describe error output .outError(Status.NotFound, Schema.string)
|
|
91
|
+
.orOutError(Status.Conflict, Schema.int) // Scala 3 unions
|
|
92
|
+
6. Set authentication .auth(AuthType.Bearer)
|
|
93
|
+
7. Add documentation .doc(Doc.paragraph("Returns a user by ID"))
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The full type-level view:
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
RoutePattern[PathInput]
|
|
100
|
+
└─ method: Method (GET, POST, ...)
|
|
101
|
+
└─ pathCodec: PathCodec[PathInput]
|
|
102
|
+
└─ Segment(SegmentCodec[A]) ── literal / int / string / uuid / bool / long / trailing
|
|
103
|
+
└─ Concat(left, right) ── left ++ right
|
|
104
|
+
└─ Transform(codec, f, g) ── bidirectional type mapping
|
|
105
|
+
|
|
106
|
+
Endpoint[PathInput, Input, Err, Output, Auth]
|
|
107
|
+
├─ route: RoutePattern[PathInput]
|
|
108
|
+
├─ input: HttpCodec[Request, Input] ── Query | Header | Body (combined with ++)
|
|
109
|
+
├─ error: HttpCodec[Response, Err] ── Body + Status (alternatives with |)
|
|
110
|
+
├─ output: HttpCodec[Response, Output] ── Body + Status (alternatives with |)
|
|
111
|
+
└─ auth: Auth <: AuthType ── None | Basic | Bearer | Digest | Custom | Or
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
The phantom type `CodecKind` (either `Request` or `Response`) on `HttpCodec` means the compiler rejects mixing the two directions, even before a server interprets the value.
|
|
115
|
+
|
|
116
|
+
## Common Patterns
|
|
117
|
+
|
|
118
|
+
Several composition patterns appear regularly when building endpoints.
|
|
119
|
+
|
|
120
|
+
**Single success response:** Use `Endpoint#out` for a 200 OK response with a body:
|
|
121
|
+
|
|
122
|
+
```scala
|
|
123
|
+
import zio.blocks.endpoint._
|
|
124
|
+
import zio.blocks.endpoint.RoutePattern._
|
|
125
|
+
import zio.blocks.schema.Schema
|
|
126
|
+
import zio.http.Method
|
|
127
|
+
|
|
128
|
+
val getUser = Endpoint(Method.GET / "users" / PathCodec.int("id"))
|
|
129
|
+
.out(Schema.string)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**Multiple success variants:** Chain additional `Endpoint#out` calls to add alternatives. The output type widens to an `Either`-based union:
|
|
133
|
+
|
|
134
|
+
```scala
|
|
135
|
+
import zio.blocks.endpoint._
|
|
136
|
+
import zio.blocks.endpoint.RoutePattern._
|
|
137
|
+
import zio.blocks.schema.Schema
|
|
138
|
+
import zio.http.{Method, Status}
|
|
139
|
+
|
|
140
|
+
val createOrUpdate = Endpoint(Method.POST / "users")
|
|
141
|
+
.in(Schema.string)
|
|
142
|
+
.out(Status.Created, Schema.int)
|
|
143
|
+
.out(Status.Ok, Schema.string)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
**Scala 3 union errors:** Use `Endpoint#orOutError` to accumulate error types as a Scala 3 union rather than nested `Either`s:
|
|
147
|
+
|
|
148
|
+
```scala
|
|
149
|
+
import zio.blocks.endpoint._
|
|
150
|
+
import zio.blocks.endpoint.RoutePattern._
|
|
151
|
+
import zio.blocks.schema.Schema
|
|
152
|
+
import zio.http.{Method, Status}
|
|
153
|
+
|
|
154
|
+
val withUnionErrors = Endpoint(Method.GET / "users")
|
|
155
|
+
.orOutError(Status.NotFound, Schema.string)
|
|
156
|
+
.orOutError(Status.Conflict, Schema.int)
|
|
157
|
+
|
|
158
|
+
val typed: Endpoint[Unit, Unit, String | Int, Unit, AuthType.None.type] = withUnionErrors
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
**Path prefixing with `RoutePattern#nest`:** Use `RoutePattern#nest` to prepend a version prefix to an existing pattern without rewriting it:
|
|
162
|
+
|
|
163
|
+
```scala
|
|
164
|
+
import zio.blocks.endpoint._
|
|
165
|
+
import zio.blocks.endpoint.RoutePattern._
|
|
166
|
+
import zio.http.Method
|
|
167
|
+
|
|
168
|
+
val route = Method.GET / "users" / PathCodec.int("id")
|
|
169
|
+
val versioned = route.nest(PathCodec("/api/v1"))
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
**Auth composition with `|`:** Combine auth types when an endpoint accepts multiple schemes:
|
|
173
|
+
|
|
174
|
+
```scala
|
|
175
|
+
import zio.blocks.endpoint._
|
|
176
|
+
import zio.blocks.endpoint.RoutePattern._
|
|
177
|
+
import zio.http.Method
|
|
178
|
+
|
|
179
|
+
val flexAuth = Endpoint(Method.GET / "me")
|
|
180
|
+
.auth(AuthType.Basic | AuthType.Bearer)
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Integration Points
|
|
184
|
+
|
|
185
|
+
The endpoint types integrate with each other and with the broader ZIO Blocks ecosystem:
|
|
186
|
+
|
|
187
|
+
```
|
|
188
|
+
Endpoint
|
|
189
|
+
├─ uses RoutePattern for routing lookup
|
|
190
|
+
├─ uses HttpCodec for all three channels (input, error, output)
|
|
191
|
+
├─ uses AuthType to carry the typed auth requirement
|
|
192
|
+
└─ uses Doc from zio-blocks-docs for API documentation
|
|
193
|
+
|
|
194
|
+
HttpCodec
|
|
195
|
+
├─ uses Schema from zio-blocks-schema for body and header serialization
|
|
196
|
+
├─ uses MediaType from zio-blocks-mediatype for content negotiation
|
|
197
|
+
└─ uses Doc for per-field documentation and examples
|
|
198
|
+
|
|
199
|
+
RoutePattern
|
|
200
|
+
└─ uses PathCodec for typed path composition
|
|
201
|
+
|
|
202
|
+
PathCodec
|
|
203
|
+
└─ uses SegmentCodec for individual segment descriptors
|
|
204
|
+
|
|
205
|
+
RouteTree (server-side only)
|
|
206
|
+
└─ uses RoutePattern to build the routing trie
|
|
207
|
+
└─ uses SegmentSubtree for per-level trie nodes
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Cross-module: `zio-blocks-openapi` consumes `Endpoint` values to generate OpenAPI 3.1 specifications. `zio-blocks-schema` provides the `Schema[A]` instances that `HttpCodec.Body` uses for serialization.
|
|
211
|
+
|
|
212
|
+
## Running the Examples
|
|
213
|
+
|
|
214
|
+
All code from this section is available as runnable examples in the `endpoint-examples` module.
|
|
215
|
+
|
|
216
|
+
**1. Clone the repository and navigate to the project:**
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
git clone https://github.com/zio/zio-blocks.git
|
|
220
|
+
cd zio-blocks
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
**2. Run individual examples with sbt:**
|
|
224
|
+
|
|
225
|
+
### Basic Endpoint Definition
|
|
226
|
+
|
|
227
|
+
Constructs `Endpoint` values from `RoutePattern` and chains request body, query parameters, headers, success outputs, response headers, and typed error variants using the builder DSL.
|
|
228
|
+
|
|
229
|
+
```scala title="endpoint-examples/src/main/scala/endpointexamples/BasicEndpointDefinition.scala"
|
|
230
|
+
/*
|
|
231
|
+
* Copyright 2024-2026 John A. De Goes and the ZIO Contributors
|
|
232
|
+
*
|
|
233
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
234
|
+
* you may not use this file except in compliance with the License.
|
|
235
|
+
* You may obtain a copy of the License at
|
|
236
|
+
*
|
|
237
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
238
|
+
*
|
|
239
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
240
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
241
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
242
|
+
* See the License for the specific language governing permissions and
|
|
243
|
+
* limitations under the License.
|
|
244
|
+
*/
|
|
245
|
+
|
|
246
|
+
package endpointexamples
|
|
247
|
+
|
|
248
|
+
import scala.language.implicitConversions
|
|
249
|
+
|
|
250
|
+
import zio.blocks.endpoint._
|
|
251
|
+
import zio.blocks.endpoint.RoutePattern._
|
|
252
|
+
import zio.blocks.schema.Schema
|
|
253
|
+
import zio.http.{Method, Status}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Endpoint — Basic Endpoint Definition
|
|
257
|
+
*
|
|
258
|
+
* Demonstrates constructing an `Endpoint` from a `RoutePattern` and attaching
|
|
259
|
+
* request body, query parameters, headers, success outputs, and error outputs
|
|
260
|
+
* using the builder DSL.
|
|
261
|
+
*
|
|
262
|
+
* Run with: sbt "endpoint-examples/runMain
|
|
263
|
+
* endpointexamples.BasicEndpointDefinition"
|
|
264
|
+
*/
|
|
265
|
+
@main def BasicEndpointDefinition(): Unit = {
|
|
266
|
+
|
|
267
|
+
// Simplest endpoint: GET /health with a string response
|
|
268
|
+
val health = Endpoint(Method.GET / "health")
|
|
269
|
+
.out(Schema.string)
|
|
270
|
+
|
|
271
|
+
println(s"Health route: ${health.route.render}")
|
|
272
|
+
|
|
273
|
+
// POST with a typed request body and a 201 Created response
|
|
274
|
+
val createUser = Endpoint(Method.POST / "users")
|
|
275
|
+
.in(Schema.string)
|
|
276
|
+
.out(Status.Created, Schema.int)
|
|
277
|
+
|
|
278
|
+
println(s"Create user route: ${createUser.route.render}")
|
|
279
|
+
|
|
280
|
+
// GET with query parameters and a request header
|
|
281
|
+
val listUsers = Endpoint(Method.GET / "users")
|
|
282
|
+
.query("page", Schema.int)
|
|
283
|
+
.query("limit", Schema.int)
|
|
284
|
+
.header("X-Trace-Id", Schema.string)
|
|
285
|
+
.out(Schema.string)
|
|
286
|
+
|
|
287
|
+
println(s"List users route: ${listUsers.route.render}")
|
|
288
|
+
|
|
289
|
+
// GET with a dynamic path segment and typed error variants
|
|
290
|
+
val getUser = Endpoint(Method.GET / "users" / PathCodec.int("id"))
|
|
291
|
+
.out(Schema.string)
|
|
292
|
+
.outError(Status.NotFound, Schema.string)
|
|
293
|
+
.outError(Status.BadRequest, Schema.string)
|
|
294
|
+
|
|
295
|
+
println(s"Get user route: ${getUser.route.render}")
|
|
296
|
+
|
|
297
|
+
// Response header on the success channel
|
|
298
|
+
val withRespHeader = Endpoint(Method.GET / "users")
|
|
299
|
+
.out(Schema.string)
|
|
300
|
+
.outHeader("X-Total-Count", Schema.int)
|
|
301
|
+
|
|
302
|
+
println(s"With response header route: ${withRespHeader.route.render}")
|
|
303
|
+
|
|
304
|
+
println("BasicEndpointDefinition complete")
|
|
305
|
+
}
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
([source](https://github.com/zio/zio-blocks/blob/main/endpoint-examples/src/main/scala/endpointexamples/BasicEndpointDefinition.scala))
|
|
309
|
+
|
|
310
|
+
```bash
|
|
311
|
+
sbt "endpoint-examples/runMain endpointexamples.BasicEndpointDefinition"
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### HttpCodec Smart Constructors and Composition
|
|
315
|
+
|
|
316
|
+
Builds `HttpCodec` atoms for query parameters, request headers, response headers, request bodies, response bodies, and status codes. Shows sequential composition with `++` and alternative composition with `|`.
|
|
317
|
+
|
|
318
|
+
```scala title="endpoint-examples/src/main/scala/endpointexamples/HttpCodecConstruction.scala"
|
|
319
|
+
/*
|
|
320
|
+
* Copyright 2024-2026 John A. De Goes and the ZIO Contributors
|
|
321
|
+
*
|
|
322
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
323
|
+
* you may not use this file except in compliance with the License.
|
|
324
|
+
* You may obtain a copy of the License at
|
|
325
|
+
*
|
|
326
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
327
|
+
*
|
|
328
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
329
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
330
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
331
|
+
* See the License for the specific language governing permissions and
|
|
332
|
+
* limitations under the License.
|
|
333
|
+
*/
|
|
334
|
+
|
|
335
|
+
package endpointexamples
|
|
336
|
+
|
|
337
|
+
import zio.blocks.chunk.Chunk
|
|
338
|
+
import zio.blocks.docs.Doc
|
|
339
|
+
import zio.blocks.endpoint._
|
|
340
|
+
import zio.blocks.mediatype.MediaTypes
|
|
341
|
+
import zio.blocks.schema.Schema
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* HttpCodec — Smart Constructors and Composition
|
|
345
|
+
*
|
|
346
|
+
* Demonstrates building `HttpCodec` atoms (query parameters, headers, bodies,
|
|
347
|
+
* status codes) and composing them with `++` (sequential) and `|`
|
|
348
|
+
* (alternative).
|
|
349
|
+
*
|
|
350
|
+
* Run with: sbt "endpoint-examples/runMain
|
|
351
|
+
* endpointexamples.HttpCodecConstruction"
|
|
352
|
+
*/
|
|
353
|
+
@main def HttpCodecConstruction(): Unit = {
|
|
354
|
+
|
|
355
|
+
// --- Smart constructors ---
|
|
356
|
+
|
|
357
|
+
// Query parameter with an optional default value
|
|
358
|
+
val pageCodec = HttpCodec.query("page", Schema.int, default = Some(1))
|
|
359
|
+
val limitCodec = HttpCodec.query("limit", Schema.int)
|
|
360
|
+
|
|
361
|
+
println(s"Query 'page' default: ${pageCodec.default}")
|
|
362
|
+
println(s"Query 'limit' default: ${limitCodec.default}")
|
|
363
|
+
|
|
364
|
+
// Request header by name and schema
|
|
365
|
+
val traceHeader = HttpCodec.requestHeader("X-Trace-Id", Schema.string)
|
|
366
|
+
println(s"Request header name: ${traceHeader.name}")
|
|
367
|
+
|
|
368
|
+
// Response header
|
|
369
|
+
val totalCount = HttpCodec.responseHeader("X-Total-Count", Schema.int)
|
|
370
|
+
println(s"Response header name: ${totalCount.name}")
|
|
371
|
+
|
|
372
|
+
// Request body restricted to JSON
|
|
373
|
+
val jsonBody =
|
|
374
|
+
HttpCodec.requestBody(Schema.string, mediaTypes = Chunk.single(MediaTypes.application.`json`))
|
|
375
|
+
println(s"Request body codec node: ${jsonBody.getClass.getSimpleName}")
|
|
376
|
+
|
|
377
|
+
// Response body
|
|
378
|
+
val respBody = HttpCodec.responseBody(Schema.string)
|
|
379
|
+
println(s"Response body codec node: ${respBody.getClass.getSimpleName}")
|
|
380
|
+
|
|
381
|
+
// Status code atoms — predefined constants
|
|
382
|
+
println(s"Ok status: ${HttpCodec.Ok}")
|
|
383
|
+
println(s"Created status: ${HttpCodec.Created}")
|
|
384
|
+
println(s"NotFound status: ${HttpCodec.NotFound}")
|
|
385
|
+
|
|
386
|
+
// --- Sequential composition with ++ ---
|
|
387
|
+
// Combines two request-side codecs into a single codec whose value is a tuple
|
|
388
|
+
val nameAndAgeQuery: HttpCodec[CodecKind.Request, (String, Int)] =
|
|
389
|
+
HttpCodec.query("name", Schema.string) ++ HttpCodec.query("age", Schema.int)
|
|
390
|
+
|
|
391
|
+
println(s"Sequential codec: ${nameAndAgeQuery.getClass.getSimpleName}")
|
|
392
|
+
|
|
393
|
+
// --- Alternative composition with | ---
|
|
394
|
+
// Builds a fallback: try the left codec, then the right
|
|
395
|
+
val okOrCreated =
|
|
396
|
+
(HttpCodec.responseBody(Schema.string) ++ HttpCodec.Ok) |
|
|
397
|
+
(HttpCodec.responseBody(Schema.int) ++ HttpCodec.Created)
|
|
398
|
+
|
|
399
|
+
println(s"Alternative codec: ${okOrCreated.getClass.getSimpleName}")
|
|
400
|
+
|
|
401
|
+
// --- Metadata: doc, examples, default ---
|
|
402
|
+
val richQuery = HttpCodec.query(
|
|
403
|
+
name = "limit",
|
|
404
|
+
schema = Schema.int,
|
|
405
|
+
default = Some(20),
|
|
406
|
+
doc = Doc.empty,
|
|
407
|
+
examples = Chunk("default" -> 20, "max" -> 100)
|
|
408
|
+
)
|
|
409
|
+
println(s"Rich query default: ${richQuery.default}")
|
|
410
|
+
println(s"Rich query examples count: ${richQuery.examples.length}")
|
|
411
|
+
|
|
412
|
+
// --- Auth codecs ---
|
|
413
|
+
val bearerCodec = HttpCodec.bearerAuth
|
|
414
|
+
val basicCodec = HttpCodec.basicAuth
|
|
415
|
+
val digestCodec = HttpCodec.digestAuth
|
|
416
|
+
println(s"Bearer codec: ${bearerCodec.getClass.getSimpleName}")
|
|
417
|
+
println(s"Basic codec: ${basicCodec.getClass.getSimpleName}")
|
|
418
|
+
println(s"Digest codec: ${digestCodec.getClass.getSimpleName}")
|
|
419
|
+
|
|
420
|
+
println("HttpCodecConstruction complete")
|
|
421
|
+
}
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
([source](https://github.com/zio/zio-blocks/blob/main/endpoint-examples/src/main/scala/endpointexamples/HttpCodecConstruction.scala))
|
|
425
|
+
|
|
426
|
+
```bash
|
|
427
|
+
sbt "endpoint-examples/runMain endpointexamples.HttpCodecConstruction"
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
### PathCodec and SegmentCodec
|
|
431
|
+
|
|
432
|
+
Demonstrates all `SegmentCodec` kinds, intra-segment composition with `~` for patterns like `v42`, bidirectional `PathCodec` decode and format, `RoutePattern` matching, `nest` for version prefixes, and `transform`/`transformOrFail` for domain type mapping.
|
|
433
|
+
|
|
434
|
+
```scala title="endpoint-examples/src/main/scala/endpointexamples/PathAndSegmentCodecs.scala"
|
|
435
|
+
/*
|
|
436
|
+
* Copyright 2024-2026 John A. De Goes and the ZIO Contributors
|
|
437
|
+
*
|
|
438
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
439
|
+
* you may not use this file except in compliance with the License.
|
|
440
|
+
* You may obtain a copy of the License at
|
|
441
|
+
*
|
|
442
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
443
|
+
*
|
|
444
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
445
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
446
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
447
|
+
* See the License for the specific language governing permissions and
|
|
448
|
+
* limitations under the License.
|
|
449
|
+
*/
|
|
450
|
+
|
|
451
|
+
package endpointexamples
|
|
452
|
+
|
|
453
|
+
import scala.language.implicitConversions
|
|
454
|
+
|
|
455
|
+
import zio.blocks.endpoint._
|
|
456
|
+
import zio.blocks.endpoint.RoutePattern._
|
|
457
|
+
import zio.http.{Method, Path}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* PathCodec and SegmentCodec — Typed Path Construction and Decoding
|
|
461
|
+
*
|
|
462
|
+
* Demonstrates `SegmentCodec` kinds, intra-segment composition with `~`,
|
|
463
|
+
* `PathCodec` construction, bidirectional decode/format, `RoutePattern`
|
|
464
|
+
* matching, nesting, and type transformations.
|
|
465
|
+
*
|
|
466
|
+
* Run with: sbt "endpoint-examples/runMain
|
|
467
|
+
* endpointexamples.PathAndSegmentCodecs"
|
|
468
|
+
*/
|
|
469
|
+
@main def PathAndSegmentCodecs(): Unit = {
|
|
470
|
+
|
|
471
|
+
// --- SegmentCodec kinds ---
|
|
472
|
+
val intSeg = SegmentCodec.int("id")
|
|
473
|
+
val strSeg = SegmentCodec.string("slug")
|
|
474
|
+
val uuidSeg = SegmentCodec.uuid("id")
|
|
475
|
+
val boolSeg = SegmentCodec.bool("flag")
|
|
476
|
+
val longSeg = SegmentCodec.long("id")
|
|
477
|
+
val trailing = SegmentCodec.Trailing
|
|
478
|
+
|
|
479
|
+
println(s"Segment kinds: int=${intSeg.render()}, string=${strSeg.render()}, uuid=${uuidSeg.render()}")
|
|
480
|
+
println(s" bool=${boolSeg.render()}, long=${longSeg.render()}, trailing=${trailing.render()}")
|
|
481
|
+
|
|
482
|
+
// Intra-segment composition: single path segment containing a literal prefix and an integer
|
|
483
|
+
// "v42" decodes to 42 and formats 42 back to "v42"
|
|
484
|
+
val versionSeg: SegmentCodec[Int] =
|
|
485
|
+
SegmentCodec.literal("v") ~ SegmentCodec.int("major")
|
|
486
|
+
|
|
487
|
+
println(s"Version segment renders as: ${versionSeg.render()}")
|
|
488
|
+
val formattedVersion: Path = versionSeg.format(3)
|
|
489
|
+
println(s"Version 3 formats to: $formattedVersion")
|
|
490
|
+
|
|
491
|
+
// --- PathCodec construction ---
|
|
492
|
+
val usersPath = PathCodec.literal("users") / PathCodec.int("id")
|
|
493
|
+
val apiPath = PathCodec("/api/v1/users")
|
|
494
|
+
val versionPath = PathCodec(versionSeg)
|
|
495
|
+
println(s"versionPath renders as: ${versionPath.render}")
|
|
496
|
+
|
|
497
|
+
println(s"usersPath renders as: ${usersPath.render}")
|
|
498
|
+
println(s"apiPath renders as: ${apiPath.render}")
|
|
499
|
+
|
|
500
|
+
// Bidirectional: decode a Path to a typed value
|
|
501
|
+
val decoded: Either[String, Int] = PathCodec.int("id").decode(Path("/42"))
|
|
502
|
+
println(s"Decoded /42: $decoded")
|
|
503
|
+
|
|
504
|
+
// Bidirectional: format a typed value back to a Path
|
|
505
|
+
val uuidValue = java.util.UUID.fromString("550e8400-e29b-41d4-a716-446655440000")
|
|
506
|
+
val formatted = PathCodec.uuid("id").format(uuidValue)
|
|
507
|
+
println(s"Formatted UUID: $formatted")
|
|
508
|
+
|
|
509
|
+
// Literal alternatives: match either /users or /members
|
|
510
|
+
val eitherPath: PathCodec[Unit] =
|
|
511
|
+
PathCodec.literal("users").orElse(PathCodec.literal("members"))
|
|
512
|
+
|
|
513
|
+
println(s"orElse matches /users: ${eitherPath.matches(Path("/users"))}")
|
|
514
|
+
println(s"orElse matches /members: ${eitherPath.matches(Path("/members"))}")
|
|
515
|
+
|
|
516
|
+
// --- RoutePattern construction and operations ---
|
|
517
|
+
val route = Method.GET / "users" / PathCodec.int("id")
|
|
518
|
+
|
|
519
|
+
// Decode: extract a typed value from a method + path
|
|
520
|
+
val routeDecoded: Either[String, Int] = route.decode(Method.GET, Path("/users/42"))
|
|
521
|
+
println(s"Route decoded: $routeDecoded")
|
|
522
|
+
|
|
523
|
+
// Encode: rebuild (Method, Path) from a typed value
|
|
524
|
+
val routeEncoded: Either[String, (Method, Path)] = route.encode(42)
|
|
525
|
+
println(s"Route encoded: $routeEncoded")
|
|
526
|
+
|
|
527
|
+
// Render: human-readable string matching OpenAPI path parameter convention
|
|
528
|
+
println(s"Route rendered: ${route.render}")
|
|
529
|
+
|
|
530
|
+
// Nest: prepend a version prefix to an existing pattern
|
|
531
|
+
val versioned = route.nest(PathCodec("/api/v1"))
|
|
532
|
+
println(s"Versioned route: ${versioned.render}")
|
|
533
|
+
|
|
534
|
+
// --- Type transformations ---
|
|
535
|
+
final case class UserId(value: Int)
|
|
536
|
+
|
|
537
|
+
val userIdCodec: PathCodec[UserId] =
|
|
538
|
+
PathCodec.int("id").transform[UserId](UserId(_), _.value)
|
|
539
|
+
|
|
540
|
+
val decodedUserId = userIdCodec.decode(Path("/99"))
|
|
541
|
+
println(s"UserId decoded: $decodedUserId")
|
|
542
|
+
|
|
543
|
+
// transformOrFail: reject non-positive segment values at parse time
|
|
544
|
+
val positiveInt: PathCodec[Int] =
|
|
545
|
+
PathCodec
|
|
546
|
+
.int("count")
|
|
547
|
+
.transformOrFail[Int](
|
|
548
|
+
n => if (n > 0) Right(n) else Left(s"Expected positive, got $n"),
|
|
549
|
+
n => Right(n)
|
|
550
|
+
)
|
|
551
|
+
|
|
552
|
+
println(s"Positive decode 5: ${positiveInt.decode(Path("/5"))}")
|
|
553
|
+
println(s"Positive decode -1: ${positiveInt.decode(Path("/-1"))}")
|
|
554
|
+
|
|
555
|
+
println("PathAndSegmentCodecs complete")
|
|
556
|
+
}
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
([source](https://github.com/zio/zio-blocks/blob/main/endpoint-examples/src/main/scala/endpointexamples/PathAndSegmentCodecs.scala))
|
|
560
|
+
|
|
561
|
+
```bash
|
|
562
|
+
sbt "endpoint-examples/runMain endpointexamples.PathAndSegmentCodecs"
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
### AuthType Patterns
|
|
566
|
+
|
|
567
|
+
Shows all built-in `AuthType` variants (None, Basic, Bearer, Digest), a custom API-key variant, OR composition with `|`, scoped bearer tokens for OAuth scope metadata, and overriding the default unauthorized status.
|
|
568
|
+
|
|
569
|
+
```scala title="endpoint-examples/src/main/scala/endpointexamples/AuthTypePatterns.scala"
|
|
570
|
+
/*
|
|
571
|
+
* Copyright 2024-2026 John A. De Goes and the ZIO Contributors
|
|
572
|
+
*
|
|
573
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
574
|
+
* you may not use this file except in compliance with the License.
|
|
575
|
+
* You may obtain a copy of the License at
|
|
576
|
+
*
|
|
577
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
578
|
+
*
|
|
579
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
580
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
581
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
582
|
+
* See the License for the specific language governing permissions and
|
|
583
|
+
* limitations under the License.
|
|
584
|
+
*/
|
|
585
|
+
|
|
586
|
+
package endpointexamples
|
|
587
|
+
|
|
588
|
+
import scala.language.implicitConversions
|
|
589
|
+
|
|
590
|
+
import zio.blocks.endpoint._
|
|
591
|
+
import zio.blocks.endpoint.RoutePattern._
|
|
592
|
+
import zio.blocks.schema.Schema
|
|
593
|
+
import zio.http.{Method, Status}
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* AuthType — Authentication Scheme Variants and Composition
|
|
597
|
+
*
|
|
598
|
+
* Demonstrates all built-in `AuthType` variants (None, Basic, Bearer, Digest),
|
|
599
|
+
* the Custom variant for API keys, OR composition with `|`, scoped bearer
|
|
600
|
+
* tokens, and overriding the default unauthorized status.
|
|
601
|
+
*
|
|
602
|
+
* Run with: sbt "endpoint-examples/runMain endpointexamples.AuthTypePatterns"
|
|
603
|
+
*/
|
|
604
|
+
@main def AuthTypePatterns(): Unit = {
|
|
605
|
+
|
|
606
|
+
// --- AuthType.None (default) ---
|
|
607
|
+
// No authentication required; every new Endpoint starts with None
|
|
608
|
+
val publicEndpoint = Endpoint(Method.GET / "health")
|
|
609
|
+
.out(Schema.string)
|
|
610
|
+
|
|
611
|
+
println(s"Public endpoint auth: ${publicEndpoint.auth}")
|
|
612
|
+
|
|
613
|
+
// --- AuthType.Basic ---
|
|
614
|
+
val basicEndpoint = Endpoint(Method.GET / "admin")
|
|
615
|
+
.auth(AuthType.Basic)
|
|
616
|
+
|
|
617
|
+
println(s"Basic auth unauth status: ${basicEndpoint.auth.unauthorizedStatus}")
|
|
618
|
+
|
|
619
|
+
// --- AuthType.Bearer ---
|
|
620
|
+
val bearerEndpoint = Endpoint(Method.GET / "me")
|
|
621
|
+
.auth(AuthType.Bearer)
|
|
622
|
+
|
|
623
|
+
println(s"Bearer auth unauth status: ${bearerEndpoint.auth.unauthorizedStatus}")
|
|
624
|
+
|
|
625
|
+
// --- AuthType.Digest ---
|
|
626
|
+
val digestEndpoint = Endpoint(Method.GET / "secure")
|
|
627
|
+
.auth(AuthType.Digest)
|
|
628
|
+
|
|
629
|
+
println(s"Digest auth unauth status: ${digestEndpoint.auth.unauthorizedStatus}")
|
|
630
|
+
|
|
631
|
+
// --- AuthType.Custom ---
|
|
632
|
+
// Wrap any HttpCodec for schemes not covered by the built-in variants
|
|
633
|
+
val apiKeyCodec = HttpCodec.requestHeader("X-Api-Key", Schema.string)
|
|
634
|
+
val apiKeyAuth = AuthType.Custom(apiKeyCodec)
|
|
635
|
+
val keyEndpoint = Endpoint(Method.GET / "data").auth(apiKeyAuth)
|
|
636
|
+
|
|
637
|
+
println(s"Custom auth unauth status: ${keyEndpoint.auth.unauthorizedStatus}")
|
|
638
|
+
|
|
639
|
+
// --- OR composition: accept either scheme ---
|
|
640
|
+
// The codec tries the left scheme first and falls back to the right
|
|
641
|
+
val flexEndpoint = Endpoint(Method.GET / "resource")
|
|
642
|
+
.auth(AuthType.Basic | AuthType.Bearer)
|
|
643
|
+
|
|
644
|
+
println(s"Flex auth unauth status: ${flexEndpoint.auth.unauthorizedStatus}")
|
|
645
|
+
|
|
646
|
+
// --- Scoped bearer: attach OAuth scope metadata ---
|
|
647
|
+
val scopedEndpoint = Endpoint(Method.GET / "admin")
|
|
648
|
+
.auth(AuthType.Scoped(AuthType.Bearer, List("admin:read", "admin:write")))
|
|
649
|
+
|
|
650
|
+
println(s"Scoped auth unauth status: ${scopedEndpoint.auth.unauthorizedStatus}")
|
|
651
|
+
|
|
652
|
+
// --- Override the default unauthorized status ---
|
|
653
|
+
// Default is Status.NotFound (to avoid leaking endpoint existence);
|
|
654
|
+
// override to Status.Unauthorized when endpoint existence is public knowledge
|
|
655
|
+
val strictEndpoint = Endpoint(Method.GET / "me")
|
|
656
|
+
.auth(AuthType.Bearer)
|
|
657
|
+
.unauthorizedStatus(Status.Unauthorized)
|
|
658
|
+
|
|
659
|
+
println(s"Strict unauth status: ${strictEndpoint.auth.unauthorizedStatus}")
|
|
660
|
+
|
|
661
|
+
println("AuthTypePatterns complete")
|
|
662
|
+
}
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
([source](https://github.com/zio/zio-blocks/blob/main/endpoint-examples/src/main/scala/endpointexamples/AuthTypePatterns.scala))
|
|
666
|
+
|
|
667
|
+
```bash
|
|
668
|
+
sbt "endpoint-examples/runMain endpointexamples.AuthTypePatterns"
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
### Complete REST API
|
|
672
|
+
|
|
673
|
+
Assembles a full users CRUD API combining `Endpoint`, `RoutePattern`, `PathCodec`, `SegmentCodec`, `HttpCodec`, `AuthType`, and `RouteTree`. Demonstrates versioned routes via `nest`, Scala 3 union error types via `orOutError`, and `RouteTree` lookup priority for efficient O(depth) dispatch.
|
|
674
|
+
|
|
675
|
+
```scala title="endpoint-examples/src/main/scala/endpointexamples/CompleteApiDefinition.scala"
|
|
676
|
+
/*
|
|
677
|
+
* Copyright 2024-2026 John A. De Goes and the ZIO Contributors
|
|
678
|
+
*
|
|
679
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
680
|
+
* you may not use this file except in compliance with the License.
|
|
681
|
+
* You may obtain a copy of the License at
|
|
682
|
+
*
|
|
683
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
684
|
+
*
|
|
685
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
686
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
687
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
688
|
+
* See the License for the specific language governing permissions and
|
|
689
|
+
* limitations under the License.
|
|
690
|
+
*/
|
|
691
|
+
|
|
692
|
+
package endpointexamples
|
|
693
|
+
|
|
694
|
+
import scala.language.implicitConversions
|
|
695
|
+
|
|
696
|
+
import zio.blocks.endpoint._
|
|
697
|
+
import zio.blocks.endpoint.RoutePattern._
|
|
698
|
+
import zio.blocks.schema.Schema
|
|
699
|
+
import zio.http.{Method, Path, Status}
|
|
700
|
+
|
|
701
|
+
/**
|
|
702
|
+
* Complete REST API — All Endpoint Types Working Together
|
|
703
|
+
*
|
|
704
|
+
* Shows a full users CRUD API built from `Endpoint`, `RoutePattern`,
|
|
705
|
+
* `PathCodec`, `SegmentCodec`, `HttpCodec`, `AuthType`, and `RouteTree`.
|
|
706
|
+
* Demonstrates versioned routes via `nest`, Scala 3 union error types via
|
|
707
|
+
* `orOutError`, and `RouteTree` lookup priority.
|
|
708
|
+
*
|
|
709
|
+
* Run with: sbt "endpoint-examples/runMain
|
|
710
|
+
* endpointexamples.CompleteApiDefinition"
|
|
711
|
+
*/
|
|
712
|
+
@main def CompleteApiDefinition(): Unit = {
|
|
713
|
+
|
|
714
|
+
// --- Domain type with a typed path codec ---
|
|
715
|
+
final case class UserId(value: Int)
|
|
716
|
+
|
|
717
|
+
val userIdPath: PathCodec[UserId] =
|
|
718
|
+
PathCodec.int("id").transform[UserId](UserId(_), _.value)
|
|
719
|
+
|
|
720
|
+
// --- Endpoint definitions ---
|
|
721
|
+
|
|
722
|
+
// GET /users?page=&limit= — paginated list, bearer-secured
|
|
723
|
+
val listUsers = Endpoint(Method.GET / "users")
|
|
724
|
+
.query("page", Schema.int)
|
|
725
|
+
.query("limit", Schema.int)
|
|
726
|
+
.out(Schema.string)
|
|
727
|
+
.outError(Status.BadRequest, Schema.string)
|
|
728
|
+
.auth(AuthType.Bearer)
|
|
729
|
+
|
|
730
|
+
// GET /users/{id} — fetch a single user, bearer-secured
|
|
731
|
+
val getUser = Endpoint(Method.GET / "users" / userIdPath)
|
|
732
|
+
.out(Schema.string)
|
|
733
|
+
.outError(Status.NotFound, Schema.string)
|
|
734
|
+
.auth(AuthType.Bearer)
|
|
735
|
+
|
|
736
|
+
// POST /users — create a user, 201 on success, bearer-secured
|
|
737
|
+
val createUser = Endpoint(Method.POST / "users")
|
|
738
|
+
.in(Schema.string)
|
|
739
|
+
.out(Status.Created, Schema.int)
|
|
740
|
+
.outError(Status.BadRequest, Schema.string)
|
|
741
|
+
.outError(Status.Conflict, Schema.string)
|
|
742
|
+
.auth(AuthType.Bearer)
|
|
743
|
+
|
|
744
|
+
// DELETE /users/{id} — remove a user, bearer-secured
|
|
745
|
+
val deleteUser = Endpoint(Method.DELETE / "users" / PathCodec.int("id"))
|
|
746
|
+
.out(Status.NoContent, Schema.unit)
|
|
747
|
+
.outError(Status.NotFound, Schema.string)
|
|
748
|
+
.auth(AuthType.Bearer)
|
|
749
|
+
|
|
750
|
+
// GET /health — public health check, no auth required
|
|
751
|
+
val health = Endpoint(Method.GET / "health")
|
|
752
|
+
.out(Schema.string)
|
|
753
|
+
|
|
754
|
+
// --- Union error types (Scala 3 only) ---
|
|
755
|
+
// orOutError accumulates error types as a native union instead of nested Eithers;
|
|
756
|
+
// the first call sets Err directly, subsequent calls widen to a union
|
|
757
|
+
val withUnionErrors = Endpoint(Method.GET / "items" / PathCodec.int("id"))
|
|
758
|
+
.orOutError(Status.NotFound, Schema.string)
|
|
759
|
+
.orOutError(Status.Conflict, Schema.int)
|
|
760
|
+
|
|
761
|
+
val _: Endpoint[Int, Unit, String | Int, Unit, AuthType.None.type] = withUnionErrors
|
|
762
|
+
|
|
763
|
+
// --- Versioned routes via nest ---
|
|
764
|
+
val v1ListUsers = listUsers.route.nest(PathCodec("/api/v1"))
|
|
765
|
+
val v2ListUsers = listUsers.route.nest(PathCodec("/api/v2"))
|
|
766
|
+
|
|
767
|
+
println("Endpoint routes:")
|
|
768
|
+
println(s" ${listUsers.route.render}")
|
|
769
|
+
println(s" ${getUser.route.render}")
|
|
770
|
+
println(s" ${createUser.route.render}")
|
|
771
|
+
println(s" ${deleteUser.route.render}")
|
|
772
|
+
println(s" ${health.route.render}")
|
|
773
|
+
println(s" v1: ${v1ListUsers.render}")
|
|
774
|
+
println(s" v2: ${v2ListUsers.render}")
|
|
775
|
+
|
|
776
|
+
// --- RouteTree: O(depth) routing trie ---
|
|
777
|
+
// Literals are matched first; dynamic segments follow priority ordering
|
|
778
|
+
// (int > long > uuid > bool > string > combined > trailing)
|
|
779
|
+
val tree = RouteTree
|
|
780
|
+
.empty[String]
|
|
781
|
+
.add(Method.GET / "users", "list-users")
|
|
782
|
+
.add(Method.GET / "users" / PathCodec.int("id"), "get-user")
|
|
783
|
+
.add(Method.POST / "users", "create-user")
|
|
784
|
+
.add(Method.DELETE / "users" / PathCodec.int("id"), "delete-user")
|
|
785
|
+
.add(Method.GET / "health", "health")
|
|
786
|
+
|
|
787
|
+
println("\nRouteTree lookups:")
|
|
788
|
+
println(s" GET /users → ${tree.get(Method.GET, Path("/users"))}")
|
|
789
|
+
println(s" GET /users/42 → ${tree.get(Method.GET, Path("/users/42"))}")
|
|
790
|
+
println(s" POST /users → ${tree.get(Method.POST, Path("/users"))}")
|
|
791
|
+
println(s" DELETE /users/7 → ${tree.get(Method.DELETE, Path("/users/7"))}")
|
|
792
|
+
println(s" GET /health → ${tree.get(Method.GET, Path("/health"))}")
|
|
793
|
+
// HEAD falls back to GET per HTTP spec
|
|
794
|
+
println(s" HEAD /users → ${tree.get(Method.HEAD, Path("/users"))}")
|
|
795
|
+
// Unregistered path returns None
|
|
796
|
+
println(s" GET /notfound → ${tree.get(Method.GET, Path("/notfound"))}")
|
|
797
|
+
|
|
798
|
+
// --- RouteTree merge: right-hand side wins on conflict ---
|
|
799
|
+
val treeA = RouteTree.empty[String].add(Method.GET / "users", "users-v1")
|
|
800
|
+
val treeB = RouteTree.empty[String].add(Method.GET / "users", "users-v2")
|
|
801
|
+
val merged = treeA.merge(treeB)
|
|
802
|
+
println(s"\nMerged GET /users → ${merged.get(Method.GET, Path("/users"))}")
|
|
803
|
+
|
|
804
|
+
// --- RoutePattern decode and encode ---
|
|
805
|
+
val route = Method.GET / "users" / PathCodec.int("id")
|
|
806
|
+
val decoded = route.decode(Method.GET, Path("/users/99"))
|
|
807
|
+
val encoded = route.encode(99)
|
|
808
|
+
println(s"\nRoute decode /users/99 → $decoded")
|
|
809
|
+
println(s"Route encode 99 → $encoded")
|
|
810
|
+
|
|
811
|
+
println("\nCompleteApiDefinition complete")
|
|
812
|
+
}
|
|
813
|
+
```
|
|
814
|
+
|
|
815
|
+
([source](https://github.com/zio/zio-blocks/blob/main/endpoint-examples/src/main/scala/endpointexamples/CompleteApiDefinition.scala))
|
|
816
|
+
|
|
817
|
+
```bash
|
|
818
|
+
sbt "endpoint-examples/runMain endpointexamples.CompleteApiDefinition"
|
|
819
|
+
```
|
|
820
|
+
|
|
821
|
+
**3. Or compile all examples at once:**
|
|
822
|
+
|
|
823
|
+
```bash
|
|
824
|
+
sbt "endpoint-examples/compile"
|
|
825
|
+
```
|