canic-host 0.35.12

Host-side build, install, fleet, and release-set library for Canic workspaces
Documentation
pub const RECORD_MARKER: &str = "record {";

#[must_use]
pub fn find_field<'a>(value: &'a serde_json::Value, field: &str) -> Option<&'a serde_json::Value> {
    match value {
        serde_json::Value::Object(map) => map
            .get(field)
            .or_else(|| map.values().find_map(|value| find_field(value, field))),
        serde_json::Value::Array(values) => {
            values.iter().find_map(|value| find_field(value, field))
        }
        _ => None,
    }
}

#[must_use]
pub fn find_string_field(value: &serde_json::Value, field: &str) -> Option<String> {
    match value {
        serde_json::Value::Object(map) => map
            .get(field)
            .and_then(|value| value.as_str().map(ToString::to_string))
            .or_else(|| {
                map.values()
                    .find_map(|value| find_string_field(value, field))
            }),
        serde_json::Value::Array(values) => values
            .iter()
            .find_map(|value| find_string_field(value, field)),
        _ => None,
    }
}

#[must_use]
pub fn response_candid(value: &serde_json::Value) -> Option<&str> {
    find_field(value, "response_candid").and_then(serde_json::Value::as_str)
}

#[must_use]
pub fn parse_candid_text_field(output: &str, field: &str) -> Option<String> {
    let after_eq = field_value_after_equals(output, field)?;
    let after_quote = after_eq.trim_start().strip_prefix('"')?;
    let (value, _) = after_quote.split_once('"')?;
    Some(value.to_string())
}

#[must_use]
pub fn parse_cycle_balance_response(output: &str) -> Option<u128> {
    output
        .split_once('=')
        .map_or(output, |(_, cycles)| cycles)
        .lines()
        .find_map(parse_leading_u128_digits)
}

#[must_use]
pub fn parse_json_u64(value: &serde_json::Value) -> Option<u64> {
    value
        .as_u64()
        .or_else(|| value.as_str().and_then(parse_u64_digits))
}

#[must_use]
pub fn parse_json_u128(value: &serde_json::Value) -> Option<u128> {
    value
        .as_u64()
        .map(u128::from)
        .or_else(|| value.as_str().and_then(parse_u128_digits))
}

#[must_use]
pub fn field_value_after_equals<'a>(text: &'a str, field: &str) -> Option<&'a str> {
    let (_, after_field) = text.split_once(field)?;
    let (_, after_eq) = after_field.split_once('=')?;
    Some(after_eq.trim_start())
}

#[must_use]
pub fn text_after<'a>(text: &'a str, marker: &str) -> Option<&'a str> {
    let (_, after_marker) = text.split_once(marker)?;
    Some(after_marker.trim_start())
}

#[must_use]
pub fn parse_u64_digits(text: &str) -> Option<u64> {
    number_digits(text).parse().ok()
}

#[must_use]
pub fn parse_u128_digits(text: &str) -> Option<u128> {
    number_digits(text).parse().ok()
}

#[must_use]
pub fn parse_leading_u128_digits(text: &str) -> Option<u128> {
    leading_number_digits(text).parse().ok()
}

#[must_use]
pub fn quoted_strings(text: &str) -> Vec<String> {
    let mut values = Vec::new();
    let mut remaining = text;
    while let Some((_, after_open)) = remaining.split_once('"') {
        let Some((value, after_close)) = after_open.split_once('"') else {
            break;
        };
        values.push(value.to_string());
        remaining = after_close;
    }
    values
}

#[must_use]
pub fn candid_record_blocks(text: &str) -> Vec<&str> {
    let mut blocks = Vec::new();
    let mut index = 0;
    while let Some(relative_start) = text[index..].find(RECORD_MARKER) {
        let start = index + relative_start;
        let mut depth = 1_u32;
        let mut cursor = start + RECORD_MARKER.len();
        let bytes = text.as_bytes();
        while cursor < text.len() {
            match bytes[cursor] {
                b'{' => depth = depth.saturating_add(1),
                b'}' => {
                    depth = depth.saturating_sub(1);
                    if depth == 0 {
                        let end = cursor + 1;
                        blocks.push(&text[start..end]);
                        index = start + RECORD_MARKER.len();
                        break;
                    }
                }
                _ => {}
            }
            cursor += 1;
        }
        if depth != 0 {
            break;
        }
    }
    blocks
}

fn number_digits(text: &str) -> String {
    text.chars()
        .skip_while(|ch| !ch.is_ascii_digit())
        .take_while(|ch| ch.is_ascii_digit() || *ch == '_' || *ch == ',')
        .filter(char::is_ascii_digit)
        .collect()
}

fn leading_number_digits(text: &str) -> String {
    text.trim_start_matches(|ch: char| ch == '(' || ch.is_whitespace())
        .chars()
        .take_while(|ch| ch.is_ascii_digit() || *ch == '_' || *ch == ',')
        .filter(char::is_ascii_digit)
        .collect()
}