@zio.dev/zio-blocks 0.0.33 → 0.0.55

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (215) hide show
  1. package/adr/2026-07-18-data-migration.md +123 -0
  2. package/guides/async-getting-started.md +687 -0
  3. package/guides/compile-time-resource-safety-with-scope.md +21 -16
  4. package/guides/getting-started-with-mux.md +1395 -0
  5. package/guides/query-dsl-extending.md +161 -102
  6. package/guides/query-dsl-fluent-builder.md +217 -157
  7. package/guides/query-dsl-reified-optics.md +12 -10
  8. package/guides/query-dsl-sql.md +640 -165
  9. package/guides/sql-checked-interpolation.md +173 -0
  10. package/guides/sql-transactions.md +286 -0
  11. package/guides/telemetry-guide.md +1130 -0
  12. package/guides/zio-schema-migration.md +29 -22
  13. package/index.md +248 -389
  14. package/package.json +1 -1
  15. package/plans/config-follow-up-prs.md +188 -0
  16. package/plans/config-pr-assessment-roadmap.md +310 -0
  17. package/reference/MuxDataFlow.jsx +250 -0
  18. package/reference/async.md +1499 -0
  19. package/reference/chunk.md +3533 -308
  20. package/reference/codegen/case-class.md +436 -0
  21. package/reference/codegen/emitter-config.md +383 -0
  22. package/reference/codegen/examples.md +664 -0
  23. package/reference/codegen/field.md +316 -0
  24. package/reference/codegen/index.md +317 -0
  25. package/reference/codegen/scala-emitter.md +392 -0
  26. package/reference/codegen/scala-file.md +276 -0
  27. package/reference/codegen/sealed-trait.md +408 -0
  28. package/reference/codegen/type-definition.md +340 -0
  29. package/reference/codegen/type-ref.md +201 -0
  30. package/reference/combinators.md +347 -117
  31. package/reference/config/config-decoder.md +460 -0
  32. package/reference/config/config-source.md +489 -0
  33. package/reference/config/errors.md +278 -0
  34. package/reference/config/flags.md +369 -0
  35. package/reference/config/formats.md +314 -0
  36. package/reference/config/index.md +304 -0
  37. package/reference/config/rollout.md +336 -0
  38. package/reference/context.md +9 -52
  39. package/reference/data-migration.md +269 -0
  40. package/reference/datastar/attributes.md +302 -0
  41. package/reference/datastar/events.md +234 -0
  42. package/reference/datastar/index.md +256 -0
  43. package/reference/datastar/signals.md +230 -0
  44. package/reference/datastar/sse.md +295 -0
  45. package/reference/datastar.md +346 -0
  46. package/reference/docs.md +1461 -345
  47. package/reference/endpoint/auth-type.md +146 -0
  48. package/reference/endpoint/bulk-creation.md +96 -0
  49. package/reference/endpoint/endpoint.md +297 -0
  50. package/reference/endpoint/http-codec.md +249 -0
  51. package/reference/endpoint/index.md +745 -0
  52. package/reference/endpoint/path-codec.md +225 -0
  53. package/reference/endpoint/route-pattern.md +194 -0
  54. package/reference/endpoint/route-tree.md +111 -0
  55. package/reference/endpoint/segment-codec.md +199 -0
  56. package/reference/html.md +1424 -0
  57. package/reference/htmx/attribute-values.md +359 -0
  58. package/reference/htmx/hx-encoding.md +111 -0
  59. package/reference/htmx/hx-params.md +204 -0
  60. package/reference/htmx/hx-swap.md +276 -0
  61. package/reference/htmx/hx-sync.md +251 -0
  62. package/reference/htmx/hx-target.md +314 -0
  63. package/reference/htmx/hx-trigger.md +457 -0
  64. package/reference/htmx/hx-url-update.md +239 -0
  65. package/reference/htmx/index.md +807 -0
  66. package/reference/htmx/response-headers.md +240 -0
  67. package/reference/http-model/headers.md +735 -0
  68. package/reference/http-model/index.md +49 -0
  69. package/reference/http-model/model.md +1517 -0
  70. package/reference/http-model/schema-codecs.md +522 -0
  71. package/reference/http-model/schema.md +750 -0
  72. package/reference/http-model/server-sent-event.md +341 -0
  73. package/reference/jwt.md +195 -0
  74. package/reference/maybe.md +943 -0
  75. package/reference/media-type.md +2 -2
  76. package/reference/mux.md +254 -0
  77. package/reference/mux.mdx +828 -0
  78. package/reference/openapi.md +1351 -0
  79. package/reference/projection.md +654 -0
  80. package/reference/resource-management/defer-handle.md +1 -1
  81. package/reference/resource-management/resource.md +31 -98
  82. package/reference/resource-management/scope.md +28 -220
  83. package/reference/resource-management/wire.md +5 -55
  84. package/reference/ringbuffer/MpmcDiagram.jsx +717 -0
  85. package/reference/ringbuffer/MpscDiagram.jsx +618 -0
  86. package/reference/ringbuffer/SpmcDiagram.jsx +680 -0
  87. package/reference/ringbuffer/SpscDiagram.jsx +677 -0
  88. package/reference/ringbuffer/advanced.mdx +109 -0
  89. package/reference/ringbuffer/index.mdx +145 -0
  90. package/reference/ringbuffer/mpmc.mdx +185 -0
  91. package/reference/ringbuffer/mpsc.mdx +164 -0
  92. package/reference/ringbuffer/spmc.mdx +108 -0
  93. package/reference/ringbuffer/spsc.mdx +416 -0
  94. package/reference/{allows.md → schema/allows.md} +4 -100
  95. package/reference/{binding-resolver.md → schema/binding-resolver.md} +1 -1
  96. package/reference/{binding.md → schema/binding.md} +3 -4
  97. package/reference/schema/built-in-codecs/avro.md +451 -0
  98. package/reference/schema/built-in-codecs/bson.md +510 -0
  99. package/reference/schema/built-in-codecs/csv.md +564 -0
  100. package/reference/schema/built-in-codecs/index.md +77 -0
  101. package/reference/schema/built-in-codecs/json/index.md +295 -0
  102. package/reference/schema/built-in-codecs/json/json-config.md +217 -0
  103. package/reference/{json-patch.md → schema/built-in-codecs/json/json-patch.md} +5 -5
  104. package/reference/{json-schema.md → schema/built-in-codecs/json/json-schema.md} +14 -47
  105. package/reference/schema/built-in-codecs/json/json-selection.md +322 -0
  106. package/reference/{json.md → schema/built-in-codecs/json/json.md} +32 -64
  107. package/reference/schema/built-in-codecs/messagepack.md +508 -0
  108. package/reference/schema/built-in-codecs/thrift.md +433 -0
  109. package/reference/schema/built-in-codecs/toon.md +1078 -0
  110. package/reference/{xml.md → schema/built-in-codecs/xml.md} +13 -9
  111. package/reference/schema/built-in-codecs/yaml.md +552 -0
  112. package/reference/{codec.md → schema/codec.md} +11 -11
  113. package/reference/{dynamic-optic.md → schema/dynamic-optic.md} +196 -5
  114. package/reference/{dynamic-schema.md → schema/dynamic-schema.md} +8 -8
  115. package/reference/schema/format.md +92 -0
  116. package/reference/schema/index.md +52 -0
  117. package/reference/schema/migration.md +297 -0
  118. package/reference/{modifier.md → schema/modifier.md} +58 -7
  119. package/reference/{optics.md → schema/optics.md} +2 -2
  120. package/reference/{patch.md → schema/patch.md} +1 -1
  121. package/{path-interpolator.md → reference/schema/path-interpolator.md} +167 -72
  122. package/reference/schema/reflect-transformer.md +140 -0
  123. package/reference/{schema-evolution → schema/schema-evolution}/as.md +8 -8
  124. package/reference/{schema-evolution → schema/schema-evolution}/index.md +2 -2
  125. package/reference/{schema-evolution → schema/schema-evolution}/into.md +8 -8
  126. package/reference/{schema-expr.md → schema/schema-expr.md} +110 -175
  127. package/reference/schema/schema-search.md +263 -0
  128. package/reference/{schema.md → schema/schema.md} +22 -2
  129. package/reference/{structural-types.md → schema/structural-types.md} +1 -1
  130. package/reference/{type-class-derivation.md → schema/type-class-derivation.md} +63 -1
  131. package/reference/smithy.md +1032 -0
  132. package/reference/sql/db-codec-deriver.md +71 -0
  133. package/reference/sql/db-codec.md +687 -0
  134. package/reference/sql/db-con.md +271 -0
  135. package/reference/sql/db-connection.md +153 -0
  136. package/reference/sql/db-param-writer.md +77 -0
  137. package/reference/sql/db-param.md +66 -0
  138. package/reference/sql/db-result-reader.md +148 -0
  139. package/reference/sql/db-tx.md +114 -0
  140. package/reference/sql/db-value.md +41 -0
  141. package/reference/sql/ddl.md +85 -0
  142. package/reference/sql/frag.md +288 -0
  143. package/reference/sql/index.md +341 -0
  144. package/reference/sql/repo.md +600 -0
  145. package/reference/sql/sql-dialect.md +73 -0
  146. package/reference/sql/sql-logger.md +62 -0
  147. package/reference/sql/sql-name-mapper.md +70 -0
  148. package/reference/sql/table-metadata.md +134 -0
  149. package/reference/sql/table.md +448 -0
  150. package/reference/sql/transactor-zio.md +399 -0
  151. package/reference/sql/transactor.md +363 -0
  152. package/reference/sql-zio.md +112 -0
  153. package/reference/streams/core/index.md +32 -0
  154. package/reference/streams/core/pipeline.md +854 -0
  155. package/reference/streams/core/sink.md +1404 -0
  156. package/reference/streams/core/stream.md +3236 -0
  157. package/reference/streams/execution-and-compatibility/async-execution.md +822 -0
  158. package/reference/streams/execution-and-compatibility/index.md +35 -0
  159. package/reference/streams/execution-and-compatibility/platform-differences.md +297 -0
  160. package/reference/streams/execution-and-compatibility/scala-2-compatibility.md +88 -0
  161. package/reference/streams/execution-and-compatibility/zero-boxing.md +393 -0
  162. package/reference/streams/index.md +726 -0
  163. package/reference/streams/primitives/index.md +30 -0
  164. package/reference/streams/primitives/reader.md +1992 -0
  165. package/reference/streams/primitives/writer.md +1201 -0
  166. package/reference/telemetry/common/any-value.md +90 -0
  167. package/reference/telemetry/common/attribute-key.md +87 -0
  168. package/reference/telemetry/common/attributes.md +118 -0
  169. package/reference/telemetry/common/index.md +39 -0
  170. package/reference/telemetry/common/instrumentation-scope.md +24 -0
  171. package/reference/telemetry/common/resource.md +34 -0
  172. package/reference/telemetry/index.md +311 -0
  173. package/reference/telemetry/logging/index.md +197 -0
  174. package/reference/telemetry/logging/log-enrichment.md +72 -0
  175. package/reference/telemetry/logging/log-formatter.md +100 -0
  176. package/reference/telemetry/logging/log-record-processor.md +56 -0
  177. package/reference/telemetry/logging/log-record.md +44 -0
  178. package/reference/telemetry/logging/log-writer.md +64 -0
  179. package/reference/telemetry/logging/logger-provider.md +142 -0
  180. package/reference/telemetry/logging/logger.md +83 -0
  181. package/reference/telemetry/logging/severity.md +62 -0
  182. package/reference/telemetry/metrics/index.md +150 -0
  183. package/reference/telemetry/metrics/instruments.md +183 -0
  184. package/reference/telemetry/metrics/labeled-instruments.md +74 -0
  185. package/reference/telemetry/metrics/meter-provider.md +76 -0
  186. package/reference/telemetry/metrics/meter.md +98 -0
  187. package/reference/telemetry/metrics/metric-data.md +57 -0
  188. package/reference/telemetry/otel/custom-exporter.md +216 -0
  189. package/reference/telemetry/otel/index.md +212 -0
  190. package/reference/telemetry/tracing/index.md +155 -0
  191. package/reference/telemetry/tracing/sampler.md +89 -0
  192. package/reference/telemetry/tracing/span-builder.md +57 -0
  193. package/reference/telemetry/tracing/span-context.md +39 -0
  194. package/reference/telemetry/tracing/span-data.md +32 -0
  195. package/reference/telemetry/tracing/span-kind.md +55 -0
  196. package/reference/telemetry/tracing/span-processor.md +53 -0
  197. package/reference/telemetry/tracing/span-status.md +47 -0
  198. package/reference/telemetry/tracing/span.md +117 -0
  199. package/reference/telemetry/tracing/tracer-provider.md +91 -0
  200. package/reference/telemetry/tracing/tracer.md +52 -0
  201. package/reference/typeid.md +5 -83
  202. package/sidebars.js +376 -43
  203. package/undocumented-report.md +528 -270
  204. package/reference/formats.md +0 -694
  205. package/reference/http-model.md +0 -1716
  206. package/reference/streams.md +0 -989
  207. package/ringbuffer.md +0 -249
  208. /package/reference/{json-differ.md → schema/built-in-codecs/json/json-differ.md} +0 -0
  209. /package/reference/{dynamic-value.md → schema/dynamic-value.md} +0 -0
  210. /package/reference/{lazy.md → schema/lazy.md} +0 -0
  211. /package/reference/{reflect.md → schema/reflect.md} +0 -0
  212. /package/reference/{registers.md → schema/registers.md} +0 -0
  213. /package/reference/{schema-error.md → schema/schema-error.md} +0 -0
  214. /package/reference/{syntax.md → schema/syntax.md} +0 -0
  215. /package/reference/{validation.md → schema/validation.md} +0 -0
