///|
pub fn RequestPreferences::RequestPreferences(
accept? : StringView = "",
accept_language? : StringView = "",
accept_encoding? : StringView = "",
accept_charset? : StringView = "",
) -> RequestPreferences {
{
accept: accept.to_owned(),
accept_language: accept_language.to_owned(),
accept_encoding: accept_encoding.to_owned(),
accept_charset: accept_charset.to_owned(),
}
}
///|
pub fn ServerVariant::ServerVariant(
id : StringView,
media : StringView,
language? : StringView,
encoding? : StringView,
charset? : StringView,
server_weight? : Int = 1000,
index? : Int = 0,
) -> ServerVariant {
{
id: id.to_owned(),
media: media.to_owned(),
language: language.map(v => v.to_owned()),
encoding: encoding.map(v => v.to_owned()),
charset: charset.map(v => v.to_owned()),
server_weight: clamp_quality(server_weight),
index,
}
}
///|
fn clamp_quality(q : Int) -> Int {
if q < 0 {
0
} else if q > 1000 {
1000
} else {
q
}
}
///|
fn combine_quality(a : Int, b : Int) -> Int {
clamp_quality(a) * clamp_quality(b) / 1000
}
///|
fn combine_all_quality(
media : Int,
language : Int,
encoding : Int,
charset : Int,
server_weight : Int,
) -> Int {
combine_quality(
combine_quality(
combine_quality(combine_quality(media, language), encoding),
charset,
),
server_weight,
)
}
///|
fn single_media_candidate(
prefs : RequestPreferences,
media : String,
) -> Result[MediaCandidate?, ParseError] {
match negotiate_media(prefs.accept[:], [media]) {
Ok(decision) => Ok(decision.candidate)
Err(err) => Err(err)
}
}
///|
fn single_language_candidate(
prefs : RequestPreferences,
language : String?,
) -> Result[(LanguageCandidate?, Int), ParseError] {
match language {
Some(tag) =>
match negotiate_language(prefs.accept_language[:], [tag]) {
Ok(decision) =>
match decision.candidate {
Some(candidate) => Ok((Some(candidate), candidate.quality))
None => Ok((None, 0))
}
Err(err) => Err(err)
}
None => Ok((None, 1000))
}
}
///|
fn single_encoding_candidate(
prefs : RequestPreferences,
encoding : String?,
) -> Result[(EncodingCandidate?, Int), ParseError] {
match encoding {
Some(coding) =>
match negotiate_encoding(prefs.accept_encoding[:], [coding]) {
Ok(decision) =>
match decision.candidate {
Some(candidate) => Ok((Some(candidate), candidate.quality))
None => Ok((None, 0))
}
Err(err) => Err(err)
}
None => Ok((None, 1000))
}
}
///|
fn single_charset_candidate(
prefs : RequestPreferences,
charset : String?,
) -> Result[(CharsetCandidate?, Int), ParseError] {
match charset {
Some(value) =>
match negotiate_charset(prefs.accept_charset[:], [value]) {
Ok(decision) =>
match decision.candidate {
Some(candidate) => Ok((Some(candidate), candidate.quality))
None => Ok((None, 0))
}
Err(err) => Err(err)
}
None => Ok((None, 1000))
}
}
///|
fn variant_reason(
variant : ServerVariant,
media_q : Int,
language_q : Int,
encoding_q : Int,
charset_q : Int,
final_q : Int,
) -> String {
let buf = StringBuilder()
buf.write_string(variant.id)
buf.write_string(" q=")
buf.write_string(quality_to_string(final_q))
buf.write_string(" from media=")
buf.write_string(quality_to_string(media_q))
buf.write_string(", language=")
buf.write_string(quality_to_string(language_q))
buf.write_string(", encoding=")
buf.write_string(quality_to_string(encoding_q))
buf.write_string(", charset=")
buf.write_string(quality_to_string(charset_q))
buf.write_string(", server=")
buf.write_string(quality_to_string(variant.server_weight))
buf.to_string()
}
///|
fn build_variant_candidate(
prefs : RequestPreferences,
variant : ServerVariant,
) -> Result[VariantCandidate?, ParseError] {
let media = match single_media_candidate(prefs, variant.media) {
Ok(Some(candidate)) => candidate
Ok(None) => return Ok(None)
Err(err) => return Err(err)
}
let (language, language_q) = match
single_language_candidate(prefs, variant.language) {
Ok(value) => value
Err(err) => return Err(err)
}
if language_q == 0 {
return Ok(None)
}
let (encoding, encoding_q) = match
single_encoding_candidate(prefs, variant.encoding) {
Ok(value) => value
Err(err) => return Err(err)
}
if encoding_q == 0 {
return Ok(None)
}
let (charset, charset_q) = match
single_charset_candidate(prefs, variant.charset) {
Ok(value) => value
Err(err) => return Err(err)
}
if charset_q == 0 {
return Ok(None)
}
let final_q = combine_all_quality(
media.quality,
language_q,
encoding_q,
charset_q,
variant.server_weight,
)
if final_q == 0 {
return Ok(None)
}
Ok(
Some({
variant,
media: Some(media),
language,
encoding,
charset,
quality: final_q,
reason: variant_reason(
variant,
media.quality,
language_q,
encoding_q,
charset_q,
final_q,
),
}),
)
}
///|
fn better_variant(
candidate : VariantCandidate,
current : VariantCandidate,
) -> Bool {
if candidate.quality != current.quality {
candidate.quality > current.quality
} else if candidate.variant.server_weight != current.variant.server_weight {
candidate.variant.server_weight > current.variant.server_weight
} else {
candidate.variant.index < current.variant.index
}
}
///|
fn contains_string(values : Array[String], value : String) -> Bool {
for item in values {
if item == value {
return true
}
}
false
}
///|
fn push_unique(values : Array[String], value : String) -> Unit {
if !contains_string(values, value) {
values.push(value)
}
}
///|
fn infer_vary(
prefs : RequestPreferences,
variants : Array[ServerVariant],
) -> Array[String] {
let vary : Array[String] = []
if !prefs.accept.is_empty() {
push_unique(vary, "Accept")
}
if !prefs.accept_language.is_empty() {
push_unique(vary, "Accept-Language")
}
if !prefs.accept_encoding.is_empty() {
push_unique(vary, "Accept-Encoding")
}
if !prefs.accept_charset.is_empty() {
push_unique(vary, "Accept-Charset")
}
let mut has_language = false
let mut has_encoding = false
let mut has_charset = false
for variant in variants {
if variant.language is Some(_) {
has_language = true
}
if variant.encoding is Some(_) {
has_encoding = true
}
if variant.charset is Some(_) {
has_charset = true
}
}
if has_language {
push_unique(vary, "Accept-Language")
}
if has_encoding {
push_unique(vary, "Accept-Encoding")
}
if has_charset {
push_unique(vary, "Accept-Charset")
}
vary
}
///|
fn variant_explanation(best : VariantCandidate?) -> String {
match best {
Some(candidate) => {
let buf = StringBuilder()
buf.write_string("selected ")
buf.write_string(candidate.variant.id)
buf.write_string(": ")
buf.write_string(candidate.reason)
buf.to_string()
}
None => "no acceptable variant matched"
}
}
///|
pub fn negotiate_variant(
preferences : RequestPreferences,
variants : Array[ServerVariant],
) -> Result[VariantDecision, ParseError] {
let matches : Array[VariantCandidate] = []
let mut best : VariantCandidate? = None
for i in 0.. {
matches.push(candidate)
if (match best {
None => true
Some(current) => better_variant(candidate, current)
}) {
best = Some(candidate)
}
}
Ok(None) => ()
Err(err) => return Err(err)
}
}
let selected = match best {
Some(candidate) => Some(candidate.variant)
None => None
}
let quality = match best {
Some(candidate) => candidate.quality
None => 0
}
Ok({
selected,
quality,
matches,
vary: infer_vary(preferences, variants),
explanation: variant_explanation(best),
})
}
///|
pub fn summarize_response(decision : VariantDecision) -> ResponseSummary {
match decision.selected {
Some(variant) =>
{
status: 200,
content_type: Some(variant.media),
content_language: variant.language,
content_encoding: variant.encoding,
charset: variant.charset,
vary: decision.vary,
explanation: decision.explanation,
}
None =>
{
status: 406,
content_type: None,
content_language: None,
content_encoding: None,
charset: None,
vary: decision.vary,
explanation: decision.explanation,
}
}
}
///|
pub fn format_variant_decision(decision : VariantDecision) -> String {
let buf = StringBuilder()
buf.write_string(decision.explanation)
buf.write_string(" (matches=")
buf.write_string(decision.matches.length().to_string())
buf.write_string(", vary=")
buf.write_string(join_header_names(decision.vary))
buf.write_char(')')
buf.to_string()
}
///|
pub fn format_response_summary(summary : ResponseSummary) -> String {
let buf = StringBuilder()
buf.write_string("status=")
buf.write_string(summary.status.to_string())
match summary.content_type {
Some(value) => {
buf.write_string(", content-type=")
buf.write_string(value)
}
None => ()
}
match summary.content_language {
Some(value) => {
buf.write_string(", content-language=")
buf.write_string(value)
}
None => ()
}
match summary.content_encoding {
Some(value) => {
buf.write_string(", content-encoding=")
buf.write_string(value)
}
None => ()
}
match summary.charset {
Some(value) => {
buf.write_string(", charset=")
buf.write_string(value)
}
None => ()
}
if summary.vary.length() > 0 {
buf.write_string(", vary=")
buf.write_string(join_header_names(summary.vary))
}
buf.write_string(", ")
buf.write_string(summary.explanation)
buf.to_string()
}