@zio.dev/zio-blocks 0.0.22 → 0.0.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/guides/query-dsl-extending.md +758 -0
- package/guides/query-dsl-fluent-builder.md +1287 -0
- package/guides/query-dsl-reified-optics.md +494 -0
- package/guides/query-dsl-sql.md +680 -0
- package/index.md +42 -39
- package/package.json +1 -1
- package/reference/codec.md +20 -18
- package/reference/docs.md +1 -1
- package/reference/dynamic-optic.md +4 -0
- package/reference/json-schema.md +1 -1
- package/reference/json.md +2 -2
- package/reference/media-type.md +460 -0
- package/reference/optics.md +4 -0
- package/reference/schema-expr.md +669 -0
- package/reference/schema.md +1 -0
- package/reference/type-class-derivation.md +50 -12
- package/scope.md +744 -490
- package/sidebars.js +12 -0
- package/undocumented-report.md +331 -0
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: media-type
|
|
3
|
+
title: "MediaType"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
`MediaType` is a **type-safe representation of IANA media types** (also known as MIME types). It captures structured metadata about content types including compressibility, binary/text classification, and associated file extensions.
|
|
7
|
+
|
|
8
|
+
ZIO Blocks MediaType is designed to be a comprehensive and efficient implementation for handling media types in Scala applications, especially those involving HTTP content negotiation, file handling, and data serialization.
|
|
9
|
+
|
|
10
|
+
`MediaType`:
|
|
11
|
+
|
|
12
|
+
- is a zero-dependency data type in the `zio-blocks-mediatype` module
|
|
13
|
+
- ships with 2,600+ predefined IANA media types auto-generated from the [mime-db](https://github.com/jshttp/mime-db) database
|
|
14
|
+
- provides a `mediaType"..."` string interpolator with compile-time validation
|
|
15
|
+
- supports wildcard and case-insensitive matching
|
|
16
|
+
- is cross-platform (JVM and Scala.js) and cross-version (Scala 2.13 and 3.x)
|
|
17
|
+
|
|
18
|
+
```scala
|
|
19
|
+
final case class MediaType(
|
|
20
|
+
mainType: String,
|
|
21
|
+
subType: String,
|
|
22
|
+
compressible: Boolean = false,
|
|
23
|
+
binary: Boolean = false,
|
|
24
|
+
fileExtensions: List[String] = Nil,
|
|
25
|
+
extensions: Map[String, String] = Map.empty,
|
|
26
|
+
parameters: Map[String, String] = Map.empty
|
|
27
|
+
)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Motivation
|
|
31
|
+
|
|
32
|
+
Media types are fundamental to content negotiation in HTTP, file type detection, and serialization format selection. Working with raw strings like `"application/json"` is error-prone — typos go undetected, metadata (is it compressible? binary? what file extensions?) must be tracked separately, and matching logic must account for wildcards and case insensitivity.
|
|
33
|
+
|
|
34
|
+
`MediaType` solves these problems by providing:
|
|
35
|
+
|
|
36
|
+
- **Structured representation** — main type, subtype, and parameters as distinct fields
|
|
37
|
+
- **Rich metadata** — compressibility, binary/text classification, and file extensions baked in
|
|
38
|
+
- **Compile-time safety** — the `mediaType"..."` interpolator catches malformed types at compile time
|
|
39
|
+
- **Correct matching** — wildcard and case-insensitive matching with parameter awareness
|
|
40
|
+
|
|
41
|
+
```text
|
|
42
|
+
MediaType
|
|
43
|
+
┌──────────┼──────────┐
|
|
44
|
+
mainType subType parameters
|
|
45
|
+
│ │ │
|
|
46
|
+
"application" "json" Map("charset" -> "utf-8")
|
|
47
|
+
│
|
|
48
|
+
┌─────┴──────────────────────────────────┐
|
|
49
|
+
│ compressible = true │
|
|
50
|
+
│ binary = false │
|
|
51
|
+
│ fileExtensions = List("json", "map") │
|
|
52
|
+
└────────────────────────────────────────┘
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
A quick example:
|
|
56
|
+
|
|
57
|
+
```scala
|
|
58
|
+
import zio.blocks.mediatype._
|
|
59
|
+
|
|
60
|
+
// Compile-time validated media type
|
|
61
|
+
val json = mediaType"application/json"
|
|
62
|
+
|
|
63
|
+
// Look up by file extension
|
|
64
|
+
val detected = MediaType.forFileExtension("png")
|
|
65
|
+
// Some(MediaType("image", "png", binary = true, ...))
|
|
66
|
+
|
|
67
|
+
// Wildcard matching
|
|
68
|
+
val textAny = mediaType"text/*"
|
|
69
|
+
val html = mediaType"text/html"
|
|
70
|
+
textAny.matches(html) // true
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Installation
|
|
74
|
+
|
|
75
|
+
Add the following to your `build.sbt`:
|
|
76
|
+
|
|
77
|
+
```scala
|
|
78
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-mediatype" % "0.0.25"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
For cross-platform projects (Scala.js):
|
|
82
|
+
|
|
83
|
+
```scala
|
|
84
|
+
libraryDependencies += "dev.zio" %%% "zio-blocks-mediatype" % "0.0.25"
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Supported Scala versions: 2.13.x and 3.x.
|
|
88
|
+
|
|
89
|
+
## Creating Instances
|
|
90
|
+
|
|
91
|
+
### Direct Construction
|
|
92
|
+
|
|
93
|
+
We can create a `MediaType` by specifying the main type and subtype directly. All other fields have sensible defaults:
|
|
94
|
+
|
|
95
|
+
```scala
|
|
96
|
+
import zio.blocks.mediatype.MediaType
|
|
97
|
+
|
|
98
|
+
// Minimal — just mainType and subType
|
|
99
|
+
val plain = MediaType("text", "plain")
|
|
100
|
+
// compressible = false, binary = false, fileExtensions = Nil, ...
|
|
101
|
+
|
|
102
|
+
// With all fields
|
|
103
|
+
val json = MediaType(
|
|
104
|
+
mainType = "application",
|
|
105
|
+
subType = "json",
|
|
106
|
+
compressible = true,
|
|
107
|
+
binary = false,
|
|
108
|
+
fileExtensions = List("json", "map"),
|
|
109
|
+
extensions = Map("source" -> "iana"),
|
|
110
|
+
parameters = Map("charset" -> "utf-8")
|
|
111
|
+
)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Parsing from a String
|
|
115
|
+
|
|
116
|
+
`MediaType.parse` parses a standard media type string in the format `mainType/subType[; key=value]*`:
|
|
117
|
+
|
|
118
|
+
```scala
|
|
119
|
+
import zio.blocks.mediatype.{MediaType, MediaTypes}
|
|
120
|
+
|
|
121
|
+
// Simple type
|
|
122
|
+
val json: Either[String, MediaType] = MediaType.parse("application/json")
|
|
123
|
+
// Right(MediaType("application", "json", compressible = true, ...))
|
|
124
|
+
|
|
125
|
+
// With parameters
|
|
126
|
+
val html = MediaType.parse("text/html; charset=utf-8")
|
|
127
|
+
// Right(MediaType("text", "html", ..., parameters = Map("charset" -> "utf-8")))
|
|
128
|
+
|
|
129
|
+
// Predefined instances are reused — parse returns the same object
|
|
130
|
+
val parsed = MediaType.parse("application/json").toOption.get
|
|
131
|
+
parsed eq MediaTypes.application.`json` // true (reference equality)
|
|
132
|
+
|
|
133
|
+
// Invalid input returns Left with an error message
|
|
134
|
+
MediaType.parse("") // Left("Invalid media type: cannot be empty")
|
|
135
|
+
MediaType.parse("applicationjson") // Left("Invalid media type: must contain '/' separator")
|
|
136
|
+
MediaType.parse("/json") // Left("Invalid media type: main type cannot be empty")
|
|
137
|
+
MediaType.parse("application/") // Left("Invalid media type: subtype cannot be empty")
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Unsafe Parsing
|
|
141
|
+
|
|
142
|
+
When we are certain the input is valid, `unsafeFromString` returns a `MediaType` directly or throws an `IllegalArgumentException`:
|
|
143
|
+
|
|
144
|
+
```scala
|
|
145
|
+
import zio.blocks.mediatype.MediaType
|
|
146
|
+
|
|
147
|
+
val json = MediaType.unsafeFromString("application/json")
|
|
148
|
+
// MediaType("application", "json", compressible = true, ...)
|
|
149
|
+
|
|
150
|
+
// Throws IllegalArgumentException for invalid input:
|
|
151
|
+
// MediaType.unsafeFromString("invalid")
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### String Interpolator
|
|
155
|
+
|
|
156
|
+
The `mediaType"..."` interpolator validates the media type at compile time. Invalid types produce compile errors, not runtime failures:
|
|
157
|
+
|
|
158
|
+
```scala
|
|
159
|
+
import zio.blocks.mediatype._
|
|
160
|
+
|
|
161
|
+
val json = mediaType"application/json"
|
|
162
|
+
val htmlUtf8 = mediaType"text/html; charset=utf-8"
|
|
163
|
+
val wildcard = mediaType"*/*"
|
|
164
|
+
val vendor = mediaType"text/vnd.api+json"
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
The interpolator reuses predefined instances when available (reference equality with `MediaTypes` constants). It does not support variable interpolation — only literal strings are accepted.
|
|
168
|
+
|
|
169
|
+
:::note
|
|
170
|
+
The `mediaType` interpolator is available after importing `zio.blocks.mediatype._`. Invalid inputs produce clear compile-time errors:
|
|
171
|
+
|
|
172
|
+
```text
|
|
173
|
+
mediaType"" → "Invalid media type: cannot be empty"
|
|
174
|
+
mediaType"applicationjson" → "Invalid media type: must contain '/' separator"
|
|
175
|
+
mediaType"/json" → "Invalid media type: main type cannot be empty"
|
|
176
|
+
mediaType"application/" → "Invalid media type: subtype cannot be empty"
|
|
177
|
+
```
|
|
178
|
+
:::
|
|
179
|
+
|
|
180
|
+
### File Extension Lookup
|
|
181
|
+
|
|
182
|
+
`MediaType.forFileExtension` finds a `MediaType` by its associated file extension. The lookup is case-insensitive and strips a leading `.` if present:
|
|
183
|
+
|
|
184
|
+
```scala
|
|
185
|
+
import zio.blocks.mediatype.MediaType
|
|
186
|
+
|
|
187
|
+
MediaType.forFileExtension("json") // Some(MediaType("application", "json", ...))
|
|
188
|
+
MediaType.forFileExtension(".html") // Some(MediaType("text", "html", ...))
|
|
189
|
+
MediaType.forFileExtension("PNG") // Some(MediaType("image", "png", ...))
|
|
190
|
+
MediaType.forFileExtension("jpg") // Some(MediaType("image", "jpeg", ...))
|
|
191
|
+
MediaType.forFileExtension("pdf") // Some(MediaType("application", "pdf", ...))
|
|
192
|
+
|
|
193
|
+
MediaType.forFileExtension("xyz123") // None (unknown extension)
|
|
194
|
+
MediaType.forFileExtension("") // None
|
|
195
|
+
MediaType.forFileExtension(".") // None
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## Predefined Media Types
|
|
199
|
+
|
|
200
|
+
The `MediaTypes` object contains 2,600+ predefined media type constants auto-generated from the [jshttp/mime-db](https://github.com/jshttp/mime-db) database. Types are organized by main type category:
|
|
201
|
+
|
|
202
|
+
| Category | Object | Examples |
|
|
203
|
+
|--------------|---------------------------|--------------------------------------|
|
|
204
|
+
| Application | `MediaTypes.application` | `json`, `pdf`, `xml`, `octet-stream` |
|
|
205
|
+
| Audio | `MediaTypes.audio` | `mpeg`, `ogg`, `wav` |
|
|
206
|
+
| Chemical | `MediaTypes.chemical` | `x-cif`, `x-pdb` |
|
|
207
|
+
| Font | `MediaTypes.font` | `woff`, `woff2`, `otf` |
|
|
208
|
+
| Image | `MediaTypes.image` | `png`, `jpeg`, `gif`, `svg+xml` |
|
|
209
|
+
| Message | `MediaTypes.message` | `rfc822`, `partial` |
|
|
210
|
+
| Model | `MediaTypes.model` | `gltf+json`, `stl` |
|
|
211
|
+
| Multipart | `MediaTypes.multipart` | `form-data`, `mixed` |
|
|
212
|
+
| Text | `MediaTypes.text` | `html`, `css`, `plain`, `csv` |
|
|
213
|
+
| Video | `MediaTypes.video` | `mp4`, `webm`, `ogg` |
|
|
214
|
+
| X-Conference | `MediaTypes.x_conference` | `x-cooltalk` |
|
|
215
|
+
| X-Shader | `MediaTypes.x_shader` | `x-vertex`, `x-fragment` |
|
|
216
|
+
| Wildcard | `MediaTypes.any` | `*/*` |
|
|
217
|
+
|
|
218
|
+
### Accessing Predefined Types
|
|
219
|
+
|
|
220
|
+
Since many subtype names contain special characters (hyphens, dots, plus signs), predefined constants use backtick identifiers:
|
|
221
|
+
|
|
222
|
+
```scala
|
|
223
|
+
import zio.blocks.mediatype.MediaTypes
|
|
224
|
+
|
|
225
|
+
// Common application types
|
|
226
|
+
val json = MediaTypes.application.`json`
|
|
227
|
+
val pdf = MediaTypes.application.`pdf`
|
|
228
|
+
val xmlType = MediaTypes.application.`xml`
|
|
229
|
+
|
|
230
|
+
// Common text types
|
|
231
|
+
val html = MediaTypes.text.`html`
|
|
232
|
+
val css = MediaTypes.text.`css`
|
|
233
|
+
val plain = MediaTypes.text.`plain`
|
|
234
|
+
|
|
235
|
+
// Common image types
|
|
236
|
+
val png = MediaTypes.image.`png`
|
|
237
|
+
val jpeg = MediaTypes.image.`jpeg`
|
|
238
|
+
|
|
239
|
+
// Wildcard (matches any type)
|
|
240
|
+
val any = MediaTypes.any
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Listing All Types
|
|
244
|
+
|
|
245
|
+
Each category object has an `all` field returning a `List[MediaType]` of all types in that category. The top-level `allMediaTypes` aggregates every category:
|
|
246
|
+
|
|
247
|
+
```scala
|
|
248
|
+
import zio.blocks.mediatype.MediaTypes
|
|
249
|
+
|
|
250
|
+
// All types in a category
|
|
251
|
+
val appTypes: List[zio.blocks.mediatype.MediaType] = MediaTypes.application.all
|
|
252
|
+
|
|
253
|
+
// All 2,600+ predefined types
|
|
254
|
+
val everything: List[zio.blocks.mediatype.MediaType] = MediaTypes.allMediaTypes
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### Predefined Metadata
|
|
258
|
+
|
|
259
|
+
Each predefined instance comes with rich metadata from the IANA registry:
|
|
260
|
+
|
|
261
|
+
```scala
|
|
262
|
+
import zio.blocks.mediatype.MediaTypes
|
|
263
|
+
|
|
264
|
+
val json = MediaTypes.application.`json`
|
|
265
|
+
json.compressible // true
|
|
266
|
+
json.binary // false
|
|
267
|
+
json.fileExtensions // List("json", "map")
|
|
268
|
+
|
|
269
|
+
val jpeg = MediaTypes.image.`jpeg`
|
|
270
|
+
jpeg.compressible // false
|
|
271
|
+
jpeg.binary // true
|
|
272
|
+
jpeg.fileExtensions // List("jpg", "jpeg", "jpe")
|
|
273
|
+
|
|
274
|
+
val html = MediaTypes.text.`html`
|
|
275
|
+
html.compressible // true
|
|
276
|
+
html.binary // false
|
|
277
|
+
html.fileExtensions // List("html", "htm", "shtml")
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
## Core Operations
|
|
281
|
+
|
|
282
|
+
### `fullType`
|
|
283
|
+
|
|
284
|
+
Returns the complete media type string by combining `mainType` and `subType` with a `/` separator:
|
|
285
|
+
|
|
286
|
+
```scala
|
|
287
|
+
import zio.blocks.mediatype.MediaType
|
|
288
|
+
|
|
289
|
+
val mt = MediaType("application", "json")
|
|
290
|
+
mt.fullType // "application/json"
|
|
291
|
+
|
|
292
|
+
MediaType("*", "*").fullType // "*/*"
|
|
293
|
+
MediaType("text", "*").fullType // "text/*"
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### `matches`
|
|
297
|
+
|
|
298
|
+
Compares two `MediaType` values with support for wildcards, case insensitivity, and parameter matching:
|
|
299
|
+
|
|
300
|
+
```scala
|
|
301
|
+
final case class MediaType(...) {
|
|
302
|
+
def matches(other: MediaType, ignoreParameters: Boolean = false): Boolean
|
|
303
|
+
}
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
The matching rules are:
|
|
307
|
+
|
|
308
|
+
1. **Wildcard matching** — `"*"` in either `mainType` or `subType` matches any value
|
|
309
|
+
2. **Case-insensitive** — `"APPLICATION/JSON"` matches `"application/json"`
|
|
310
|
+
3. **Parameter subset** — when `ignoreParameters = false` (the default), all parameters in `this` must exist in `other` with matching values (case-insensitive). Extra parameters in `other` are allowed.
|
|
311
|
+
|
|
312
|
+
```scala
|
|
313
|
+
import zio.blocks.mediatype._
|
|
314
|
+
|
|
315
|
+
val json = mediaType"application/json"
|
|
316
|
+
val textAll = mediaType"text/*"
|
|
317
|
+
val html = mediaType"text/html"
|
|
318
|
+
val any = mediaType"*/*"
|
|
319
|
+
|
|
320
|
+
// Exact match
|
|
321
|
+
json.matches(json) // true
|
|
322
|
+
|
|
323
|
+
// Wildcard matching
|
|
324
|
+
any.matches(json) // true — */* matches anything
|
|
325
|
+
textAll.matches(html) // true — text/* matches text/html
|
|
326
|
+
textAll.matches(json) // false — text/* does not match application/json
|
|
327
|
+
|
|
328
|
+
// Case-insensitive
|
|
329
|
+
MediaType("APPLICATION", "JSON").matches(json) // true
|
|
330
|
+
|
|
331
|
+
// Parameter matching
|
|
332
|
+
val htmlUtf8 = MediaType("text", "html", parameters = Map("charset" -> "utf-8"))
|
|
333
|
+
val htmlLatin = MediaType("text", "html", parameters = Map("charset" -> "iso-8859-1"))
|
|
334
|
+
val htmlFull = MediaType("text", "html", parameters = Map("charset" -> "utf-8", "boundary" -> "xxx"))
|
|
335
|
+
|
|
336
|
+
htmlUtf8.matches(htmlLatin) // false — charset mismatch
|
|
337
|
+
htmlUtf8.matches(htmlLatin, ignoreParameters = true) // true — parameters ignored
|
|
338
|
+
htmlUtf8.matches(htmlFull) // true — subset match (charset matches)
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### `forFileExtension`
|
|
342
|
+
|
|
343
|
+
Looks up a `MediaType` by file extension. The lookup is case-insensitive and strips a leading `.` if present:
|
|
344
|
+
|
|
345
|
+
```scala
|
|
346
|
+
object MediaType {
|
|
347
|
+
def forFileExtension(ext: String): Option[MediaType]
|
|
348
|
+
}
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
```scala
|
|
352
|
+
import zio.blocks.mediatype.MediaType
|
|
353
|
+
|
|
354
|
+
MediaType.forFileExtension("json") // Some(MediaType("application", "json", ...))
|
|
355
|
+
MediaType.forFileExtension(".html") // Some(MediaType("text", "html", ...))
|
|
356
|
+
MediaType.forFileExtension("PNG") // Some(MediaType("image", "png", ...))
|
|
357
|
+
MediaType.forFileExtension("") // None
|
|
358
|
+
MediaType.forFileExtension("xyz") // None
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
:::tip
|
|
362
|
+
`forFileExtension` gives priority to `text/*` types when an extension maps to multiple types. For example, `"js"` maps to `text/javascript` rather than `application/javascript`.
|
|
363
|
+
:::
|
|
364
|
+
|
|
365
|
+
### `parse`
|
|
366
|
+
|
|
367
|
+
Parses a media type string into a `MediaType`, returning `Left` with an error message for invalid input:
|
|
368
|
+
|
|
369
|
+
```scala
|
|
370
|
+
object MediaType {
|
|
371
|
+
def parse(s: String): Either[String, MediaType]
|
|
372
|
+
}
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
When the parsed type matches a predefined instance, that instance is returned (preserving reference equality and all metadata). Parameters from the input string are merged into the result:
|
|
376
|
+
|
|
377
|
+
```scala
|
|
378
|
+
import zio.blocks.mediatype.{MediaType, MediaTypes}
|
|
379
|
+
|
|
380
|
+
// Returns predefined instance with full metadata
|
|
381
|
+
val json = MediaType.parse("application/json")
|
|
382
|
+
// Right(MediaType("application", "json", compressible=true, binary=false, ...))
|
|
383
|
+
|
|
384
|
+
// Parameters are parsed and attached
|
|
385
|
+
val result = MediaType.parse("multipart/form-data; boundary=abc; charset=utf-8")
|
|
386
|
+
// Right(MediaType(..., parameters = Map("boundary" -> "abc", "charset" -> "utf-8")))
|
|
387
|
+
|
|
388
|
+
// Unknown types get a fresh instance
|
|
389
|
+
val custom = MediaType.parse("custom/x-my-format")
|
|
390
|
+
// Right(MediaType("custom", "x-my-format"))
|
|
391
|
+
|
|
392
|
+
// Malformed parameters (no "=") are silently ignored
|
|
393
|
+
val partial = MediaType.parse("text/html; charset=utf-8; malformed")
|
|
394
|
+
// Right(MediaType(..., parameters = Map("charset" -> "utf-8")))
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
### `unsafeFromString`
|
|
398
|
+
|
|
399
|
+
Like `parse`, but throws `IllegalArgumentException` instead of returning `Left`:
|
|
400
|
+
|
|
401
|
+
```scala
|
|
402
|
+
object MediaType {
|
|
403
|
+
def unsafeFromString(s: String): MediaType
|
|
404
|
+
}
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
For valid input, it returns the corresponding `MediaType` (reusing predefined instances when possible). For invalid input, it throws an exception with a descriptive message:
|
|
408
|
+
|
|
409
|
+
```scala
|
|
410
|
+
import zio.blocks.mediatype.MediaType
|
|
411
|
+
|
|
412
|
+
val json = MediaType.unsafeFromString("application/json")
|
|
413
|
+
|
|
414
|
+
// Throws IllegalArgumentException:
|
|
415
|
+
// MediaType.unsafeFromString("not-a-media-type")
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
## Advanced Usage
|
|
419
|
+
|
|
420
|
+
### Content Negotiation
|
|
421
|
+
|
|
422
|
+
We can use `matches` with wildcard types to implement HTTP-style content negotiation:
|
|
423
|
+
|
|
424
|
+
```scala
|
|
425
|
+
import zio.blocks.mediatype._
|
|
426
|
+
|
|
427
|
+
def negotiate(
|
|
428
|
+
accept: List[MediaType],
|
|
429
|
+
available: List[MediaType]
|
|
430
|
+
): Option[MediaType] =
|
|
431
|
+
available.find(avail => accept.exists(_.matches(avail, ignoreParameters = true)))
|
|
432
|
+
|
|
433
|
+
val accept = List(mediaType"text/*", mediaType"application/json")
|
|
434
|
+
val available = List(mediaType"application/xml", mediaType"application/json", mediaType"text/html")
|
|
435
|
+
|
|
436
|
+
negotiate(accept, available)
|
|
437
|
+
// Some(MediaType("application", "json", ...))
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
### File Type Detection
|
|
441
|
+
|
|
442
|
+
Combine `forFileExtension` with file path processing to detect content types:
|
|
443
|
+
|
|
444
|
+
```scala
|
|
445
|
+
import zio.blocks.mediatype.MediaType
|
|
446
|
+
|
|
447
|
+
def detectContentType(filename: String): Option[MediaType] = {
|
|
448
|
+
val ext = filename.lastIndexOf('.') match {
|
|
449
|
+
case -1 => ""
|
|
450
|
+
case i => filename.substring(i + 1)
|
|
451
|
+
}
|
|
452
|
+
MediaType.forFileExtension(ext)
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
detectContentType("report.pdf") // Some(MediaType("application", "pdf", ...))
|
|
456
|
+
detectContentType("photo.jpg") // Some(MediaType("image", "jpeg", ...))
|
|
457
|
+
detectContentType("data.json") // Some(MediaType("application", "json", ...))
|
|
458
|
+
detectContentType("Makefile") // None
|
|
459
|
+
```
|
|
460
|
+
|
package/reference/optics.md
CHANGED
|
@@ -5,6 +5,10 @@ title: "Optics"
|
|
|
5
5
|
|
|
6
6
|
Optics are a fundamental feature of ZIO Blocks that enable type-safe, composable access and modification of nested data structures. What sets ZIO Blocks apart is its implementation of **reflective optics** — a novel construct that combines the operational capabilities of traditional optics with embedded structural metadata, enabling both data manipulation AND introspection.
|
|
7
7
|
|
|
8
|
+
:::tip
|
|
9
|
+
For a practical walkthrough of building query DSLs with optics, see the [Writing a Query DSL with Reified Optics](../guides/query-dsl-reified-optics.md) guide.
|
|
10
|
+
:::
|
|
11
|
+
|
|
8
12
|
## What Are Optics?
|
|
9
13
|
|
|
10
14
|
Optics are abstractions that allow you to focus on a specific part of a data structure. They provide a way to **view**, **update**, and **traverse** nested fields in immutable data types without boilerplate code:
|