distributed_cli 4.0.0

The `distributed` CLI for Distributed applications: contracts check/accept, scaffold projects, describe manifests, compile clients, and render schema artifacts. Also a library so other CLIs (e.g. hops) can mount its commands.
Documentation
use super::*;

pub(crate) fn canonicalize_commands(
    commands: &mut [ManifestCommand],
) -> Result<(), ClientCompileError> {
    for command in commands.iter_mut() {
        canonicalize_string_set(
            &mut command.grants,
            &format!("command `{}` grant", command.name),
        )?;
        for descriptor in &command.extensions.trusted_presets {
            validate_nonempty(&descriptor.name, "trusted preset name")?;
            validate_nonempty(&descriptor.codec, "trusted preset codec")?;
            if descriptor.name.len() > 128
                || descriptor.name.trim() != descriptor.name
                || descriptor.name.chars().any(char::is_control)
            {
                return Err(ClientCompileError::manifest(
                    "client.manifest.trusted_preset_name",
                    format!(
                        "manifest command `{}` has an invalid trusted preset name",
                        command.name
                    ),
                ));
            }
        }
        command.extensions.trusted_presets.sort();
        if command
            .extensions
            .trusted_presets
            .windows(2)
            .any(|pair| pair[0].name == pair[1].name)
        {
            return Err(ClientCompileError::manifest(
                "client.manifest.trusted_preset_inventory",
                format!(
                    "manifest command `{}` repeats a trusted preset descriptor",
                    command.name
                ),
            ));
        }
        if let Some(defaults) = &mut command.extensions.input_defaults {
            for default in &mut defaults.defaults {
                validate_nonempty_strings(
                    &default.path,
                    &format!("command `{}` input default path", command.name),
                )?;
            }
            defaults.defaults.sort();
            if defaults
                .defaults
                .windows(2)
                .any(|pair| pair[0].path == pair[1].path)
            {
                return Err(ClientCompileError::manifest(
                    "client.manifest.input_default_path",
                    format!(
                        "manifest command `{}` repeats an input default path",
                        command.name
                    ),
                ));
            }
        }
        if let Some(direct) = &mut command.extensions.direct_projection {
            if let Some(partition) = &mut direct.partition {
                canonicalize_effect_expression(partition);
            }
        }
        if let Some(effects) = &mut command.extensions.effects {
            for effect in &mut effects.operations {
                canonicalize_effect(effect);
            }
        }
        if let Some(confirmations) = &mut command.extensions.confirmations {
            for confirmation in &mut confirmations.expected {
                canonicalize_effect_key(&mut confirmation.key);
                if let Some(partition) = &mut confirmation.partition {
                    canonicalize_effect_expression(partition);
                }
            }
        }
    }
    commands.sort_by(|left, right| left.name.cmp(&right.name));
    Ok(())
}

pub(crate) fn canonicalize_effect(effect: &mut ManifestEffect) {
    match effect {
        ManifestEffect::Upsert { key, fields, .. } | ManifestEffect::Patch { key, fields, .. } => {
            canonicalize_effect_key(key);
            for field in fields.iter_mut() {
                canonicalize_effect_expression(&mut field.value);
            }
            fields.sort_by(|left, right| left.field.cmp(&right.field));
        }
        ManifestEffect::Delete { key, .. } => canonicalize_effect_key(key),
        ManifestEffect::Link { source, target, .. }
        | ManifestEffect::Unlink { source, target, .. } => {
            canonicalize_effect_key(source);
            canonicalize_effect_key(target);
        }
        ManifestEffect::InvalidateRelationship { source, .. } => {
            canonicalize_effect_key(source);
        }
        ManifestEffect::InvalidateModel { .. } => {}
    }
}

pub(crate) fn canonicalize_effect_key(key: &mut ManifestEffectKey) {
    for field in &mut key.fields {
        canonicalize_effect_expression(&mut field.value);
    }
}

pub(crate) fn canonicalize_effect_expression(expression: &mut ManifestEffectExpression) {
    if let ManifestEffectExpression::Constant { value } = expression {
        *value = canonical_json_value(std::mem::take(value));
    }
}