html-generator 0.0.5

A robust Rust library designed for transforming Markdown into SEO-optimized, accessible HTML. Featuring front matter extraction, custom header processing, table of contents generation, and performance optimization for web projects of any scale.
Documentation
// Copyright © 2023 - 2026 Static Site Generator (SSG). All rights reserved.
// SPDX-License-Identifier: Apache-2.0 OR MIT

//! Serialize/deserialize enums as single-key YAML mappings.
//!
//! By default, serde serializes externally-tagged enums as
//! `variant_name` (unit) or `{variant: value}` (other). This
//! module forces *all* variants — including unit variants — to
//! use the single-key mapping form:
//!
//! ```yaml
//! variant_name: null   # unit variant
//! variant_name: value  # newtype/tuple/struct variant
//! ```
//!
//! # Usage
//!
//! ```ignore
//! #[derive(Serialize, Deserialize)]
//! struct Config {
//!     #[serde(with = "yaml_safe::with::singleton_map")]
//!     mode: Mode,
//! }
//! ```

use serde::{Deserialize, Deserializer, Serialize, Serializer};

use crate::yaml::error::Error;
use crate::yaml::mapping::Mapping;
use crate::yaml::value::Value;

/// Serialize an enum as a single-key mapping.
pub(crate) fn serialize<T, S>(
    value: &T,
    serializer: S,
) -> Result<S::Ok, S::Error>
where
    T: Serialize,
    S: Serializer,
{
    // Serialize to Value first, then convert unit variants
    let v = value
        .serialize(crate::yaml::value::ValueSerializer)
        .map_err(serde::ser::Error::custom)?;
    let mapped = to_singleton_map(v);
    mapped.serialize(serializer)
}

/// Deserialize an enum from a single-key mapping.
pub(crate) fn deserialize<'de, T, D>(
    deserializer: D,
) -> Result<T, D::Error>
where
    T: Deserialize<'de>,
    D: Deserializer<'de>,
{
    let v = Value::deserialize(deserializer)?;
    let converted = from_singleton_map(v);
    T::deserialize(converted).map_err(serde::de::Error::custom)
}

/// Convert a Value representing an enum to singleton-map form.
pub(crate) fn to_singleton_map(v: Value) -> Value {
    match v {
        // Unit variant: "VariantName" → {VariantName: null}
        Value::String(s) => {
            let mut m = Mapping::new();
            m.insert(Value::String(s), Value::Null);
            Value::Mapping(m)
        }
        // Already a mapping (newtype/tuple/struct variant)
        other => other,
    }
}

/// Convert a singleton-map Value back to enum form.
pub(crate) fn from_singleton_map(v: Value) -> Value {
    match v {
        Value::Mapping(ref m) if m.len() == 1 => {
            let (_, val) = m.iter().next().expect("len == 1");
            if val.is_null() {
                // Could be a unit variant: {Name: null} → "Name"
                // But it might also be a legitimate newtype with
                // null value. We leave it as mapping and let serde
                // decide — the enum deserializer handles both.
                v
            } else {
                v
            }
        }
        other => other,
    }
}

/// Applies singleton_map transformation to a Value, suitable
/// for use in `apply_to_value` patterns.
pub(crate) fn apply_to_value(v: &mut Value) -> Result<(), Error> {
    *v = to_singleton_map(std::mem::take(v));
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn from_singleton_map_passthrough_on_multi_key_mapping() {
        let mut m = Mapping::new();
        m.insert(Value::String("a".into()), Value::String("1".into()));
        m.insert(Value::String("b".into()), Value::String("2".into()));
        let v = Value::Mapping(m);
        let out = from_singleton_map(v.clone());
        // len != 1 → fall through to `other => other`.
        assert_eq!(out, v);
    }

    #[test]
    fn from_singleton_map_passthrough_on_non_mapping() {
        let v = Value::String("x".into());
        let out = from_singleton_map(v.clone());
        assert_eq!(out, v);
    }

    #[test]
    fn apply_to_value_wraps_string() {
        let mut v = Value::String("Variant".into());
        apply_to_value(&mut v).unwrap();
        let m = v.as_mapping().expect("wrapped as mapping");
        assert_eq!(m.len(), 1);
        let (k, val) = m.iter().next().unwrap();
        assert_eq!(k, &Value::String("Variant".into()));
        assert!(val.is_null());
    }

    #[test]
    fn apply_to_value_passes_non_string_through() {
        let mut v = Value::Null;
        apply_to_value(&mut v).unwrap();
        assert!(v.is_null());
    }
}