///|
fn parse_quoted_label_value(cursor : TextCursor) -> Result[String, String] {
if !cursor.consume('"') {
return Err("expected quoted label value")
}
let encoded = StringBuilder()
while !cursor.is_end() {
match cursor.next() {
Some('"') => return unescape_label_value(encoded.to_string())
Some('\\') =>
match cursor.next() {
Some(escaped) => {
encoded.write_char('\\')
encoded.write_char(escaped)
}
None => return Err("trailing backslash in quoted label value")
}
Some(char) => encoded.write_char(char)
None => ()
}
}
Err("unterminated quoted label value")
}
///|
fn parse_labels_from(cursor : TextCursor) -> Result[Array[Label], String] {
if !cursor.consume('{') {
return Err("expected '{' before label set")
}
cursor.skip_horizontal_space()
let labels : Array[Label] = []
if cursor.consume('}') {
return Ok(labels)
}
while true {
let name = cursor.read_label_name()
if !is_valid_label_name(name) {
return Err("invalid or missing label name")
}
cursor.skip_horizontal_space()
if !cursor.consume('=') {
return Err("expected '=' after label name '\{name}'")
}
cursor.skip_horizontal_space()
let value = match parse_quoted_label_value(cursor) {
Ok(value) => value
Err(error) => return Err(error)
}
labels.push(Label::new(name, value))
cursor.skip_horizontal_space()
if cursor.consume('}') {
return Ok(labels)
}
if !cursor.consume(',') {
return Err("expected ',' or '}' after label value")
}
cursor.skip_horizontal_space()
if cursor.peek() == Some('}') {
return Err("trailing comma in label set")
}
}
Err("unexpected end of label set")
}
///|
/// Parse one complete OpenMetrics label set.
///
/// Whitespace around separators is accepted, while a trailing comma is
/// rejected.
pub fn parse_label_set(input : StringView) -> Result[Array[Label], String] {
let cursor = TextCursor::new(input)
cursor.skip_horizontal_space()
let labels = match parse_labels_from(cursor) {
Ok(labels) => labels
Err(error) => return Err(error)
}
cursor.skip_horizontal_space()
if !cursor.is_end() {
return Err("unexpected text after label set")
}
Ok(labels)
}
///|
fn encode_label_set_inner(
labels : Array[Label],
canonical_number_label : StringView,
) -> String {
if labels.is_empty() {
return ""
}
let out = StringBuilder()
out.write_char('{')
for index in 0.. 0 {
out.write_char(',')
}
let label = labels[index]
let value = if !canonical_number_label.is_empty() &&
canonical_number_label.equal_to_string(label.name) {
match parse_number(label.value) {
Ok(number) => format_number(number)
Err(_) => label.value
}
} else {
label.value
}
out.write_string(label.name)
out.write_string("=\"")
out.write_string(escape_label_value(value))
out.write_char('"')
}
out.write_char('}')
out.to_string()
}
///|
/// Encode labels in their existing order.
pub fn encode_label_set(labels : Array[Label]) -> String {
encode_label_set_inner(labels, "")
}
///|
fn encode_metric_label_set(
labels : Array[Label],
canonical_number_label : StringView,
) -> String {
encode_label_set_inner(labels, canonical_number_label)
}