config-disassembler 0.8.3

Disassemble config files into smaller files and reassemble on demand.
Documentation
//! Type definitions for XML element representation.
//!
//! Uses serde_json::Value for flexible representation matching the TypeScript structure:
//! - Object with keys: element names, @attr for attributes, #text for text content, ?xml for declaration
//! - Values: string, nested object, or array of objects/strings

use serde_json::Value as JsonValue;

/// XmlElement is a flexible representation of XML - equivalent to TypeScript's XmlElement type.
/// Uses serde_json::Value for compatibility with quickxml_to_serde output.
pub type XmlElement = JsonValue;

/// Parameters for parsing an element during disassembly.
#[derive(Debug, Clone)]
pub struct XmlElementParams<'a> {
    pub element: XmlElement,
    pub disassembled_path: &'a str,
    pub unique_id_elements: Option<&'a str>,
    pub root_element_name: &'a str,
    pub root_attributes: XmlElement,
    pub key: &'a str,
    pub leaf_content: XmlElement,
    pub leaf_count: usize,
    pub has_nested_elements: bool,
    pub format: &'a str,
    pub xml_declaration: Option<XmlElement>,
    pub strategy: &'a str,
    /// Pre-resolved unique-ID for this element. When `Some`, the file
    /// writer uses this verbatim and skips its own `parse_unique_id_element`
    /// derivation. Set by the caller when collision detection across a
    /// sibling group has determined this element must use a hash fallback
    /// instead of its derived id (see `resolve_collision_safe_ids`).
    pub precomputed_unique_id: Option<&'a str>,
}

/// Options for building a single disassembled file.
#[derive(Debug, Clone)]
pub struct BuildDisassembledFileOptions<'a> {
    pub content: XmlElement,
    pub disassembled_path: &'a str,
    pub output_file_name: Option<&'a str>,
    pub subdirectory: Option<&'a str>,
    pub wrap_key: Option<&'a str>,
    pub is_grouped_array: bool,
    pub root_element_name: &'a str,
    pub root_attributes: XmlElement,
    pub format: &'a str,
    pub xml_declaration: Option<XmlElement>,
    pub unique_id_elements: Option<&'a str>,
    /// Pre-resolved unique-ID for this element. When `Some`, takes
    /// precedence over `unique_id_elements` derivation. See the matching
    /// field on [`XmlElementParams`] for details.
    pub precomputed_unique_id: Option<&'a str>,
}

/// Result from unified element parsing.
#[derive(Debug, Clone, Default)]
pub struct UnifiedParseResult {
    pub leaf_content: XmlElement,
    pub leaf_count: usize,
    pub has_nested_elements: bool,
    pub nested_groups: Option<XmlElementArrayMap>,
}

/// Map of tag name to array of elements.
pub type XmlElementArrayMap = std::collections::HashMap<String, Vec<XmlElement>>;

/// Rule for decomposing a nested tag when using grouped-by-tag strategy.
/// E.g. write each &lt;objectPermissions&gt; to its own file, or group &lt;fieldPermissions&gt; by object.
#[derive(Debug, Clone)]
pub struct DecomposeRule {
    /// Element tag to decompose (e.g. "objectPermissions", "fieldPermissions").
    pub tag: String,
    /// Subdirectory under disassembled path (defaults to tag if empty).
    pub path_segment: String,
    /// "split" = one file per array item (filename from field); "group" = group by field, one file per group.
    pub mode: String,
    /// Field name: for split, used for filename; for group, used to group items.
    pub field: String,
}

/// Options for building disassembled files from a source file.
#[derive(Debug, Clone)]
pub struct BuildDisassembledFilesOptions<'a> {
    pub file_path: &'a str,
    pub disassembled_path: &'a str,
    pub base_name: &'a str,
    pub post_purge: bool,
    pub format: &'a str,
    pub unique_id_elements: Option<&'a str>,
    pub strategy: &'a str,
    /// When strategy is grouped-by-tag, optionally decompose specific tags (split or group by field).
    pub decompose_rules: Option<&'a [DecomposeRule]>,
}

/// Parameters for writing leaf content.
#[derive(Debug, Clone)]
pub struct LeafWriteParams<'a> {
    pub leaf_count: usize,
    pub leaf_content: XmlElement,
    pub strategy: &'a str,
    pub key_order: Vec<String>,
    pub options: LeafWriteOptions<'a>,
}

#[derive(Debug, Clone)]
pub struct LeafWriteOptions<'a> {
    pub disassembled_path: &'a str,
    pub output_file_name: &'a str,
    pub root_element_name: &'a str,
    pub root_attributes: XmlElement,
    pub xml_declaration: Option<XmlElement>,
    pub format: &'a str,
}

/// Rule for multi-level disassembly: which files to further disassemble and how.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct MultiLevelRule {
    /// File name pattern (e.g. "programProcesses-meta"); any XML file whose name contains this is processed.
    pub file_pattern: String,
    /// Root element to strip (e.g. "LoyaltyProgramSetup"); its inner content becomes the new document.
    pub root_to_strip: String,
    /// Comma-separated unique-id elements for the second-level disassembly (e.g. "parameterName,ruleName").
    pub unique_id_elements: String,
    /// Path segment under the disassembly root for reassembly (e.g. "programProcesses").
    #[serde(default)]
    pub path_segment: String,
    /// Root element name to wrap reassembled files with (defaults to root_to_strip).
    #[serde(default)]
    pub wrap_root_element: String,
    /// xmlns value for the wrap root (optional; captured from original when stripping).
    #[serde(default)]
    pub wrap_xmlns: String,
}

/// Persisted config for multi-level reassembly (stored as .multi_level.json in the disassembly root).
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
pub struct MultiLevelConfig {
    pub rules: Vec<MultiLevelRule>,
}

/// A rule that extracts an XML element's text content to a typed companion
/// ("sidecar") file during disassembly, and injects it back during reassembly.
///
/// **Why:** XML formats sometimes embed non-XML blobs — OpenAPI YAML, WSDL,
/// JSON schemas — as escaped text inside a single element. Extracting those
/// blobs to their own typed files lets native tooling (formatters, linters,
/// IDE plugins) operate on them directly, keeps large content changes out of
/// the XML diff, and makes the surrounding XML easier to read and review. The
/// roundtrip is lossless: reassembly reads the sidecar and injects the raw
/// content back before writing the final XML.
///
/// **Format:** `element_name:extension`, e.g. `"schema:yaml"`. Multiple specs
/// are passed as a slice; use comma separation at the CLI/NAPI layer when
/// parsing from a string.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct SidecarSpec {
    /// Name of the XML element whose text content is extracted.
    pub element: String,
    /// File extension for the sidecar file (without leading dot), e.g. `"yaml"`.
    pub extension: String,
    /// Format of the content as it existed inside the XML before extraction
    /// ("json" or "yaml"). Detected at disassembly time and stored in
    /// `.sidecars.json` so reassembly can convert the sidecar back to its
    /// original format before injecting it into the XML. `None` means the
    /// content was not recognised as JSON or YAML (raw pass-through).
    #[serde(default)]
    pub original_format: Option<String>,
}