///|
/// Column metadata from an InfluxDB annotated CSV response.
pub(all) struct AnnotatedColumn {
name : String
data_type : String
group : Bool
default : String
}
///|
/// One data row from an annotated CSV response.
pub(all) struct AnnotatedRow {
values : Array[String]
}
///|
/// Parsed annotated CSV response.
pub(all) struct AnnotatedCsv {
columns : Array[AnnotatedColumn]
rows : Array[AnnotatedRow]
}
///|
/// Parse an InfluxDB annotated CSV response.
pub fn parse_annotated_csv(text : String) -> Result[AnnotatedCsv, String] {
let data_types : Array[String] = Array(capacity=0)
let groups : Array[Bool] = Array(capacity=0)
let defaults : Array[String] = Array(capacity=0)
let data_rows : Array[Array[String]] = Array(capacity=0)
let mut column_names : Array[String] = Array(capacity=0)
for line in text.split("\n") {
let clean = line.trim().to_owned()
if clean.is_empty() {
continue
}
if clean.has_prefix("#datatype") {
match parse_csv_row(clean[9:].to_owned()) {
Ok(values) => {
data_types.clear()
data_types.append(values)
}
Err(error) => return Err(error)
}
continue
}
if clean.has_prefix("#group") {
match parse_bool_annotation(clean[6:].to_owned()) {
Ok(values) => {
groups.clear()
groups.append(values)
}
Err(error) => return Err(error)
}
continue
}
if clean.has_prefix("#default") {
match parse_csv_row(clean[8:].to_owned()) {
Ok(values) => {
defaults.clear()
defaults.append(values)
}
Err(error) => return Err(error)
}
continue
}
if clean.has_prefix("#") {
continue
}
match parse_csv_row(clean) {
Ok(row) =>
if column_names.is_empty() {
column_names = row
} else {
data_rows.push(row)
}
Err(error) => return Err(error)
}
}
if column_names.is_empty() {
return Err("annotated CSV must contain a column-name row")
}
let columns : Array[AnnotatedColumn] = Array(capacity=0)
for i in 1.. Result[Array[String], String] {
let values : Array[String] = Array(capacity=0)
let field = StringBuilder()
let mut in_quotes = false
let mut i = 0
while i < line.length() {
let code = line.at(i).to_int()
if in_quotes {
if code == 34 {
if i + 1 < line.length() && line.at(i + 1).to_int() == 34 {
field.write_char('"')
i = i + 1
} else {
in_quotes = false
}
} else {
field.write_char(line.at(i).to_char().unwrap())
}
} else if code == 34 {
in_quotes = true
} else if code == 44 {
values.push(field.to_string())
field.reset()
} else {
field.write_char(line.at(i).to_char().unwrap())
}
i = i + 1
}
values.push(field.to_string())
Ok(values)
}
///|
/// Parse the #group annotation, which contains true/false tokens.
fn parse_bool_annotation(line : String) -> Result[Array[Bool], String] {
match parse_csv_row(line) {
Ok(values) => {
let out : Array[Bool] = Array(capacity=0)
for value in values {
if value.is_empty() {
out.push(false)
} else if value == "true" {
out.push(true)
} else if value == "false" {
out.push(false)
} else {
return Err("invalid #group value \{value}")
}
}
Ok(out)
}
Err(error) => Err(error)
}
}