!htc #19023 improve/refactor Language and LanguageRange, add convenience helpers

This commit is contained in:
Mathias 2015-11-26 15:37:39 +01:00
parent 301d1fd337
commit d036aee09f
5 changed files with 71 additions and 13 deletions

View file

@ -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);
}

View file

@ -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$;

View file

@ -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(_)) }
}

View file

@ -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.
*/

View file

@ -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: _*))
}