rototo 0.1.0-alpha.8

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

use super::super::index::*;
use super::super::source::SourceDocument;
use super::super::syntax::{ParsedToml, item_location, value_location};
use super::fields::{
    expression_field, integer_field, optional_expression_field, optional_string_field,
};
use crate::diagnostics::DiagnosticLocation;

pub(crate) fn project_layer(document: &SourceDocument, toml: &ParsedToml, id: &str) -> LayerNode {
    let root = toml.root_table();
    let location = document.document_location();
    let schema_version = root
        .map(|root| integer_field(document, root, "schema_version", location.clone()))
        .unwrap_or_else(|| ProjectField::Missing {
            location: location.clone(),
        });
    let description = root.and_then(|root| optional_string_field(document, root, "description"));
    let unit = root
        .map(|root| expression_field(document, root, "unit", location.clone()))
        .unwrap_or_else(|| ProjectField::Missing {
            location: location.clone(),
        });
    let buckets = root
        .map(|root| integer_field(document, root, "buckets", location.clone()))
        .unwrap_or_else(|| ProjectField::Missing {
            location: location.clone(),
        });

    let mut allocations = Vec::new();
    let mut allocations_invalid = false;
    match root.and_then(|root| root.get("allocation")) {
        None => {}
        Some(item) => match item.as_array() {
            Some(values) => {
                allocations = values
                    .iter()
                    .enumerate()
                    .map(|(index, value)| project_allocation(document, index, value))
                    .collect();
            }
            None => allocations_invalid = true,
        },
    }

    LayerNode {
        doc: document.id,
        id: id.to_owned(),
        location,
        schema_version,
        description,
        unit,
        buckets,
        allocations,
        allocations_invalid,
    }
}

fn project_allocation(
    document: &SourceDocument,
    index: usize,
    value: &TomlValue<'_>,
) -> AllocationNode {
    let location = value_location(document, value);
    let Some(table) = value.as_table() else {
        return AllocationNode {
            index,
            location: location.clone(),
            id: ProjectField::Invalid { location },
            status: None,
            eligibility: None,
            arms: Vec::new(),
            arms_invalid: false,
            invalid_shape: true,
        };
    };

    let id = string_field(document, table, "id", location.clone());
    let status = optional_string_field(document, table, "status");
    let eligibility = optional_expression_field(document, table, "eligibility");

    let mut arms = Vec::new();
    let mut arms_invalid = false;
    match table.get("arm") {
        None => {}
        Some(item) => match item.as_array() {
            Some(values) => {
                arms = values
                    .iter()
                    .enumerate()
                    .map(|(arm_index, value)| project_arm(document, arm_index, value))
                    .collect();
            }
            None => arms_invalid = true,
        },
    }

    AllocationNode {
        index,
        location,
        id,
        status,
        eligibility,
        arms,
        arms_invalid,
        invalid_shape: false,
    }
}

fn project_arm(document: &SourceDocument, index: usize, value: &TomlValue<'_>) -> ArmNode {
    let location = value_location(document, value);
    let Some(table) = value.as_table() else {
        return ArmNode {
            index,
            location: location.clone(),
            name: ProjectField::Invalid {
                location: location.clone(),
            },
            buckets: ProjectField::Invalid { location },
            invalid_shape: true,
        };
    };

    ArmNode {
        index,
        location: location.clone(),
        name: string_field(document, table, "name", location.clone()),
        buckets: string_field(document, table, "buckets", location),
        invalid_shape: false,
    }
}

fn string_field(
    document: &SourceDocument,
    table: &toml_span::value::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,
        },
    }
}