///|
/// Negotiation failures which can be mapped to a safe HTTP error response.
pub(all) suberror HttpMetricsError {
MalformedAccept(String)
AcceptHeaderTooLarge
NotAcceptable
} derive(Debug, Eq)
///|
let max_accept_header_bytes = 8192
///|
let max_accept_ranges = 32
///|
fn parse_quality(value : String) -> Int? {
if value.is_empty() {
return None
}
let mut seen_dot = false
let mut whole = -1
let mut fraction = 0
let mut fraction_digits = 0
for char in value {
if char == '.' {
if seen_dot {
return None
}
seen_dot = true
} else if char >= '0' && char <= '9' {
let digit = match char {
'0' => 0
'1' => 1
'2' => 2
'3' => 3
'4' => 4
'5' => 5
'6' => 6
'7' => 7
'8' => 8
'9' => 9
_ => abort("digit guard is exhaustive")
}
if !seen_dot {
if whole != -1 {
return None
}
whole = digit
} else {
if fraction_digits >= 3 {
return None
}
fraction = fraction * 10 + digit
fraction_digits += 1
}
} else {
return None
}
}
if whole < 0 || whole > 1 {
return None
}
if whole == 1 && fraction != 0 {
return None
}
let mut scaled_fraction = fraction
for _ in fraction_digits..<3 {
scaled_fraction *= 10
}
Some(whole * 1000 + scaled_fraction)
}
///|
fn parse_accept_range(
value : String,
) -> (String, Int, String?) raise HttpMetricsError {
let parts = [ for part in value.split(";") => part.trim().to_owned() ]
if parts.is_empty() || parts[0].is_empty() || !parts[0].contains("/") {
raise MalformedAccept("invalid media range")
}
let media_type = parts[0].to_lower()
let mut quality = 1000
let mut version : String? = None
for index in 1.. piece.trim().to_owned() ]
if pair.length() != 2 || pair[0].is_empty() || pair[1].is_empty() {
raise MalformedAccept("invalid media parameter")
}
match pair[0].to_lower() {
"q" =>
match parse_quality(pair[1]) {
Some(parsed) => quality = parsed
None => raise MalformedAccept("invalid q value")
}
"version" => version = Some(pair[1])
_ => ()
}
}
(media_type, quality, version)
}
///|
fn match_specificity(
media_type : String,
version : String?,
format : @openmetrics.ExpositionFormat,
) -> Int? {
match format {
PrometheusText => {
let version_matches = match version {
Some(value) => value == "0.0.4"
None => true
}
if !version_matches {
None
} else if media_type == "text/plain" {
Some(2)
} else if media_type == "text/*" {
Some(1)
} else if media_type == "*/*" {
Some(0)
} else {
None
}
}
OpenMetricsText => {
let version_matches = match version {
Some(value) => value == "1.0.0"
None => true
}
if !version_matches {
None
} else if media_type == "application/openmetrics-text" {
Some(2)
} else if media_type == "application/*" {
Some(1)
} else if media_type == "*/*" {
Some(0)
} else {
None
}
}
}
}
///|
fn update_candidate(
current_quality : Int,
current_specificity : Int,
quality : Int,
specificity : Int,
) -> (Int, Int) {
// RFC-style media-range specificity is resolved before quality. This keeps
// `application/openmetrics-text;q=0, */*;q=1` from selecting OpenMetrics.
if specificity > current_specificity ||
(specificity == current_specificity && quality > current_quality) {
(quality, specificity)
} else {
(current_quality, current_specificity)
}
}
///|
/// Select a supported exposition format from an HTTP Accept header.
///
/// Missing or blank Accept defaults to Prometheus text 0.0.4 for legacy
/// scraper compatibility. For equal quality and equally specific explicit
/// formats, OpenMetrics wins; a wildcard-only tie remains Prometheus.
pub fn negotiate(
accept : String?,
) -> @openmetrics.ExpositionFormat raise HttpMetricsError {
match accept {
None => PrometheusText
Some(header) => {
if header.length() > max_accept_header_bytes {
raise AcceptHeaderTooLarge
}
if header.trim().to_owned().is_empty() {
return PrometheusText
}
let mut prometheus_quality = -1
let mut prometheus_specificity = -1
let mut openmetrics_quality = -1
let mut openmetrics_specificity = -1
let mut ranges = 0
for raw_range in header.split(",") {
let range = raw_range.trim().to_owned()
if range.is_empty() {
continue
}
ranges += 1
if ranges > max_accept_ranges {
raise AcceptHeaderTooLarge
}
let (media_type, quality, version) = parse_accept_range(range)
match match_specificity(media_type, version, PrometheusText) {
Some(specificity) => {
let (next_quality, next_specificity) = update_candidate(
prometheus_quality, prometheus_specificity, quality, specificity,
)
prometheus_quality = next_quality
prometheus_specificity = next_specificity
}
None => ()
}
match match_specificity(media_type, version, OpenMetricsText) {
Some(specificity) => {
let (next_quality, next_specificity) = update_candidate(
openmetrics_quality, openmetrics_specificity, quality, specificity,
)
openmetrics_quality = next_quality
openmetrics_specificity = next_specificity
}
None => ()
}
}
if prometheus_quality <= 0 && openmetrics_quality <= 0 {
raise NotAcceptable
}
if prometheus_quality > openmetrics_quality {
PrometheusText
} else if openmetrics_quality > prometheus_quality {
OpenMetricsText
} else if prometheus_specificity > openmetrics_specificity {
PrometheusText
} else if openmetrics_specificity > prometheus_specificity {
OpenMetricsText
} else if prometheus_specificity == 0 {
PrometheusText
} else {
OpenMetricsText
}
}
}
}