rototo 0.1.0-alpha.3

Control plane for runtime configuration of your application.
Documentation
use serde_json::Value as JsonValue;
use toml_span::Value as TomlValue;
use toml_span::value::Table;

use crate::diagnostics::DiagnosticLocation;

use super::super::index::*;
use super::super::source::SourceDocument;
use super::super::syntax::{item_location, plain_toml_from_span_value};

pub(super) fn integer_field(
    document: &SourceDocument,
    table: &Table<'_>,
    key: &str,
    missing_location: DiagnosticLocation,
) -> ProjectField<i64> {
    match table.get(key) {
        Some(item) => match item.as_integer() {
            Some(value) => ProjectField::Present(Spanned {
                value,
                location: item_location(document, item),
            }),
            None => ProjectField::Invalid {
                location: item_location(document, item),
            },
        },
        None => ProjectField::Missing {
            location: missing_location,
        },
    }
}

pub(super) fn string_field(
    document: &SourceDocument,
    table: &Table<'_>,
    key: &str,
    missing_location: DiagnosticLocation,
) -> ProjectField<String> {
    match table.get(key) {
        Some(item) => match item.as_str() {
            Some(value) => ProjectField::Present(Spanned {
                value: value.to_owned(),
                location: item_location(document, item),
            }),
            None => ProjectField::Invalid {
                location: item_location(document, item),
            },
        },
        None => ProjectField::Missing {
            location: missing_location,
        },
    }
}

pub(super) fn optional_string_field(
    document: &SourceDocument,
    table: &Table<'_>,
    key: &str,
) -> Option<ProjectField<String>> {
    let item = table.get(key)?;
    Some(match item.as_str() {
        Some(value) => ProjectField::Present(Spanned {
            value: value.to_owned(),
            location: item_location(document, item),
        }),
        None => ProjectField::Invalid {
            location: item_location(document, item),
        },
    })
}

pub(super) fn predicate_op_field(
    document: &SourceDocument,
    table: &Table<'_>,
    missing_location: DiagnosticLocation,
) -> ProjectField<PredicateOp> {
    match string_field(document, table, "op", missing_location) {
        ProjectField::Present(op) => ProjectField::Present(Spanned {
            value: PredicateOp::from_str(&op.value),
            location: op.location,
        }),
        ProjectField::Invalid { location } => ProjectField::Invalid { location },
        ProjectField::Missing { location } => ProjectField::Missing { location },
    }
}

pub(super) fn project_value_shape(
    document: &SourceDocument,
    item: &TomlValue<'_>,
) -> ValueShapeNode {
    ValueShapeNode {
        location: item_location(document, item),
        shape: value_shape(item),
        value: json_from_toml_value(item),
    }
}

pub(super) fn project_bucket_range(
    document: &SourceDocument,
    item: &TomlValue<'_>,
) -> BucketRangeNode {
    let location = item_location(document, item);
    let Some(array) = item.as_array() else {
        return BucketRangeNode {
            location,
            is_array: false,
            len: 0,
            start: None,
            end: None,
        };
    };
    let values: Vec<_> = array.iter().collect();
    BucketRangeNode {
        location,
        is_array: true,
        len: values.len(),
        start: values.first().and_then(|value| value.as_integer()),
        end: values.get(1).and_then(|value| value.as_integer()),
    }
}

fn value_shape(item: &TomlValue<'_>) -> ValueShape {
    if item.as_str().is_some() {
        ValueShape::String
    } else if item.as_integer().is_some() {
        ValueShape::Integer
    } else if item.as_float().is_some() {
        ValueShape::Float
    } else if item.as_bool().is_some() {
        ValueShape::Boolean
    } else if item.as_array().is_some() {
        ValueShape::Array
    } else {
        ValueShape::Table
    }
}

pub(crate) fn json_from_toml_value(value: &TomlValue<'_>) -> JsonValue {
    serde_json::to_value(plain_toml_from_span_value(value)).unwrap_or(JsonValue::Null)
}