@@ -0,0 +1,745 @@
1
+ ---
2
+ id: index
3
+ title: "Endpoint"
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.55"
45
+ ```
46
+
47
+ **Scala.js (Scala 3.x):**
48
+ ```scala
49
+ libraryDependencies += "dev.zio" %%% "zio-blocks-endpoint" % "0.0.55"
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.55" // JVM
55
+ libraryDependencies += "dev.zio" %%% "zio-blocks-next-endpoint" % "0.0.55" // 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
+ package endpointexamples
231
+
232
+ import scala.language.implicitConversions
233
+
234
+ import zio.blocks.endpoint._
235
+ import zio.blocks.endpoint.RoutePattern._
236
+ import zio.blocks.schema.Schema
237
+ import zio.http.{Method, Status}
238
+
239
+ /**
240
+ * Endpoint — Basic Endpoint Definition
241
+ *
242
+ * Demonstrates constructing an `Endpoint` from a `RoutePattern` and attaching
243
+ * request body, query parameters, headers, success outputs, and error outputs
244
+ * using the builder DSL.
245
+ *
246
+ * Run with: sbt "endpoint-examples/runMain
247
+ * endpointexamples.BasicEndpointDefinition"
248
+ */
249
+ @main def BasicEndpointDefinition(): Unit = {
250
+
251
+ // Simplest endpoint: GET /health with a string response
252
+ val health = Endpoint(Method.GET / "health")
253
+ .out(Schema.string)
254
+
255
+ println(s"Health route: ${health.route.render}")
256
+
257
+ // POST with a typed request body and a 201 Created response
258
+ val createUser = Endpoint(Method.POST / "users")
259
+ .in(Schema.string)
260
+ .out(Status.Created, Schema.int)
261
+
262
+ println(s"Create user route: ${createUser.route.render}")
263
+
264
+ // GET with query parameters and a request header
265
+ val listUsers = Endpoint(Method.GET / "users")
266
+ .query("page", Schema.int)
267
+ .query("limit", Schema.int)
268
+ .header("X-Trace-Id", Schema.string)
269
+ .out(Schema.string)
270
+
271
+ println(s"List users route: ${listUsers.route.render}")
272
+
273
+ // GET with a dynamic path segment and typed error variants
274
+ val getUser = Endpoint(Method.GET / "users" / PathCodec.int("id"))
275
+ .out(Schema.string)
276
+ .outError(Status.NotFound, Schema.string)
277
+ .outError(Status.BadRequest, Schema.string)
278
+
279
+ println(s"Get user route: ${getUser.route.render}")
280
+
281
+ // Response header on the success channel
282
+ val withRespHeader = Endpoint(Method.GET / "users")
283
+ .out(Schema.string)
284
+ .outHeader("X-Total-Count", Schema.int)
285
+
286
+ println(s"With response header route: ${withRespHeader.route.render}")
287
+
288
+ println("BasicEndpointDefinition complete")
289
+ }
290
+ ```
291
+
292
+ ([source](https://github.com/zio/zio-blocks/blob/main/endpoint-examples/src/main/scala/endpointexamples/BasicEndpointDefinition.scala))
293
+
294
+ ```bash
295
+ sbt "endpoint-examples/runMain endpointexamples.BasicEndpointDefinition"
296
+ ```
297
+
298
+ ### HttpCodec Smart Constructors and Composition
299
+
300
+ 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 `|`.
301
+
302
+ ```scala title="endpoint-examples/src/main/scala/endpointexamples/HttpCodecConstruction.scala"
303
+ package endpointexamples
304
+
305
+ import zio.blocks.chunk.Chunk
306
+ import zio.blocks.docs.Doc
307
+ import zio.blocks.endpoint._
308
+ import zio.blocks.mediatype.MediaTypes
309
+ import zio.blocks.schema.Schema
310
+
311
+ /**
312
+ * HttpCodec — Smart Constructors and Composition
313
+ *
314
+ * Demonstrates building `HttpCodec` atoms (query parameters, headers, bodies,
315
+ * status codes) and composing them with `++` (sequential) and `|`
316
+ * (alternative).
317
+ *
318
+ * Run with: sbt "endpoint-examples/runMain
319
+ * endpointexamples.HttpCodecConstruction"
320
+ */
321
+ @main def HttpCodecConstruction(): Unit = {
322
+
323
+ // --- Smart constructors ---
324
+
325
+ // Query parameter with an optional default value
326
+ val pageCodec = HttpCodec.query("page", Schema.int, default = Some(1))
327
+ val limitCodec = HttpCodec.query("limit", Schema.int)
328
+
329
+ println(s"Query 'page' default: ${pageCodec.default}")
330
+ println(s"Query 'limit' default: ${limitCodec.default}")
331
+
332
+ // Request header by name and schema
333
+ val traceHeader = HttpCodec.requestHeader("X-Trace-Id", Schema.string)
334
+ println(s"Request header name: ${traceHeader.name}")
335
+
336
+ // Response header
337
+ val totalCount = HttpCodec.responseHeader("X-Total-Count", Schema.int)
338
+ println(s"Response header name: ${totalCount.name}")
339
+
340
+ // Request body restricted to JSON
341
+ val jsonBody =
342
+ HttpCodec.requestBody(Schema.string, mediaTypes = Chunk.single(MediaTypes.application.`json`))
343
+ println(s"Request body codec node: ${jsonBody.getClass.getSimpleName}")
344
+
345
+ // Response body
346
+ val respBody = HttpCodec.responseBody(Schema.string)
347
+ println(s"Response body codec node: ${respBody.getClass.getSimpleName}")
348
+
349
+ // Status code atoms — predefined constants
350
+ println(s"Ok status: ${HttpCodec.Ok}")
351
+ println(s"Created status: ${HttpCodec.Created}")
352
+ println(s"NotFound status: ${HttpCodec.NotFound}")
353
+
354
+ // --- Sequential composition with ++ ---
355
+ // Combines two request-side codecs into a single codec whose value is a tuple
356
+ val nameAndAgeQuery: HttpCodec[CodecKind.Request, (String, Int)] =
357
+ HttpCodec.query("name", Schema.string) ++ HttpCodec.query("age", Schema.int)
358
+
359
+ println(s"Sequential codec: ${nameAndAgeQuery.getClass.getSimpleName}")
360
+
361
+ // --- Alternative composition with | ---
362
+ // Builds a fallback: try the left codec, then the right
363
+ val okOrCreated =
364
+ (HttpCodec.responseBody(Schema.string) ++ HttpCodec.Ok) |
365
+ (HttpCodec.responseBody(Schema.int) ++ HttpCodec.Created)
366
+
367
+ println(s"Alternative codec: ${okOrCreated.getClass.getSimpleName}")
368
+
369
+ // --- Metadata: doc, examples, default ---
370
+ val richQuery = HttpCodec.query(
371
+ name = "limit",
372
+ schema = Schema.int,
373
+ default = Some(20),
374
+ doc = Doc.empty,
375
+ examples = Chunk("default" -> 20, "max" -> 100)
376
+ )
377
+ println(s"Rich query default: ${richQuery.default}")
378
+ println(s"Rich query examples count: ${richQuery.examples.length}")
379
+
380
+ // --- Auth codecs ---
381
+ val bearerCodec = HttpCodec.bearerAuth
382
+ val basicCodec = HttpCodec.basicAuth
383
+ val digestCodec = HttpCodec.digestAuth
384
+ println(s"Bearer codec: ${bearerCodec.getClass.getSimpleName}")
385
+ println(s"Basic codec: ${basicCodec.getClass.getSimpleName}")
386
+ println(s"Digest codec: ${digestCodec.getClass.getSimpleName}")
387
+
388
+ println("HttpCodecConstruction complete")
389
+ }
390
+ ```
391
+
392
+ ([source](https://github.com/zio/zio-blocks/blob/main/endpoint-examples/src/main/scala/endpointexamples/HttpCodecConstruction.scala))
393
+
394
+ ```bash
395
+ sbt "endpoint-examples/runMain endpointexamples.HttpCodecConstruction"
396
+ ```
397
+
398
+ ### PathCodec and SegmentCodec
399
+
400
+ 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.
401
+
402
+ ```scala title="endpoint-examples/src/main/scala/endpointexamples/PathAndSegmentCodecs.scala"
403
+ package endpointexamples
404
+
405
+ import scala.language.implicitConversions
406
+
407
+ import zio.blocks.endpoint._
408
+ import zio.blocks.endpoint.RoutePattern._
409
+ import zio.http.{Method, Path}
410
+
411
+ /**
412
+ * PathCodec and SegmentCodec — Typed Path Construction and Decoding
413
+ *
414
+ * Demonstrates `SegmentCodec` kinds, intra-segment composition with `~`,
415
+ * `PathCodec` construction, bidirectional decode/format, `RoutePattern`
416
+ * matching, nesting, and type transformations.
417
+ *
418
+ * Run with: sbt "endpoint-examples/runMain
419
+ * endpointexamples.PathAndSegmentCodecs"
420
+ */
421
+ @main def PathAndSegmentCodecs(): Unit = {
422
+
423
+ // --- SegmentCodec kinds ---
424
+ val intSeg = SegmentCodec.int("id")
425
+ val strSeg = SegmentCodec.string("slug")
426
+ val uuidSeg = SegmentCodec.uuid("id")
427
+ val boolSeg = SegmentCodec.bool("flag")
428
+ val longSeg = SegmentCodec.long("id")
429
+ val trailing = SegmentCodec.Trailing
430
+
431
+ println(s"Segment kinds: int=${intSeg.render()}, string=${strSeg.render()}, uuid=${uuidSeg.render()}")
432
+ println(s" bool=${boolSeg.render()}, long=${longSeg.render()}, trailing=${trailing.render()}")
433
+
434
+ // Intra-segment composition: single path segment containing a literal prefix and an integer
435
+ // "v42" decodes to 42 and formats 42 back to "v42"
436
+ val versionSeg: SegmentCodec[Int] =
437
+ SegmentCodec.literal("v") ~ SegmentCodec.int("major")
438
+
439
+ println(s"Version segment renders as: ${versionSeg.render()}")
440
+ val formattedVersion: Path = versionSeg.format(3)
441
+ println(s"Version 3 formats to: $formattedVersion")
442
+
443
+ // --- PathCodec construction ---
444
+ val usersPath = PathCodec.literal("users") / PathCodec.int("id")
445
+ val apiPath = PathCodec("/api/v1/users")
446
+ val versionPath = PathCodec(versionSeg)
447
+ println(s"versionPath renders as: ${versionPath.render}")
448
+
449
+ println(s"usersPath renders as: ${usersPath.render}")
450
+ println(s"apiPath renders as: ${apiPath.render}")
451
+
452
+ // Bidirectional: decode a Path to a typed value
453
+ val decoded: Either[String, Int] = PathCodec.int("id").decode(Path("/42"))
454
+ println(s"Decoded /42: $decoded")
455
+
456
+ // Bidirectional: format a typed value back to a Path
457
+ val uuidValue = java.util.UUID.fromString("550e8400-e29b-41d4-a716-446655440000")
458
+ val formatted = PathCodec.uuid("id").format(uuidValue)
459
+ println(s"Formatted UUID: $formatted")
460
+
461
+ // Literal alternatives: match either /users or /members
462
+ val eitherPath: PathCodec[Unit] =
463
+ PathCodec.literal("users").orElse(PathCodec.literal("members"))
464
+
465
+ println(s"orElse matches /users: ${eitherPath.matches(Path("/users"))}")
466
+ println(s"orElse matches /members: ${eitherPath.matches(Path("/members"))}")
467
+
468
+ // --- RoutePattern construction and operations ---
469
+ val route = Method.GET / "users" / PathCodec.int("id")
470
+
471
+ // Decode: extract a typed value from a method + path
472
+ val routeDecoded: Either[String, Int] = route.decode(Method.GET, Path("/users/42"))
473
+ println(s"Route decoded: $routeDecoded")
474
+
475
+ // Encode: rebuild (Method, Path) from a typed value
476
+ val routeEncoded: Either[String, (Method, Path)] = route.encode(42)
477
+ println(s"Route encoded: $routeEncoded")
478
+
479
+ // Render: human-readable string matching OpenAPI path parameter convention
480
+ println(s"Route rendered: ${route.render}")
481
+
482
+ // Nest: prepend a version prefix to an existing pattern
483
+ val versioned = route.nest(PathCodec("/api/v1"))
484
+ println(s"Versioned route: ${versioned.render}")
485
+
486
+ // --- Type transformations ---
487
+ final case class UserId(value: Int)
488
+
489
+ val userIdCodec: PathCodec[UserId] =
490
+ PathCodec.int("id").transform(UserId(_), _.value)
491
+
492
+ val decodedUserId = userIdCodec.decode(Path("/99"))
493
+ println(s"UserId decoded: $decodedUserId")
494
+
495
+ // transformOrFail: reject non-positive segment values at parse time
496
+ val positiveInt: PathCodec[Int] =
497
+ PathCodec
498
+ .int("count")
499
+ .transformOrFail(
500
+ n => if (n > 0) Right(n) else Left(s"Expected positive, got $n"),
501
+ (n: Int) => Right(n)
502
+ )
503
+
504
+ println(s"Positive decode 5: ${positiveInt.decode(Path("/5"))}")
505
+ println(s"Positive decode -1: ${positiveInt.decode(Path("/-1"))}")
506
+
507
+ println("PathAndSegmentCodecs complete")
508
+ }
509
+ ```
510
+
511
+ ([source](https://github.com/zio/zio-blocks/blob/main/endpoint-examples/src/main/scala/endpointexamples/PathAndSegmentCodecs.scala))
512
+
513
+ ```bash
514
+ sbt "endpoint-examples/runMain endpointexamples.PathAndSegmentCodecs"
515
+ ```
516
+
517
+ ### AuthType Patterns
518
+
519
+ 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.
520
+
521
+ ```scala title="endpoint-examples/src/main/scala/endpointexamples/AuthTypePatterns.scala"
522
+ package endpointexamples
523
+
524
+ import scala.language.implicitConversions
525
+
526
+ import zio.blocks.endpoint._
527
+ import zio.blocks.endpoint.RoutePattern._
528
+ import zio.blocks.schema.Schema
529
+ import zio.http.{Method, Status}
530
+
531
+ /**
532
+ * AuthType — Authentication Scheme Variants and Composition
533
+ *
534
+ * Demonstrates all built-in `AuthType` variants (None, Basic, Bearer, Digest),
535
+ * the Custom variant for API keys, OR composition with `|`, scoped bearer
536
+ * tokens, and overriding the default unauthorized status.
537
+ *
538
+ * Run with: sbt "endpoint-examples/runMain endpointexamples.AuthTypePatterns"
539
+ */
540
+ @main def AuthTypePatterns(): Unit = {
541
+
542
+ // --- AuthType.None (default) ---
543
+ // No authentication required; every new Endpoint starts with None
544
+ val publicEndpoint = Endpoint(Method.GET / "health")
545
+ .out(Schema.string)
546
+
547
+ println(s"Public endpoint auth: ${publicEndpoint.auth}")
548
+
549
+ // --- AuthType.Basic ---
550
+ val basicEndpoint = Endpoint(Method.GET / "admin")
551
+ .auth(AuthType.Basic)
552
+
553
+ println(s"Basic auth unauth status: ${basicEndpoint.auth.unauthorizedStatus}")
554
+
555
+ // --- AuthType.Bearer ---
556
+ val bearerEndpoint = Endpoint(Method.GET / "me")
557
+ .auth(AuthType.Bearer)
558
+
559
+ println(s"Bearer auth unauth status: ${bearerEndpoint.auth.unauthorizedStatus}")
560
+
561
+ // --- AuthType.Digest ---
562
+ val digestEndpoint = Endpoint(Method.GET / "secure")
563
+ .auth(AuthType.Digest)
564
+
565
+ println(s"Digest auth unauth status: ${digestEndpoint.auth.unauthorizedStatus}")
566
+
567
+ // --- AuthType.Custom ---
568
+ // Wrap any HttpCodec for schemes not covered by the built-in variants
569
+ val apiKeyCodec = HttpCodec.requestHeader("X-Api-Key", Schema.string)
570
+ val apiKeyAuth = AuthType.Custom(apiKeyCodec)
571
+ val keyEndpoint = Endpoint(Method.GET / "data").auth(apiKeyAuth)
572
+
573
+ println(s"Custom auth unauth status: ${keyEndpoint.auth.unauthorizedStatus}")
574
+
575
+ // --- OR composition: accept either scheme ---
576
+ // The codec tries the left scheme first and falls back to the right
577
+ val flexEndpoint = Endpoint(Method.GET / "resource")
578
+ .auth(AuthType.Basic | AuthType.Bearer)
579
+
580
+ println(s"Flex auth unauth status: ${flexEndpoint.auth.unauthorizedStatus}")
581
+
582
+ // --- Scoped bearer: attach OAuth scope metadata ---
583
+ val scopedEndpoint = Endpoint(Method.GET / "admin")
584
+ .auth(AuthType.Scoped(AuthType.Bearer, List("admin:read", "admin:write")))
585
+
586
+ println(s"Scoped auth unauth status: ${scopedEndpoint.auth.unauthorizedStatus}")
587
+
588
+ // --- Override the default unauthorized status ---
589
+ // Default is Status.NotFound (to avoid leaking endpoint existence);
590
+ // override to Status.Unauthorized when endpoint existence is public knowledge
591
+ val strictEndpoint = Endpoint(Method.GET / "me")
592
+ .auth(AuthType.Bearer)
593
+ .unauthorizedStatus(Status.Unauthorized)
594
+
595
+ println(s"Strict unauth status: ${strictEndpoint.auth.unauthorizedStatus}")
596
+
597
+ println("AuthTypePatterns complete")
598
+ }
599
+ ```
600
+
601
+ ([source](https://github.com/zio/zio-blocks/blob/main/endpoint-examples/src/main/scala/endpointexamples/AuthTypePatterns.scala))
602
+
603
+ ```bash
604
+ sbt "endpoint-examples/runMain endpointexamples.AuthTypePatterns"
605
+ ```
606
+
607
+ ### Complete REST API
608
+
609
+ 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.
610
+
611
+ ```scala title="endpoint-examples/src/main/scala/endpointexamples/CompleteApiDefinition.scala"
612
+ package endpointexamples
613
+
614
+ import scala.language.implicitConversions
615
+
616
+ import zio.blocks.endpoint._
617
+ import zio.blocks.endpoint.RoutePattern._
618
+ import zio.blocks.schema.Schema
619
+ import zio.http.{Method, Path, Status}
620
+
621
+ /**
622
+ * Complete REST API — All Endpoint Types Working Together
623
+ *
624
+ * Shows a full users CRUD API built from `Endpoint`, `RoutePattern`,
625
+ * `PathCodec`, `SegmentCodec`, `HttpCodec`, `AuthType`, and `RouteTree`.
626
+ * Demonstrates versioned routes via `nest`, Scala 3 union error types via
627
+ * `orOutError`, and `RouteTree` lookup priority.
628
+ *
629
+ * Run with: sbt "endpoint-examples/runMain
630
+ * endpointexamples.CompleteApiDefinition"
631
+ */
632
+ @main def CompleteApiDefinition(): Unit = {
633
+
634
+ // --- Domain type with a typed path codec ---
635
+ final case class UserId(value: Int)
636
+
637
+ val userIdPath: PathCodec[UserId] =
638
+ PathCodec.int("id").transform(UserId(_), _.value)
639
+
640
+ // --- Endpoint definitions ---
641
+
642
+ // GET /users?page=&limit= — paginated list, bearer-secured
643
+ val listUsers = Endpoint(Method.GET / "users")
644
+ .query("page", Schema.int)
645
+ .query("limit", Schema.int)
646
+ .out(Schema.string)
647
+ .outError(Status.BadRequest, Schema.string)
648
+ .auth(AuthType.Bearer)
649
+
650
+ // GET /users/{id} — fetch a single user, bearer-secured
651
+ val getUser = Endpoint(Method.GET / "users" / userIdPath)
652
+ .out(Schema.string)
653
+ .outError(Status.NotFound, Schema.string)
654
+ .auth(AuthType.Bearer)
655
+
656
+ // POST /users — create a user, 201 on success, bearer-secured
657
+ val createUser = Endpoint(Method.POST / "users")
658
+ .in(Schema.string)
659
+ .out(Status.Created, Schema.int)
660
+ .outError(Status.BadRequest, Schema.string)
661
+ .outError(Status.Conflict, Schema.string)
662
+ .auth(AuthType.Bearer)
663
+
664
+ // DELETE /users/{id} — remove a user, bearer-secured
665
+ val deleteUser = Endpoint(Method.DELETE / "users" / PathCodec.int("id"))
666
+ .out(Status.NoContent, Schema.unit)
667
+ .outError(Status.NotFound, Schema.string)
668
+ .auth(AuthType.Bearer)
669
+
670
+ // GET /health — public health check, no auth required
671
+ val health = Endpoint(Method.GET / "health")
672
+ .out(Schema.string)
673
+
674
+ // --- Union error types (Scala 3 only) ---
675
+ // orOutError accumulates error types as a native union instead of nested Eithers;
676
+ // the first call sets Err directly, subsequent calls widen to a union
677
+ val withUnionErrors = Endpoint(Method.GET / "items" / PathCodec.int("id"))
678
+ .orOutError(Status.NotFound, Schema.string)
679
+ .orOutError(Status.Conflict, Schema.int)
680
+
681
+ val _: Endpoint[Int, Unit, String | Int, Unit, AuthType.None.type] = withUnionErrors
682
+
683
+ // --- Versioned routes via nest ---
684
+ val v1ListUsers = listUsers.route.nest(PathCodec("/api/v1"))
685
+ val v2ListUsers = listUsers.route.nest(PathCodec("/api/v2"))
686
+
687
+ println("Endpoint routes:")
688
+ println(s" ${listUsers.route.render}")
689
+ println(s" ${getUser.route.render}")
690
+ println(s" ${createUser.route.render}")
691
+ println(s" ${deleteUser.route.render}")
692
+ println(s" ${health.route.render}")
693
+ println(s" v1: ${v1ListUsers.render}")
694
+ println(s" v2: ${v2ListUsers.render}")
695
+
696
+ // --- RouteTree: O(depth) routing trie ---
697
+ // Literals are matched first; dynamic segments follow priority ordering
698
+ // (int > long > uuid > bool > string > combined > trailing)
699
+ val tree = RouteTree
700
+ .empty[String]
701
+ .add(Method.GET / "users", "list-users")
702
+ .add(Method.GET / "users" / PathCodec.int("id"), "get-user")
703
+ .add(Method.POST / "users", "create-user")
704
+ .add(Method.DELETE / "users" / PathCodec.int("id"), "delete-user")
705
+ .add(Method.GET / "health", "health")
706
+
707
+ println("\nRouteTree lookups:")
708
+ println(s" GET /users → ${tree.get(Method.GET, Path("/users"))}")
709
+ println(s" GET /users/42 → ${tree.get(Method.GET, Path("/users/42"))}")
710
+ println(s" POST /users → ${tree.get(Method.POST, Path("/users"))}")
711
+ println(s" DELETE /users/7 → ${tree.get(Method.DELETE, Path("/users/7"))}")
712
+ println(s" GET /health → ${tree.get(Method.GET, Path("/health"))}")
713
+ // HEAD falls back to GET per HTTP spec
714
+ println(s" HEAD /users → ${tree.get(Method.HEAD, Path("/users"))}")
715
+ // Unregistered path returns None
716
+ println(s" GET /notfound → ${tree.get(Method.GET, Path("/notfound"))}")
717
+
718
+ // --- RouteTree merge: right-hand side wins on conflict ---
719
+ val treeA = RouteTree.empty[String].add(Method.GET / "users", "users-v1")
720
+ val treeB = RouteTree.empty[String].add(Method.GET / "users", "users-v2")
721
+ val merged = treeA.merge(treeB)
722
+ println(s"\nMerged GET /users → ${merged.get(Method.GET, Path("/users"))}")
723
+
724
+ // --- RoutePattern decode and encode ---
725
+ val route = Method.GET / "users" / PathCodec.int("id")
726
+ val decoded = route.decode(Method.GET, Path("/users/99"))
727
+ val encoded = route.encode(99)
728
+ println(s"\nRoute decode /users/99 → $decoded")
729
+ println(s"Route encode 99 → $encoded")
730
+
731
+ println("\nCompleteApiDefinition complete")
732
+ }
733
+ ```
734
+
735
+ ([source](https://github.com/zio/zio-blocks/blob/main/endpoint-examples/src/main/scala/endpointexamples/CompleteApiDefinition.scala))
736
+
737
+ ```bash
738
+ sbt "endpoint-examples/runMain endpointexamples.CompleteApiDefinition"
739
+ ```
740
+
741
+ **3. Or compile all examples at once:**
742
+
743
+ ```bash
744
+ sbt "endpoint-examples/compile"
745
+ ```