or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

authentication.mdhttp-methods-routing.mdindex.mdpath-matching.mdquery-parameters.mdstatus-codes-responses.md

query-parameters.mddocs/

0

# Query Parameter Processing

1

2

Type-safe query parameter extraction and validation with support for optional parameters, multi-value parameters, custom decoders, and validation with detailed error reporting.

3

4

## Capabilities

5

6

### Query Parameter Extraction

7

8

#### Basic Query Parameter Extractor (:?)

9

10

Fundamental query parameter extractor that provides access to all query parameters.

11

12

```scala { .api }

13

/**

14

* Query parameter extractor

15

* Extracts request and its query parameters as a Map

16

*/

17

object :? {

18

def unapply[F[_]](req: Request[F]): Some[(Request[F], Map[String, collection.Seq[String]])]

19

}

20

```

21

22

**Usage Examples:**

23

24

```scala

25

val routes = HttpRoutes.of[IO] {

26

// Access all query parameters

27

case GET -> Root / "search" :? params =>

28

val query = params.get("q").flatMap(_.headOption).getOrElse("")

29

Ok(s"Search query: $query")

30

31

// Pattern match with specific extractors

32

case GET -> Root / "users" :? Limit(limit) +& Offset(offset) =>

33

Ok(s"Limit: $limit, Offset: $offset")

34

}

35

```

36

37

#### Parameter Combination (+&)

38

39

Combinator for extracting multiple query parameters from the same request.

40

41

```scala { .api }

42

/**

43

* Multiple parameter extractor combinator

44

* Allows chaining multiple parameter extractors

45

*/

46

object +& {

47

def unapply(params: Map[String, collection.Seq[String]]):

48

Some[(Map[String, collection.Seq[String]], Map[String, collection.Seq[String]])]

49

}

50

```

51

52

**Usage Examples:**

53

54

```scala

55

object Limit extends QueryParamDecoderMatcher[Int]("limit")

56

object Offset extends QueryParamDecoderMatcher[Int]("offset")

57

object SortBy extends QueryParamDecoderMatcher[String]("sort")

58

59

val routes = HttpRoutes.of[IO] {

60

// Combine multiple parameters

61

case GET -> Root / "users" :? Limit(limit) +& Offset(offset) =>

62

Ok(s"Pagination: limit=$limit, offset=$offset")

63

64

// Chain many parameters

65

case GET -> Root / "search" :? Query(q) +& Limit(limit) +& SortBy(sort) =>

66

Ok(s"Search: $q, limit: $limit, sort: $sort")

67

}

68

```

69

70

### Query Parameter Matchers

71

72

#### Basic Query Parameter Decoder Matcher

73

74

Type-safe query parameter extraction with automatic type conversion.

75

76

```scala { .api }

77

/**

78

* Basic query parameter matcher with type conversion

79

* Uses QueryParamDecoder for type-safe conversion

80

*/

81

abstract class QueryParamDecoderMatcher[T: QueryParamDecoder](name: String) {

82

def unapply(params: Map[String, collection.Seq[String]]): Option[T]

83

def unapplySeq(params: Map[String, collection.Seq[String]]): Option[collection.Seq[T]]

84

}

85

```

86

87

**Usage Examples:**

88

89

```scala

90

// Define parameter matchers

91

object UserId extends QueryParamDecoderMatcher[Int]("user_id")

92

object Active extends QueryParamDecoderMatcher[Boolean]("active")

93

object Tags extends QueryParamDecoderMatcher[String]("tags")

94

95

val routes = HttpRoutes.of[IO] {

96

// Single value extraction

97

case GET -> Root / "user" :? UserId(id) =>

98

Ok(s"User ID: $id")

99

100

// Boolean parameters

101

case GET -> Root / "users" :? Active(isActive) =>

102

Ok(s"Active users only: $isActive")

103

104

// Multiple values of same parameter

105

case GET -> Root / "posts" :? Tags.unapplySeq(tags) =>

106

Ok(s"Tags: ${tags.mkString(", ")}")

107

}

108

```

109

110

#### Query Parameter Matcher with Implicit QueryParam

111

112

Simplified matcher that uses implicit QueryParam for parameter name resolution.

113

114

```scala { .api }

115

/**

116

* Query parameter matcher using implicit QueryParam for name resolution

117

*/

118

abstract class QueryParamMatcher[T: QueryParamDecoder: QueryParam]

119

extends QueryParamDecoderMatcher[T](QueryParam[T].key.value)

120

```

121

122

**Usage Examples:**

123

124

