pipa-lang 1.0.0-alpha.1

A tiny template language
Documentation
// SPDX-FileCopyrightText: Copyright 2026 olav@occy.org
// SPDX-License-Identifier: MPL-2.0

//! Format values as JSON or YAML.

use crate::serde::byte_keys_as_strings;
use crate::serde::bytes_as_string;
use crate::value::Value;
use crate::value::error::Error;
use crate::value::list::List;
use crate::value::map::Map;
use indexmap::IndexMap;
use serde::Serialize;

pub fn json(value: Value) -> Result<Vec<u8>, Error> {
    serde_json::to_vec(&Strategy::new(value)).map_err(|e| Error::Format(e.to_string()))
}

pub fn json_pretty(value: Value) -> Result<Vec<u8>, Error> {
    serde_json::to_vec_pretty(&Strategy::new(value)).map_err(|e| Error::Format(e.to_string()))
}

pub fn json_string(value: Value) -> Result<String, Error> {
    serde_json::to_string(&Strategy::new(value)).map_err(|e| Error::Format(e.to_string()))
}

pub fn json_string_pretty(value: Value) -> Result<String, Error> {
    serde_json::to_string_pretty(&Strategy::new(value)).map_err(|e| Error::Format(e.to_string()))
}

pub fn yaml(value: Value) -> Result<String, Error> {
    serde_yaml::to_string(&Strategy::new(value)).map_err(|e| Error::Format(e.to_string()))
}

#[derive(Serialize)]
#[serde(untagged)]
enum Strategy {
    Default(Box<Value>),
    Simple(Simple),
}

impl Strategy {
    fn new(value: Value) -> Strategy {
        match value.meta().get(b"internal-serialize") == Some(b"simple") {
            true => Strategy::Simple(Simple::new(value)),
            false => Strategy::Default(Box::new(value)),
        }
    }
}

#[derive(Serialize)]
#[serde(untagged)]
enum Simple {
    Bytes(SimpleBytes),
    List(SimpleList),
    Map(SimpleMap),
}

impl Simple {
    fn new(value: Value) -> Self {
        match value {
            Value::List(v) if should_concat(&value) => Simple::Bytes(SimpleBytes(v.join())),
            Value::Map(v) if should_concat(&value) => Simple::Bytes(SimpleBytes(v.join())),
            Value::Bytes(v) => Simple::Bytes(SimpleBytes(v.join())),
            Value::List(v) => Simple::List(SimpleList::new(v)),
            Value::Map(v) => Simple::Map(SimpleMap::new(v)),
        }
    }
}

#[derive(Serialize)]
struct SimpleBytes(#[serde(with = "bytes_as_string")] Vec<u8>);

#[derive(Serialize)]
struct SimpleList(Vec<Simple>);

impl SimpleList {
    fn new(value: List) -> Self {
        SimpleList(value.data.into_iter().map(Simple::new).collect())
    }
}

#[derive(Serialize)]
pub(crate) struct SimpleMap(
    #[serde(with = "byte_keys_as_strings")] IndexMap<Vec<u8>, Simple, ahash::RandomState>,
);

impl SimpleMap {
    fn new(value: Map) -> Self {
        SimpleMap(
            value
                .data
                .into_iter()
                .map(|(k, v)| (k, Simple::new(v)))
                .collect(),
        )
    }
}

fn should_concat(value: &Value) -> bool {
    value.meta().get(b"internal-format") == Some(b"concat")
}