!htc #19023 improve/refactor Language and LanguageRange, add convenience helpers
This commit is contained in:
parent
301d1fd337
commit
d036aee09f
5 changed files with 71 additions and 13 deletions
|
|
@ -7,8 +7,12 @@ package akka.http.javadsl.model.headers;
|
|||
import akka.http.impl.util.Util;
|
||||
import akka.http.scaladsl.model.headers.Language$;
|
||||
|
||||
public abstract class Language implements LanguageRange {
|
||||
public abstract class Language {
|
||||
public static Language create(String primaryTag, String... subTags) {
|
||||
return Language$.MODULE$.apply(primaryTag, Util.<String, String>convertArray(subTags));
|
||||
}
|
||||
|
||||
public abstract String primaryTag();
|
||||
public abstract Iterable<String> getSubTags();
|
||||
public abstract LanguageRange withQValue(float qValue);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,10 +7,8 @@ package akka.http.javadsl.model.headers;
|
|||
public interface LanguageRange {
|
||||
public abstract String primaryTag();
|
||||
public abstract float qValue();
|
||||
|
||||
public abstract Iterable<String> getSubTags();
|
||||
public abstract boolean matches(Language language);
|
||||
|
||||
public abstract Iterable<String> getSubTags();
|
||||
public abstract LanguageRange withQValue(float qValue);
|
||||
|
||||
public static final LanguageRange ALL = akka.http.scaladsl.model.headers.LanguageRange.$times$.MODULE$;
|
||||
|
|
|
|||
|
|
@ -23,5 +23,5 @@ private[parser] trait AcceptLanguageHeader { this: Parser with CommonRules with
|
|||
}
|
||||
}
|
||||
|
||||
def `language-range` = rule { ws('*') ~ push(LanguageRange.`*`) | language }
|
||||
def `language-range` = rule { ws('*') ~ push(LanguageRange.`*`) | language ~> (LanguageRange(_)) }
|
||||
}
|
||||
|
|
@ -193,6 +193,19 @@ final case class HttpRequest(method: HttpMethod = HttpMethods.GET,
|
|||
range ← encodingRanges
|
||||
} yield range).sortBy(-_.qValue)
|
||||
|
||||
/**
|
||||
* The language-ranges accepted by the client according to the `Accept-Language` request header.
|
||||
* The returned ranges are sorted by increasing generality (i.e. most specific first).
|
||||
*/
|
||||
def acceptedLanguageRanges: immutable.Seq[LanguageRange] =
|
||||
(for {
|
||||
`Accept-Language`(languageRanges) ← headers
|
||||
range ← languageRanges
|
||||
} yield range).sortBy {
|
||||
case _: LanguageRange.`*` ⇒ 0 // most general, needs to come last
|
||||
case x ⇒ -(x.subTags.size + 1) // more subtags -> more specific -> go first
|
||||
}
|
||||
|
||||
/**
|
||||
* All cookies provided by the client in one or more `Cookie` headers.
|
||||
*/
|
||||
|
|
@ -243,6 +256,22 @@ final case class HttpRequest(method: HttpMethod = HttpMethods.GET,
|
|||
case x ⇒ x collectFirst { case r if r matches encoding ⇒ r.qValue } getOrElse 0f
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the given language is accepted by the client.
|
||||
*/
|
||||
def isLanguageAccepted(language: Language, ranges: Seq[LanguageRange] = acceptedLanguageRanges): Boolean =
|
||||
qValueForLanguage(language, ranges) > 0f
|
||||
|
||||
/**
|
||||
* Returns the q-value that the client (implicitly or explicitly) attaches to the given language.
|
||||
* Note: The given ranges must be sorted by increasing generality (i.e. most specific first)!
|
||||
*/
|
||||
def qValueForLanguage(language: Language, ranges: Seq[LanguageRange] = acceptedLanguageRanges): Float =
|
||||
ranges match {
|
||||
case Nil ⇒ 1.0f // http://tools.ietf.org/html/rfc7231#section-5.3.1
|
||||
case x ⇒ x collectFirst { case r if r matches language ⇒ r.qValue } getOrElse 0f
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether this request can be safely retried, which is the case only of the request method is idempotent.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -4,11 +4,13 @@
|
|||
|
||||
package akka.http.scaladsl.model.headers
|
||||
|
||||
import scala.language.implicitConversions
|
||||
import scala.collection.immutable
|
||||
import akka.http.impl.util._
|
||||
import akka.http.scaladsl.model.WithQValue
|
||||
import akka.http.javadsl.{ model ⇒ jm }
|
||||
import akka.http.impl.util.JavaMapping.Implicits._
|
||||
import akka.japi
|
||||
|
||||
sealed trait LanguageRange extends jm.headers.LanguageRange with ValueRenderable with WithQValue[LanguageRange] {
|
||||
def qValue: Float
|
||||
|
|
@ -23,7 +25,7 @@ sealed trait LanguageRange extends jm.headers.LanguageRange with ValueRenderable
|
|||
}
|
||||
|
||||
/** Java API */
|
||||
def matches(language: jm.headers.Language): Boolean = matches(language.asScala)
|
||||
def matches(language: jm.headers.Language) = matches(language.asScala)
|
||||
def getSubTags: java.lang.Iterable[String] = subTags.asJava
|
||||
}
|
||||
object LanguageRange {
|
||||
|
|
@ -31,19 +33,44 @@ object LanguageRange {
|
|||
require(0.0f <= qValue && qValue <= 1.0f, "qValue must be >= 0 and <= 1.0")
|
||||
def primaryTag = "*"
|
||||
def subTags = Nil
|
||||
def matches(lang: Language): Boolean = true
|
||||
def matches(lang: Language) = true
|
||||
def withQValue(qValue: Float) =
|
||||
if (qValue == 1.0f) `*` else if (qValue != this.qValue) `*`(qValue.toFloat) else this
|
||||
}
|
||||
object `*` extends `*`(1.0f)
|
||||
|
||||
final case class One(language: Language, qValue: Float) extends LanguageRange {
|
||||
require(0.0f <= qValue && qValue <= 1.0f, "qValue must be >= 0 and <= 1.0")
|
||||
def matches(l: Language) =
|
||||
(language.primaryTag equalsIgnoreCase l.primaryTag) &&
|
||||
language.subTags.size <= l.subTags.size &&
|
||||
(language.subTags zip l.subTags).forall(t ⇒ t._1 equalsIgnoreCase t._2)
|
||||
def primaryTag = language.primaryTag
|
||||
def subTags = language.subTags
|
||||
def withQValue(qValue: Float) = One(language, qValue)
|
||||
}
|
||||
|
||||
implicit def apply(language: Language): LanguageRange = apply(language, 1.0f)
|
||||
def apply(language: Language, qValue: Float): LanguageRange = One(language, qValue)
|
||||
}
|
||||
|
||||
final case class Language(primaryTag: String, subTags: immutable.Seq[String], qValue: Float = 1.0f) extends jm.headers.Language with LanguageRange {
|
||||
require(0.0f <= qValue && qValue <= 1.0f, "qValue must be >= 0 and <= 1.0")
|
||||
def matches(lang: Language): Boolean = lang.primaryTag == this.primaryTag && lang.subTags == this.subTags
|
||||
def withQValue(qValue: Float): Language = Language(primaryTag, subTags, qValue)
|
||||
override def withQValue(qValue: Double): Language = withQValue(qValue.toFloat)
|
||||
final case class Language(primaryTag: String, subTags: immutable.Seq[String])
|
||||
extends jm.headers.Language with ValueRenderable with WithQValue[LanguageRange] {
|
||||
def withQValue(qValue: Float) = LanguageRange(this, qValue.toFloat)
|
||||
def render[R <: Rendering](r: R): r.type = {
|
||||
r ~~ primaryTag
|
||||
if (subTags.nonEmpty) subTags.foreach(r ~~ '-' ~~ _)
|
||||
r
|
||||
}
|
||||
|
||||
/** Java API */
|
||||
def getSubTags: java.lang.Iterable[String] = subTags.asJava
|
||||
}
|
||||
object Language {
|
||||
def apply(primaryTag: String, subTags: String*) = new Language(primaryTag, immutable.Seq(subTags: _*))
|
||||
implicit def apply(compoundTag: String): Language =
|
||||
if (compoundTag.indexOf('-') >= 0) {
|
||||
val tags = compoundTag.split('-')
|
||||
new Language(tags.head, immutable.Seq(tags.tail: _*))
|
||||
} else new Language(compoundTag, immutable.Seq.empty)
|
||||
def apply(primaryTag: String, subTags: String*): Language = new Language(primaryTag, immutable.Seq(subTags: _*))
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue