@zio.dev/zio-blocks 0.0.33 → 0.0.51

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/guides/compile-time-resource-safety-with-scope.md +16 -17
  2. package/guides/getting-started-with-mux.md +1507 -0
  3. package/guides/query-dsl-extending.md +161 -102
  4. package/guides/query-dsl-fluent-builder.md +217 -157
  5. package/guides/query-dsl-reified-optics.md +12 -10
  6. package/guides/query-dsl-sql.md +246 -165
  7. package/guides/telemetry-guide.md +1069 -0
  8. package/guides/zio-schema-migration.md +29 -22
  9. package/index.md +292 -50
  10. package/package.json +1 -1
  11. package/plans/config-follow-up-prs.md +188 -0
  12. package/plans/config-pr-assessment-roadmap.md +310 -0
  13. package/reference/MuxDataFlow.jsx +250 -0
  14. package/reference/async.md +651 -0
  15. package/reference/chunk.md +3533 -308
  16. package/reference/codegen/case-class.md +436 -0
  17. package/reference/codegen/emitter-config.md +383 -0
  18. package/reference/codegen/examples.md +664 -0
  19. package/reference/codegen/field.md +316 -0
  20. package/reference/codegen/index.md +317 -0
  21. package/reference/codegen/scala-emitter.md +392 -0
  22. package/reference/codegen/scala-file.md +276 -0
  23. package/reference/codegen/sealed-trait.md +408 -0
  24. package/reference/codegen/type-definition.md +340 -0
  25. package/reference/codegen/type-ref.md +201 -0
  26. package/reference/combinators.md +347 -117
  27. package/reference/config.md +158 -0
  28. package/reference/context.md +4 -4
  29. package/reference/datastar.md +346 -0
  30. package/reference/docs.md +1461 -345
  31. package/reference/endpoint/auth-type.md +146 -0
  32. package/reference/endpoint/endpoint.md +297 -0
  33. package/reference/endpoint/http-codec.md +249 -0
  34. package/reference/endpoint/index.md +825 -0
  35. package/reference/endpoint/path-codec.md +237 -0
  36. package/reference/endpoint/route-pattern.md +196 -0
  37. package/reference/endpoint/route-tree.md +111 -0
  38. package/reference/endpoint/segment-codec.md +212 -0
  39. package/reference/html.md +1120 -0
  40. package/reference/htmx/attribute-values.md +359 -0
  41. package/reference/htmx/hx-encoding.md +111 -0
  42. package/reference/htmx/hx-params.md +204 -0
  43. package/reference/htmx/hx-swap.md +276 -0
  44. package/reference/htmx/hx-sync.md +251 -0
  45. package/reference/htmx/hx-target.md +314 -0
  46. package/reference/htmx/hx-trigger.md +457 -0
  47. package/reference/htmx/hx-url-update.md +239 -0
  48. package/reference/htmx/index.md +855 -0
  49. package/reference/http-model/index.md +47 -0
  50. package/reference/http-model/model.md +1481 -0
  51. package/reference/http-model/schema.md +747 -0
  52. package/reference/maybe.md +826 -0
  53. package/reference/media-type.md +2 -2
  54. package/reference/mux.mdx +823 -0
  55. package/reference/openapi.md +1351 -0
  56. package/reference/resource-management/defer-handle.md +1 -1
  57. package/reference/resource-management/resource.md +31 -2
  58. package/reference/resource-management/scope.md +28 -12
  59. package/reference/resource-management/wire.md +3 -7
  60. package/reference/ringbuffer/MpmcDiagram.jsx +717 -0
  61. package/reference/ringbuffer/MpscDiagram.jsx +618 -0
  62. package/reference/ringbuffer/SpmcDiagram.jsx +680 -0
  63. package/reference/ringbuffer/SpscDiagram.jsx +677 -0
  64. package/reference/ringbuffer/advanced.mdx +109 -0
  65. package/reference/ringbuffer/index.mdx +145 -0
  66. package/reference/ringbuffer/mpmc.mdx +151 -0
  67. package/reference/ringbuffer/mpsc.mdx +132 -0
  68. package/reference/ringbuffer/spmc.mdx +108 -0
  69. package/reference/ringbuffer/spsc.mdx +344 -0
  70. package/reference/{allows.md → schema/allows.md} +4 -4
  71. package/reference/{binding-resolver.md → schema/binding-resolver.md} +1 -1
  72. package/reference/{binding.md → schema/binding.md} +2 -3
  73. package/reference/schema/built-in-codecs/avro.md +451 -0
  74. package/reference/schema/built-in-codecs/bson.md +480 -0
  75. package/reference/schema/built-in-codecs/csv.md +564 -0
  76. package/reference/schema/built-in-codecs/index.md +77 -0
  77. package/reference/schema/built-in-codecs/json/index.md +295 -0
  78. package/reference/schema/built-in-codecs/json/json-config.md +217 -0
  79. package/reference/{json-patch.md → schema/built-in-codecs/json/json-patch.md} +5 -5
  80. package/reference/{json-schema.md → schema/built-in-codecs/json/json-schema.md} +14 -47
  81. package/reference/schema/built-in-codecs/json/json-selection.md +322 -0
  82. package/reference/{json.md → schema/built-in-codecs/json/json.md} +32 -64
  83. package/reference/schema/built-in-codecs/messagepack.md +508 -0
  84. package/reference/schema/built-in-codecs/thrift.md +433 -0
  85. package/reference/schema/built-in-codecs/toon.md +1078 -0
  86. package/reference/{xml.md → schema/built-in-codecs/xml.md} +13 -9
  87. package/reference/schema/built-in-codecs/yaml.md +552 -0
  88. package/reference/{codec.md → schema/codec.md} +10 -10
  89. package/reference/{dynamic-optic.md → schema/dynamic-optic.md} +151 -5
  90. package/reference/{dynamic-schema.md → schema/dynamic-schema.md} +8 -8
  91. package/reference/schema/format.md +92 -0
  92. package/reference/schema/index.md +50 -0
  93. package/reference/schema/migration.md +297 -0
  94. package/reference/{modifier.md → schema/modifier.md} +58 -7
  95. package/reference/{optics.md → schema/optics.md} +2 -2
  96. package/reference/{patch.md → schema/patch.md} +1 -1
  97. package/{path-interpolator.md → reference/schema/path-interpolator.md} +165 -72
  98. package/reference/{schema-evolution → schema/schema-evolution}/as.md +8 -8
  99. package/reference/{schema-evolution → schema/schema-evolution}/index.md +2 -2
  100. package/reference/{schema-evolution → schema/schema-evolution}/into.md +8 -8
  101. package/reference/{schema-expr.md → schema/schema-expr.md} +110 -175
  102. package/reference/{schema.md → schema/schema.md} +12 -0
  103. package/reference/{structural-types.md → schema/structural-types.md} +1 -1
  104. package/reference/{type-class-derivation.md → schema/type-class-derivation.md} +63 -1
  105. package/reference/smithy.md +533 -0
  106. package/reference/sql/db-codec-deriver.md +71 -0
  107. package/reference/sql/db-codec.md +687 -0
  108. package/reference/sql/db-con.md +271 -0
  109. package/reference/sql/db-connection.md +153 -0
  110. package/reference/sql/db-param-writer.md +77 -0
  111. package/reference/sql/db-param.md +66 -0
  112. package/reference/sql/db-result-reader.md +146 -0
  113. package/reference/sql/db-tx.md +82 -0
  114. package/reference/sql/db-value.md +41 -0
  115. package/reference/sql/ddl.md +85 -0
  116. package/reference/sql/frag.md +254 -0
  117. package/reference/sql/index.md +341 -0
  118. package/reference/sql/repo.md +600 -0
  119. package/reference/sql/sql-dialect.md +73 -0
  120. package/reference/sql/sql-logger.md +62 -0
  121. package/reference/sql/sql-name-mapper.md +70 -0
  122. package/reference/sql/table-metadata.md +134 -0
  123. package/reference/sql/table.md +448 -0
  124. package/reference/sql/transactor-zio.md +399 -0
  125. package/reference/sql/transactor.md +353 -0
  126. package/reference/sql-zio.md +112 -0
  127. package/reference/streams/concurrent-operators.md +106 -0
  128. package/reference/streams/index.md +653 -0
  129. package/reference/streams/pipeline.md +718 -0
  130. package/reference/streams/reader.md +1284 -0
  131. package/reference/streams/scala-2-compatibility.md +55 -0
  132. package/reference/streams/sink.md +1426 -0
  133. package/reference/streams/stream.md +2526 -0
  134. package/reference/streams/writer.md +1045 -0
  135. package/reference/streams/zero-boxing.md +275 -0
  136. package/reference/telemetry.md +693 -0
  137. package/reference/typeid.md +5 -19
  138. package/sidebars.js +238 -43
  139. package/reference/formats.md +0 -694
  140. package/reference/http-model.md +0 -1716
  141. package/reference/streams.md +0 -989
  142. package/ringbuffer.md +0 -249
  143. /package/reference/{json-differ.md → schema/built-in-codecs/json/json-differ.md} +0 -0
  144. /package/reference/{dynamic-value.md → schema/dynamic-value.md} +0 -0
  145. /package/reference/{lazy.md → schema/lazy.md} +0 -0
  146. /package/reference/{reflect.md → schema/reflect.md} +0 -0
  147. /package/reference/{registers.md → schema/registers.md} +0 -0
  148. /package/reference/{schema-error.md → schema/schema-error.md} +0 -0
  149. /package/reference/{syntax.md → schema/syntax.md} +0 -0
  150. /package/reference/{validation.md → schema/validation.md} +0 -0
@@ -0,0 +1,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
+ ```