distributed 4.0.2

CQRS/ES framework for Rust using Plain Old Rust Structs — append-only events, replay, snapshots, outbox, service bus, and pluggable infrastructure
Documentation
use super::*;

pub(crate) fn trusted_preset_descriptors(
    manifest: &DistributedClientManifest,
) -> Result<Vec<ClientTrustedPresetDescriptor>, ClientManifestError> {
    let models: BTreeMap<&str, &ClientModel> = manifest
        .models
        .iter()
        .map(|model| (model.id.as_str(), model))
        .collect();
    let mut descriptors = BTreeMap::<String, String>::new();

    for command in &manifest.commands {
        for descriptor in &command.extensions.trusted_presets {
            insert_trusted_preset_descriptor(
                &mut descriptors,
                descriptor,
                &format!("command `{}`", command.name),
            )?;
        }
    }
    for model in &manifest.models {
        if let ClientRowPolicy::Predicate { expression } = &model.row_policy {
            collect_row_policy_trusted_presets(expression, model, &models, &mut descriptors)?;
        }
    }

    Ok(descriptors
        .into_iter()
        .map(|(name, codec)| ClientTrustedPresetDescriptor { name, codec })
        .collect())
}

fn collect_row_policy_trusted_presets(
    expression: &FilterExpr,
    model: &ClientModel,
    models: &BTreeMap<&str, &ClientModel>,
    descriptors: &mut BTreeMap<String, String>,
) -> Result<(), ClientManifestError> {
    match expression {
        FilterExpr::And(expressions) | FilterExpr::Or(expressions) => {
            for expression in expressions {
                collect_row_policy_trusted_presets(expression, model, models, descriptors)?;
            }
        }
        FilterExpr::Not(expression) => {
            collect_row_policy_trusted_presets(expression, model, models, descriptors)?;
        }
        FilterExpr::Cmp {
            column,
            rhs: Operand::Claim(claim),
            ..
        } => {
            insert_row_policy_trusted_preset(model, column, &claim.header, descriptors)?;
        }
        FilterExpr::In { column, values, .. } => {
            for value in values {
                if let Operand::Claim(claim) = value {
                    insert_row_policy_trusted_preset(model, column, &claim.header, descriptors)?;
                }
            }
        }
        FilterExpr::Rel { field, predicate } => {
            let relationship = model
                .relationships
                .iter()
                .find(|relationship| relationship.name == *field)
                .ok_or_else(|| {
                    ClientManifestError(format!(
                        "model `{}` client-visible row policy references absent relationship `{field}`",
                        model.id
                    ))
                })?;
            let target = models
                .get(relationship.target_model.as_str())
                .copied()
                .ok_or_else(|| {
                    ClientManifestError(format!(
                        "model `{}` client-visible row policy relationship `{field}` targets absent model `{}`",
                        model.id, relationship.target_model
                    ))
                })?;
            collect_row_policy_trusted_presets(predicate, target, models, descriptors)?;
        }
        FilterExpr::Cmp { .. } | FilterExpr::IsNull { .. } => {}
    }
    Ok(())
}

fn insert_row_policy_trusted_preset(
    model: &ClientModel,
    column: &str,
    name: &str,
    descriptors: &mut BTreeMap<String, String>,
) -> Result<(), ClientManifestError> {
    let field = model
        .fields
        .iter()
        .find(|field| field.name == column)
        .ok_or_else(|| {
            ClientManifestError(format!(
                "model `{}` client-visible row policy references absent field `{column}`",
                model.id
            ))
        })?;
    if matches!(field.codec.as_str(), "base64" | "json") {
        return Err(ClientManifestError(format!(
            "model `{}` row-policy claim `{name}` targets `{column}` with non-local codec `{}`",
            model.id, field.codec
        )));
    }
    insert_trusted_preset_descriptor(
        descriptors,
        &ClientTrustedPresetDescriptor {
            name: name.into(),
            codec: field.codec.clone(),
        },
        &format!("model `{}` row policy field `{column}`", model.id),
    )
}

