0
# Path Matching and Extraction
1
2
Comprehensive path decomposition and parameter extraction system for building REST APIs with type-safe URL routing, query parameter handling, and file extension matching.
3
4
## Capabilities
5
6
### Basic Path Extractors
7
8
Core path decomposition operators for building URL routing patterns.
9
10
```scala { .api }
11
/**
12
* Root path constant - represents the root "/" path
13
*/
14
val Root: Uri.Path.Root.type
15
16
/**
17
* Path segment extractor - separates path into parent and last segment
18
*/
19
object / {
20
def unapply(path: Path): Option[(Path, String)]
21
}
22
23
/**
24
* Path prefix extractor - extracts first segment and remaining path
25
*/
26
object /: {
27
def unapply(path: Path): Option[(String, Path)]
28
}
29
```
30
31
**Usage Examples:**
32
33
```scala
34
import org.http4s.dsl.io._
35
36
val routes = HttpRoutes.of[IO] {
37
// Root path
38
case GET -> Root =>
39
Ok("Home page")
40
41
// Single segment
42
case GET -> Root / "about" =>
43
Ok("About page")
44
45
// Multiple segments
46
case GET -> Root / "api" / "v1" / "users" =>
47
Ok("Users API v1")
48
49
// Path prefix matching
50
case GET -> ("api" /: rest) =>
51
Ok(s"API endpoint: $rest")
52
}
53
```
54
55
### Path Variables
56
57
Type-safe extraction of path parameters with automatic parsing and validation.
58
59
```scala { .api }
60
/**
61
* Integer path variable extractor
62
*/
63
object IntVar extends PathVar[Int] {
64
def unapply(str: String): Option[Int]
65
}
66
67
/**
68
* Long path variable extractor
69
*/
70
object LongVar extends PathVar[Long] {
71
def unapply(str: String): Option[Long]
72
}
73
74
/**
75
* UUID path variable extractor
76
*/
77
object UUIDVar extends PathVar[UUID] {
78
def unapply(str: String): Option[UUID]
79
}
80
81
/**
82
* Base class for path variable extractors
83
*/
84
abstract class PathVar[A](cast: String => Try[A]) {
85
def unapply(str: String): Option[A]
86
}
87
```
88
89
**Usage Examples:**
90
91
```scala
92
import org.http4s.dsl.io._
93
import java.util.UUID
94
95
val routes = HttpRoutes.of[IO] {
96
// Integer path parameter
97
case GET -> Root / "users" / IntVar(userId) =>
98
Ok(s"User ID: $userId")
99
100
// Long path parameter
101
case GET -> Root / "orders" / LongVar(orderId) =>
102
Ok(s"Order ID: $orderId")
103
104
// UUID path parameter
105
case GET -> Root / "sessions" / UUIDVar(sessionId) =>
106
Ok(s"Session: $sessionId")
107
108
// Multiple path variables
109
case GET -> Root / "users" / IntVar(userId) / "orders" / LongVar(orderId) =>
110
Ok(s"User $userId, Order $orderId")
111
112
// Path variables with validation (returns None for invalid formats)
113
case GET -> Root / "products" / IntVar(productId) if productId > 0 =>
114
Ok(s"Valid product ID: $productId")
115
}
116
```
117
118
### File Extension Matching
119
120
Extract file extensions from paths and filenames for content negotiation and file handling.
121
122
```scala { .api }
123
/**
124
* File extension extractor for paths and filenames
125
*/
126
object ~ {
127
/** Extract extension from URI path */
128
def unapply(path: Path): Option[(Path, String)]
129
130
/** Extract extension from filename string */
131
def unapply(fileName: String): Option[(String, String)]
132
}
133
```
134
135
**Usage Examples:**
136
137
```scala
138
import org.http4s.dsl.io._
139
140
val routes = HttpRoutes.of[IO] {
141
// File extension from path
142
case GET -> Root / "documents" / (fileName ~ "pdf") =>
143
Ok(s"PDF document: $fileName")
144
145
// Multiple extension matching
146
case GET -> Root / "images" / (fileName ~ ext) =>
147
ext match {
148
case "jpg" | "png" | "gif" => Ok(s"Image: $fileName.$ext")
149
case "svg" => Ok(s"Vector image: $fileName.$ext")
150
case _ => BadRequest("Unsupported image format")
151
}
152
153
// Combined with path variables
154
case GET -> Root / "api" / "files" / IntVar(id) ~ "json" =>
155
Ok(s"JSON file for ID: $id")
156
}
157
```
158
159
### Query Parameter Extraction
160
161
Comprehensive query parameter handling with type safety and validation.
162
163
```scala { .api }
164
/**
165
* Query parameter extractor - extracts all query parameters
166
*/
167
object :? {
168
def unapply[F[_]](req: Request[F]): Some[(Request[F], Map[String, collection.Seq[String]])]
169
}
170
171
/**
172
* Multiple query parameter extractor - allows chaining parameter matchers
173
*/
174
object +& {
175
def unapply(
176
params: Map[String, collection.Seq[String]]
177
): Some[(Map[String, collection.Seq[String]], Map[String, collection.Seq[String]])]
178
}
179
```
180
181
**Usage Examples:**
182
183
```scala
184
import org.http4s.dsl.io._
185
186
// Define query parameter matchers
187
object NameParam extends QueryParamDecoderMatcher[String]("name")
188
object AgeParam extends OptionalQueryParamDecoderMatcher[Int]("age")
189
object ActiveParam extends FlagQueryParamMatcher("active")
190
191
val routes = HttpRoutes.of[IO] {
192
// Single query parameter
193
case GET -> Root / "search" :? NameParam(name) =>
194
Ok(s"Searching for: $name")
195
196
// Multiple query parameters
197
case GET -> Root / "users" :? NameParam(name) +& AgeParam(maybeAge) =>
198
val ageStr = maybeAge.map(_.toString).getOrElse("unknown")
199
Ok(s"User: $name, Age: $ageStr")
200
201
// Flag parameter (present/absent)
202
case GET -> Root / "items" :? ActiveParam(isActive) =>
203
Ok(s"Show active items: $isActive")
204
205
// Access raw query parameters
206
case GET -> Root / "debug" :? queryParams =>
207
Ok(s"Query params: $queryParams")
208
}
209
```
210
211
### Query Parameter Matchers
212
213
Type-safe query parameter matchers with validation and error handling.
214
215
```scala { .api }
216
/**
217
* Basic query parameter matcher with decoder
218
*/
219
abstract class QueryParamDecoderMatcher[T: QueryParamDecoder](name: String) {
220
def unapply(params: Map[String, collection.Seq[String]]): Option[T]
221
def unapplySeq(params: Map[String, collection.Seq[String]]): Option[collection.Seq[T]]
222
}
223
224
/**
225
* Optional query parameter matcher
226
*/
227
abstract class OptionalQueryParamDecoderMatcher[T: QueryParamDecoder](name: String) {
228
def unapply(params: Map[String, collection.Seq[String]]): Option[Option[T]]
229
}
230
231
/**
232
* Query parameter matcher with default value
233
*/
234
abstract class QueryParamDecoderMatcherWithDefault[T: QueryParamDecoder](name: String, default: T) {
235
def unapply(params: Map[String, collection.Seq[String]]): Option[T]
236
}
237
238
/**
239
* Flag query parameter matcher (checks presence/absence)
240
*/
241
abstract class FlagQueryParamMatcher(name: String) {
242
def unapply(params: Map[String, collection.Seq[String]]): Option[Boolean]
243
}
244
245
/**
246
* Multi-value query parameter matcher
247
*/
248
abstract class OptionalMultiQueryParamDecoderMatcher[T: QueryParamDecoder](name: String) {
249
def unapply(
250
params: Map[String, collection.Seq[String]]
251
): Option[ValidatedNel[ParseFailure, List[T]]]
252
}
253
254
/**
255
* Validating query parameter matcher with detailed error reporting
256
*/
257
abstract class ValidatingQueryParamDecoderMatcher[T: QueryParamDecoder](name: String) {
258
def unapply(params: Map[String, collection.Seq[String]]): Option[ValidatedNel[ParseFailure, T]]
259
}
260
```
261
262
**Usage Examples:**
263
264
```scala
265
import org.http4s.dsl.io._
266
import cats.data.Validated
267
268
// Custom query parameter matchers
269
object LimitParam extends QueryParamDecoderMatcherWithDefault[Int]("limit", 10)
270
object TagsParam extends OptionalMultiQueryParamDecoderMatcher[String]("tags")
271
object SortParam extends ValidatingQueryParamDecoderMatcher[String]("sort")
272
273
val routes = HttpRoutes.of[IO] {
274
// Parameter with default value
275
case GET -> Root / "items" :? LimitParam(limit) =>
276
Ok(s"Limit: $limit items")
277
278
// Multi-value parameters
279
case GET -> Root / "posts" :? TagsParam(Validated.Valid(tags)) =>
280
Ok(s"Tags: ${tags.mkString(", ")}")
281
282
case GET -> Root / "posts" :? TagsParam(Validated.Invalid(errors)) =>
283
BadRequest(s"Invalid tags: ${errors.toList.mkString(", ")}")
284
285
// Validating parameters with error handling
286
case GET -> Root / "data" :? SortParam(Validated.Valid(sortBy)) =>
287
Ok(s"Sorting by: $sortBy")
288
289
case GET -> Root / "data" :? SortParam(Validated.Invalid(errors)) =>
290
BadRequest(s"Invalid sort parameter: ${errors.head.sanitized}")
291
}
292
```
293
294
### Matrix Parameters
295
296
Advanced URI matrix parameter extraction for multi-dimensional resource addressing.
297
298
```scala { .api }
299
/**
300
* Matrix parameter extractor for multi-dimensional resource identification
301
*/
302
abstract class MatrixVar[F[_]: Foldable](name: String, domain: F[String]) {
303
def unapplySeq(str: String): Option[Seq[String]]
304
}
305
```
306
307
**Usage Examples:**
308
309
```scala
310
import org.http4s.dsl.io._
311
312
// Define matrix parameter extractor
313
object BoardVar extends MatrixVar("square", List("x", "y"))
314
315
val routes = HttpRoutes.of[IO] {
316
// Matrix parameters: /board/square;x=5;y=3
317
case GET -> Root / "board" / BoardVar(IntVar(x), IntVar(y)) =>
318
Ok(s"Board position: ($x, $y)")
319
}
320
```
321
322
### Advanced Pattern Matching
323
324
Complex pattern matching combinations using the conjunction operator.
325
326
```scala { .api }
327
/**
328
* Conjunction extractor for combining multiple patterns
329
*/
330
object & {
331
def unapply[A](a: A): Some[(A, A)]
332
}
333
```
334
335
**Usage Examples:**
336
337
```scala
338
import org.http4s.dsl.io._
339
340
// Define custom extractors
341
object EvenNumber { def unapply(i: Int): Boolean = i % 2 == 0 }
342
object PositiveNumber { def unapply(i: Int): Boolean = i > 0 }
343
344
val routes = HttpRoutes.of[IO] {
345
// Conjunction matching
346
case GET -> Root / "numbers" / IntVar(EvenNumber() & PositiveNumber()) =>
347
Ok("Even and positive number")
348
349
case GET -> Root / "numbers" / IntVar(n) =>
350
Ok(s"Number: $n")
351
}
352
```
353
354
### Authentication Support
355
356
Extract authentication context from authenticated requests using the `as` extractor.
357
358
```scala { .api }
359
/**
360
* Authentication extractor for AuthedRequest
361
*/
362
trait Auth {
363
object as {
364
def unapply[F[_], A](ar: AuthedRequest[F, A]): Option[(Request[F], A)]
365
}
366
}
367
```
368
369
**Usage Examples:**
370
371
```scala
372
import org.http4s.dsl.io._
373
import org.http4s.AuthedRequest
374
375
// Assuming you have authentication middleware that produces AuthedRequest[IO, User]
376
val authedRoutes = AuthedRoutes.of[User, IO] {
377
case GET -> Root / "profile" as user =>
378
Ok(s"Profile for user: ${user.name}")
379
380
case POST -> Root / "posts" as user =>
381
// Create post for authenticated user
382
Ok(s"Post created by ${user.name}")
383
384
case authedReq @ GET -> Root / "admin" as user if user.isAdmin =>
385
Ok("Admin panel")
386
}
387
```
388
389
## Error Handling
390
391
Path matching provides automatic error handling for common scenarios:
392
393
- **Invalid path variables**: Return `None` from extractors, causing route to not match
394
- **Query parameter parsing errors**: Captured in `ValidatedNel` for detailed error reporting
395
- **Missing required parameters**: Handled through pattern match failure
396
397
## Type Safety
398
399
- All path extractors preserve type information through Scala's type system
400
- Path variables automatically validate and convert string segments to target types
401
- Query parameter matchers provide compile-time type safety with runtime validation
402
- Pattern matching ensures exhaustive handling of route scenarios