forked from akka/akka-http
/
MediaRange.scala
161 lines (145 loc) · 6.44 KB
/
MediaRange.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
/**
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
*/
package akka.http.scaladsl.model
import language.implicitConversions
import java.util
import akka.http.impl.util._
import akka.http.javadsl.{ model ⇒ jm }
import akka.http.scaladsl.model.MediaType.WithFixedCharset
sealed abstract class MediaRange extends jm.MediaRange with Renderable with WithQValue[MediaRange] {
def value: String
def mainType: String
def params: Map[String, String]
def qValue: Float
def matches(mediaType: MediaType): Boolean
def isApplication = false
def isAudio = false
def isImage = false
def isMessage = false
def isMultipart = false
def isText = false
def isVideo = false
def isWildcard = mainType == "*"
/**
* Returns a copy of this instance with the params replaced by the given ones.
* If the given map contains a "q" value the `qValue` member is (also) updated.
*/
def withParams(params: Map[String, String]): MediaRange
/**
* Constructs a `ContentTypeRange` from this instance and the given charset.
*/
def withCharsetRange(charsetRange: HttpCharsetRange): ContentTypeRange = ContentTypeRange(this, charsetRange)
/** Java API */
def getParams: util.Map[String, String] = {
import collection.JavaConverters._
params.asJava
}
/** Java API */
def matches(mediaType: jm.MediaType): Boolean = {
import akka.http.impl.util.JavaMapping.Implicits._
matches(mediaType.asScala)
}
}
object MediaRange {
private[http] def extractCharset(mediaType: MediaType): Option[HttpCharset] = mediaType match {
case mt: WithFixedCharset ⇒ Some(mt.charset)
case _: MediaType ⇒ None
}
private[http] def splitOffQValue(params: Map[String, String], defaultQ: Float = 1.0f): (Map[String, String], Float) =
params.get("q") match {
case Some(x) ⇒ (params - "q") → (try x.toFloat catch { case _: NumberFormatException ⇒ 1.0f })
case None ⇒ params → defaultQ
}
private final case class Custom(mainType: String, params: Map[String, String], qValue: Float)
extends MediaRange with ValueRenderable {
require(0.0f <= qValue && qValue <= 1.0f, "qValue must be >= 0 and <= 1.0")
def matches(mediaType: MediaType) = (mainType == "*" || mediaType.mainType == mainType) &&
this.params.forall { case (key, value) ⇒ mediaType.params.get(key).contains(value) }
def withParams(params: Map[String, String]) = custom(mainType, params, qValue)
def withQValue(qValue: Float) = if (qValue != this.qValue) custom(mainType, params, qValue) else this
def render[R <: Rendering](r: R): r.type = {
r ~~ mainType ~~ '/' ~~ '*'
if (qValue < 1.0f) r ~~ ";q=" ~~ qValue
if (params.nonEmpty) params foreach { case (k, v) ⇒ r ~~ ';' ~~ ' ' ~~ k ~~ '=' ~~# v }
r
}
override def isApplication = mainType == "application"
override def isAudio = mainType == "audio"
override def isImage = mainType == "image"
override def isMessage = mainType == "message"
override def isMultipart = mainType == "multipart"
override def isText = mainType == "text"
override def isVideo = mainType == "video"
}
def custom(mainType: String, params: Map[String, String] = Map.empty, qValue: Float = 1.0f): MediaRange = {
val (ps, q) = splitOffQValue(params, qValue)
Custom(mainType.toRootLowerCase, ps, q)
}
final case class One(mediaType: MediaType, qValue: Float) extends MediaRange with ValueRenderable {
require(0.0f <= qValue && qValue <= 1.0f, "qValue must be >= 0 and <= 1.0")
def mainType = mediaType.mainType
def params = mediaType.params
override def isApplication = mediaType.isApplication
override def isAudio = mediaType.isAudio
override def isImage = mediaType.isImage
override def isMessage = mediaType.isMessage
override def isMultipart = mediaType.isMultipart
override def isText = mediaType.isText
override def isVideo = mediaType.isVideo
def matches(mediaType: MediaType) =
this.mediaType.mainType == mediaType.mainType &&
this.mediaType.subType == mediaType.subType &&
this.mediaType.params
.filterNot { case (key, value) ⇒ key == "charset" }
.forall { case (key, value) ⇒ mediaType.params.get(key).contains(value) } &&
extractCharset(this.mediaType) == extractCharset(mediaType)
def withParams(params: Map[String, String]) = copy(mediaType = mediaType.withParams(params))
def withQValue(qValue: Float) = copy(qValue = qValue)
def render[R <: Rendering](r: R): r.type = if (qValue < 1.0f) r ~~ mediaType ~~ ";q=" ~~ qValue else r ~~ mediaType
}
implicit def apply(mediaType: MediaType): MediaRange = apply(mediaType, 1.0f)
def apply(mediaType: MediaType, qValue: Float = 1.0f): MediaRange = One(mediaType, qValue)
}
object MediaRanges extends ObjectRegistry[String, MediaRange] {
sealed abstract case class PredefinedMediaRange(value: String) extends MediaRange with LazyValueBytesRenderable {
val mainType = value takeWhile (_ != '/')
register(mainType, this)
def params = Map.empty
def qValue = 1.0f
def withParams(params: Map[String, String]) = MediaRange.custom(mainType, params)
def withQValue(qValue: Float) = if (qValue != 1.0f) MediaRange.custom(mainType, params, qValue) else this
}
val `*/*` = new PredefinedMediaRange("*/*") {
def matches(mediaType: MediaType) = true
}
val `*/*;q=MIN` = `*/*`.withQValue(Float.MinPositiveValue)
val `application/*` = new PredefinedMediaRange("application/*") {
def matches(mediaType: MediaType) = mediaType.isApplication
override def isApplication = true
}
val `audio/*` = new PredefinedMediaRange("audio/*") {
def matches(mediaType: MediaType) = mediaType.isAudio
override def isAudio = true
}
val `image/*` = new PredefinedMediaRange("image/*") {
def matches(mediaType: MediaType) = mediaType.isImage
override def isImage = true
}
val `message/*` = new PredefinedMediaRange("message/*") {
def matches(mediaType: MediaType) = mediaType.isMessage
override def isMessage = true
}
val `multipart/*` = new PredefinedMediaRange("multipart/*") {
def matches(mediaType: MediaType) = mediaType.isMultipart
override def isMultipart = true
}
val `text/*` = new PredefinedMediaRange("text/*") {
def matches(mediaType: MediaType) = mediaType.isText
override def isText = true
}
val `video/*` = new PredefinedMediaRange("video/*") {
def matches(mediaType: MediaType) = mediaType.isVideo
override def isVideo = true
}
}