```scala

125

// Define parameter with implicit QueryParam

126

case class Limit(value: Int)

127

implicit val limitParam: QueryParam[Limit] = QueryParam.fromKey("limit")

128

implicit val limitDecoder: QueryParamDecoder[Limit] =

129

QueryParamDecoder[Int].map(Limit.apply)

130

131

object LimitMatcher extends QueryParamMatcher[Limit]

132

133

val routes = HttpRoutes.of[IO] {

134

case GET -> Root / "data" :? LimitMatcher(limit) =>

135

Ok(s"Limit: ${limit.value}")

136

}

137

```

138

139

### Optional Query Parameters

140

141

#### Optional Query Parameter Decoder Matcher

142

143

Handle optional query parameters that may or may not be present.

144

145

```scala { .api }

146

/**

147

* Optional query parameter matcher

148

* Returns Some(Some(value)) if parameter present and valid

149

* Returns Some(None) if parameter absent

150

* Returns None if parameter present but invalid

151

*/

152

abstract class OptionalQueryParamDecoderMatcher[T: QueryParamDecoder](name: String) {

153

def unapply(params: Map[String, collection.Seq[String]]): Option[Option[T]]

154

}

155

```

156

157

**Usage Examples:**

158

159

```scala

160

object OptionalLimit extends OptionalQueryParamDecoderMatcher[Int]("limit")

161

object OptionalSort extends OptionalQueryParamDecoderMatcher[String]("sort")

162

163

val routes = HttpRoutes.of[IO] {

164

case GET -> Root / "users" :? OptionalLimit(limitOpt) +& OptionalSort(sortOpt) =>

165

val limit = limitOpt.getOrElse(10)

166

val sort = sortOpt.getOrElse("name")

167

Ok(s"Limit: $limit, Sort: $sort")

168

}

169

```

170

171

#### Query Parameter Matcher with Default Value

172

173

Provide default values for missing query parameters.

174

175

```scala { .api }

176

/**

177

* Query parameter matcher with default value

178

* Returns default value if parameter is missing

179

* Returns None if parameter is present but invalid

180

*/

181

abstract class QueryParamDecoderMatcherWithDefault[T: QueryParamDecoder](name: String, default: T) {

182

def unapply(params: Map[String, collection.Seq[String]]): Option[T]

183

}

184

185

abstract class QueryParamMatcherWithDefault[T: QueryParamDecoder: QueryParam](default: T)

186

extends QueryParamDecoderMatcherWithDefault[T](QueryParam[T].key.value, default)

187

```

188

189

**Usage Examples:**

190

191

```scala

192

object LimitWithDefault extends QueryParamDecoderMatcherWithDefault[Int]("limit", 10)

193

object PageWithDefault extends QueryParamDecoderMatcherWithDefault[Int]("page", 1)

194

195

val routes = HttpRoutes.of[IO] {

196

case GET -> Root / "data" :? LimitWithDefault(limit) +& PageWithDefault(page) =>

197

Ok(s"Page: $page, Limit: $limit")

198

// ?limit=20&page=3 -> "Page: 3, Limit: 20"

199

// ?page=2 -> "Page: 2, Limit: 10" (default limit)

200

// (no params) -> "Page: 1, Limit: 10" (both defaults)

201

}

202

```

203

204

### Multi-Value Query Parameters

205

206

#### Optional Multi-Value Query Parameter Matcher

207

208

Handle query parameters that can appear multiple times.

209

210

```scala { .api }

211

/**

212

* Multi-value query parameter matcher

213

* Handles parameters that appear multiple times in the query string

214

* Returns Validated result with all values or parse errors

215

*/

216

abstract class OptionalMultiQueryParamDecoderMatcher[T: QueryParamDecoder](name: String) {

217

def unapply(params: Map[String, collection.Seq[String]]):

218

Option[ValidatedNel[ParseFailure, List[T]]]

219

}

220

```

221

222

**Usage Examples:**

223

224

```scala

225

import cats.data.Validated

226

import cats.data.Validated.{Invalid, Valid}

227

228

object Tags extends OptionalMultiQueryParamDecoderMatcher[String]("tag")

229

object Ids extends OptionalMultiQueryParamDecoderMatcher[Int]("id")

230

231

val routes = HttpRoutes.of[IO] {

232

// Handle multiple tag parameters: ?tag=scala&tag=http4s&tag=web

233

case GET -> Root / "posts" :? Tags(tagResult) =>

234

tagResult match {

235

case Valid(tags) => Ok(s"Tags: ${tags.mkString(", ")}")

236

case Invalid(errors) => BadRequest(s"Invalid tags: ${errors.toList.mkString(", ")}")

237

}

238

239

// Multiple ID parameters: ?id=1&id=2&id=3

240

case GET -> Root / "users" :? Ids(idResult) =>

241

idResult match {

242

case Valid(ids) => Ok(s"User IDs: ${ids.mkString(", ")}")

243

case Invalid(_) => BadRequest("Invalid user IDs")

244

}

245

}

246

```

247

248

### Validating Query Parameters

249

250

#### Validating Query Parameter Matcher

