polyxml-cli 0.23.3

Command-line interface and polyglot XSD-to-code generator for PolyXML
Documentation
//! Shared validation and normalization for direct generation and both manifest forms.
use super::*;
use std::io::{Error, ErrorKind, Result};

fn invalid(message: impl Into<String>) -> Error {
    Error::new(ErrorKind::InvalidInput, message.into())
}

pub(crate) fn target_options(target: &config::TargetConfig) -> TargetEmitOptions<'_> {
    TargetEmitOptions {
        backend: target.backend.as_deref(),
        features: &target.features,
        slots: target.slots,
        kw_only: target.kw_only,
        package: target.package.as_deref().or(target.namespace.as_deref()),
        mode: target
            .mode
            .as_deref()
            .or_else(|| target.modules.filter(|v| *v).map(|_| "modules")),
        zero_copy: target.zero_copy,
        codecs: target.codecs,
        style: target.style.as_deref(),
        builder: None,
        codec: None,
        rkyv: None,
        phf: None,
        custom_header: target.custom_header.as_deref(),
    }
}

impl<'a> TargetEmitOptions<'a> {
    pub(crate) fn resolve(mut self, lang: &str) -> Result<Self> {
        let language = lang.to_lowercase();
        let target = match language.as_str() {
            "python" | "py" => "python",
            "rust" | "rs" => "rust",
            "typescript" | "ts" => "typescript",
            "c++" | "cpp" => "cpp",
            "cs" | "c#" | "csharp" => "csharp",
            "java" => "java",
            "go" => "go",
            _ => return Err(invalid(format!("Unknown target '{lang}'. Supported targets: python, rust, cpp, java, typescript, go, csharp."))),
        };
        let supported_backends = match target {
            "python" => "dataclass, pydantic",
            "typescript" => "interfaces, zod, valibot, typebox",
            "java" => "standard, jackson",
            "csharp" => "standard, source-gen",
            "cpp" => "standard, glaze",
            "go" => "standard, easyjson, sonic",
            _ => "standard",
        };
        if let Some(backend) = self.backend {
            let valid = match target {
                "python" => PythonBackend::from_str_loose(backend).is_some(),
                "typescript" => TypeScriptBackend::from_str_loose(backend).is_some(),
                "java" => JavaBackend::from_str_loose(backend).is_some(),
                "cpp" => CppBackend::from_str_loose(backend).is_some(),
                "go" => GoBackend::from_str_loose(backend).is_some(),
                "csharp" => matches!(backend, "standard" | "source-gen"),
                _ => backend == "standard",
            };
            if !valid {
                return Err(invalid(format!("backend '{backend}' is not supported for target '{target}'. Supported backends: {supported_backends}.")));
            }
        }

        let supported_styles: &[&str] = match target {
            "java" => &["record", "pojo", "class"],
            "csharp" => &["record", "pojo", "class", "record-class", "record-struct"],
            "python" => &["dataclass"],
            _ => &[],
        };
        if let Some(style) = self.style {
            if !supported_styles.contains(&style) {
                return Err(invalid(format!(
                    "style '{style}' is not supported for target '{target}'. Supported styles: {}.",
                    if supported_styles.is_empty() {
                        "none".into()
                    } else {
                        supported_styles.join(", ")
                    }
                )));
            }
            if target == "python"
                && self.backend.is_some_and(|v| {
                    PythonBackend::from_str_loose(v) == Some(PythonBackend::Pydantic)
                })
            {
                return Err(invalid(
                    "style 'dataclass' requires the Python dataclass backend.",
                ));
            }
        }
        if let Some(mode) = self.mode {
            if target != "cpp" || CppMode::from_str_loose(mode).is_none() {
                return Err(invalid(format!("mode '{mode}' is not supported for target '{target}'; C++ supports header or modules.")));
            }
        }
        if target != "python" && (self.slots.is_some() || self.kw_only.is_some()) {
            return Err(invalid("slots and kw_only are Python-only options."));
        }
        let supported_features: &[&str] = match target {
            "rust" => &["zero-copy", "rkyv", "phf"],
            "java" => &["builder", "direct-codec"],
            "python" => &["slots", "kw-only"],
            _ => &[],
        };
        for feature in self.features {
            if !supported_features.contains(&feature.as_str()) {
                return Err(invalid(format!("feature '{feature}' is not supported for target '{target}'. Supported features: {}.", if supported_features.is_empty() { "none".into() } else { supported_features.join(", ") })));
            }
            let option = match feature.as_str() {
                "zero-copy" => &mut self.zero_copy,
                "rkyv" => &mut self.rkyv,
                "phf" => &mut self.phf,
                "builder" => &mut self.builder,
                "slots" => &mut self.slots,
                "kw-only" => &mut self.kw_only,
                "direct-codec" => {
                    self.codec = Some("direct");
                    continue;
                }
                _ => unreachable!(),
            };
            if *option == Some(false) {
                return Err(invalid(format!(
                    "feature '{feature}' conflicts with an explicitly disabled option."
                )));
            }
            *option = Some(true);
        }
        if target == "python"
            && self
                .backend
                .is_some_and(|v| PythonBackend::from_str_loose(v) == Some(PythonBackend::Pydantic))
            && (self.slots.is_some() || self.kw_only.is_some())
        {
            return Err(invalid(
                "slots and kw-only require the Python dataclass backend.",
            ));
        }
        Ok(self)
    }
}