fn insert_trusted_preset_descriptor(
    descriptors: &mut BTreeMap<String, String>,
    descriptor: &ClientTrustedPresetDescriptor,
    owner: &str,
) -> Result<(), ClientManifestError> {
    if descriptor.name.is_empty()
        || descriptor.name.len() > 128
        || descriptor.name.trim() != descriptor.name
        || descriptor.name.chars().any(char::is_control)
    {
        return Err(ClientManifestError(format!(
            "{owner} has an invalid trusted preset name"
        )));
    }
    match descriptors.entry(descriptor.name.clone()) {
        std::collections::btree_map::Entry::Vacant(entry) => {
            entry.insert(descriptor.codec.clone());
        }
        std::collections::btree_map::Entry::Occupied(entry) if entry.get() == &descriptor.codec => {
        }
        std::collections::btree_map::Entry::Occupied(entry) => {
            return Err(ClientManifestError(format!(
                "trusted preset `{}` uses incompatible codecs `{}` and `{}` across the selected client surface ({owner})",
                descriptor.name,
                entry.get(),
                descriptor.codec
            )));
        }
    }
    Ok(())
}
pub(super) fn validate_surface_structure(surface: &Surface) -> Result<(), ClientManifestError> {
    fn unique_nonempty<'a>(
        values: impl IntoIterator<Item = &'a str>,
        label: &str,
    ) -> Result<(), ClientManifestError> {
        let mut seen = BTreeSet::new();
        for value in values {
            if value.trim().is_empty() {
                return Err(ClientManifestError(format!("{label} id must not be empty")));
            }
            if !seen.insert(value) {
                return Err(ClientManifestError(format!(
                    "duplicate {label} id `{value}`"
                )));
            }
        }
        Ok(())
    }

    unique_nonempty(
        surface
            .models
            .values()
            .map(|model| model.model_name.as_str()),
        "model",
    )?;
    unique_nonempty(
        surface
            .models
            .values()
            .map(|model| model.table_name.as_str()),
        "model source table",
    )?;
    unique_nonempty(
        surface
            .models
            .values()
            .map(|model| model.object_name.as_str()),
        "model typename",
    )?;
    for (key, model) in &surface.models {
        if key != &model.model_name {
            return Err(ClientManifestError(format!(
                "surface model map key `{key}` does not match model id `{}`",
                model.model_name
            )));
        }
        unique_nonempty(
            model.columns.iter().map(|field| field.name.as_str()),
            "field",
        )?;
        unique_nonempty(
            model.relationships.iter().map(|field| field.name.as_str()),
            "relationship",
        )?;
        if let SurfaceRowPolicy::Predicate(predicate) = &model.row_policy {
            predicate
                .validate_row_policy_literals()
                .map_err(ClientManifestError)?;
            if !predicate.is_client_portable() {
                return Err(ClientManifestError(format!(
                    "model `{}` exposes a row policy with a JavaScript-unsafe integer; select it through surface_for_role so it becomes server-only",
                    model.model_name
                )));
            }
        }
        for relationship in &model.relationships {
            if !surface.models.contains_key(&relationship.target_model) {
                return Err(ClientManifestError(format!(
                    "model `{}` relationship `{}` targets absent model `{}`",
                    model.model_name, relationship.name, relationship.target_model
                )));
            }
            unique_nonempty(
                relationship.dependencies.iter().map(String::as_str),
                &format!(
                    "model `{}` relationship `{}` dependency",
                    model.model_name, relationship.name
                ),
            )?;
        }
    }
    unique_nonempty(
        surface.query_fields.iter().map(|root| root.name.as_str()),
        "query root",
    )?;
    unique_nonempty(
        surface
            .subscription_fields
            .iter()
            .map(|root| root.name.as_str()),
        "subscription root",
    )?;
    unique_nonempty(
        surface
            .commands
            .iter()
            .map(|command| command.command_name.as_str()),
        "command",
    )?;
    unique_nonempty(
        surface
            .commands
            .iter()
            .map(|command| command.field_name.as_str()),
        "command mutation field",
    )?;
    for command in &surface.commands {
        unique_nonempty(
            command.roles.iter().map(String::as_str),
            &format!("command `{}` role", command.command_name),
        )?;
    }
    unique_nonempty(
        surface
            .projectors
            .iter()
            .map(|projector| projector.name.as_str()),
        "projector",
    )?;
    for projector in &surface.projectors {
        unique_nonempty(
            projector.facts.iter().map(String::as_str),
            &format!("projector `{}` fact", projector.name),
        )?;
        unique_nonempty(
            projector.models.iter().map(String::as_str),
            &format!("projector `{}` model", projector.name),
        )?;
        for model in &projector.models {
            if !surface.models.contains_key(model) {
                return Err(ClientManifestError(format!(
                    "projector `{}` targets absent model `{model}`",
                    projector.name
                )));
            }
        }
    }
    Ok(())
}