251

252

Get detailed validation results with error information.

253

254

```scala { .api }

255

/**

256

* Validating query parameter matcher

257

* Returns validation result with detailed error information

258

*/

259

abstract class ValidatingQueryParamDecoderMatcher[T: QueryParamDecoder](name: String) {

260

def unapply(params: Map[String, collection.Seq[String]]):

261

Option[ValidatedNel[ParseFailure, T]]

262

}

263

```

264

265

**Usage Examples:**

266

267

```scala

268

import cats.data.Validated.{Invalid, Valid}

269

270

object ValidatingAge extends ValidatingQueryParamDecoderMatcher[Int]("age")

271

object ValidatingEmail extends ValidatingQueryParamDecoderMatcher[String]("email")

272

273

val routes = HttpRoutes.of[IO] {

274

case GET -> Root / "user" :? ValidatingAge(ageResult) =>

275

ageResult match {

276

case Valid(age) if age >= 0 && age <= 150 =>

277

Ok(s"Valid age: $age")

278

case Valid(age) =>

279

BadRequest(s"Age out of range: $age")

280

case Invalid(errors) =>

281

BadRequest(s"Invalid age format: ${errors.head.sanitized}")

282

}

283

}

284

```

285

286

#### Optional Validating Query Parameter Matcher

287

288

Combine optional parameters with validation.

289

290

```scala { .api }

291

/**

292

* Optional validating query parameter matcher

293

* Returns None if parameter absent, Some(validation result) if present

294

*/

295

abstract class OptionalValidatingQueryParamDecoderMatcher[T: QueryParamDecoder](name: String) {

296

def unapply(params: Map[String, collection.Seq[String]]):

297

Some[Option[ValidatedNel[ParseFailure, T]]]

298

}

299

```

300

301

**Usage Examples:**

302

303

```scala

304

object OptionalValidatingLimit extends OptionalValidatingQueryParamDecoderMatcher[Int]("limit")

305

306

val routes = HttpRoutes.of[IO] {

307

case GET -> Root / "data" :? OptionalValidatingLimit(limitOpt) =>

308

limitOpt match {

309

case None => Ok("No limit specified")

310

case Some(Valid(limit)) if limit > 0 => Ok(s"Limit: $limit")

311

case Some(Valid(limit)) => BadRequest("Limit must be positive")

312

case Some(Invalid(errors)) => BadRequest(s"Invalid limit: ${errors.head.sanitized}")

313

}

314

}

315

```

316

317

### Flag Query Parameters

318

319

#### Boolean Flag Query Parameter Matcher

320

321

Handle boolean flag parameters (present/absent).

322

323

```scala { .api }

324

/**

325

* Boolean flag query parameter matcher

326

* Returns true if parameter is present (regardless of value)

327

* Returns false if parameter is absent

328

*/

329

abstract class FlagQueryParamMatcher(name: String) {

330

def unapply(params: Map[String, collection.Seq[String]]): Option[Boolean]

331

}

332

```

333

334

**Usage Examples:**

335

336

```scala

337

object DebugFlag extends FlagQueryParamMatcher("debug")

338

object VerboseFlag extends FlagQueryParamMatcher("verbose")

339

340

val routes = HttpRoutes.of[IO] {

341

case GET -> Root / "status" :? DebugFlag(debug) +& VerboseFlag(verbose) =>

342

val message = (debug, verbose) match {

343

case (true, true) => "Debug and verbose mode enabled"

344

case (true, false) => "Debug mode enabled"

345

case (false, true) => "Verbose mode enabled"

346

case (false, false) => "Normal mode"

347

}

348

Ok(message)

349

// ?debug -> debug=true, verbose=false

350

// ?debug&verbose -> debug=true, verbose=true

351

// (no flags) -> debug=false, verbose=false

352

}

353

```

354

355

### Custom Query Parameter Decoders

356

357

You can create custom decoders for complex parameter types:

358

359

```scala

360

// Custom case class

361

case class SortOrder(field: String, direction: String)

362

363

// Custom decoder

364

implicit val sortOrderDecoder: QueryParamDecoder[SortOrder] =

365

QueryParamDecoder[String].emap { str =>

366

str.split(":") match {

367

case Array(field, direction) if Set("asc", "desc").contains(direction) =>

368

Right(SortOrder(field, direction))

369

case _ =>

370

Left(ParseFailure("Invalid sort format, expected 'field:direction'", ""))

371

}

372

}

373

374

object SortOrderParam extends QueryParamDecoderMatcher[SortOrder]("sort")

375

376

val routes = HttpRoutes.of[IO] {

377

// ?sort=name:asc or ?sort=date:desc

378

case GET -> Root / "users" :? SortOrderParam(sort) =>

379

Ok(s"Sort by ${sort.field} ${sort.direction}")

380

}

381

```