armour-core 0.1.7

Core types for armour ecosystem
Documentation
//! Leak-based `Deserialize` impls for [`Typ`], [`KeyScheme`], and related types.
//!
//! # Warning
//!
//! Every call to `deserialize` leaks memory proportional to the size of the
//! decoded schema (strings and field arrays become `&'static` via [`Box::leak`]).
//! Safe for CLI tools and short-lived clients that deserialize a bounded set
//! of schemas per process. **Do not use in long-running servers that parse
//! untrusted or unbounded JSON schemas.**

use serde::Deserialize;

use crate::key_type::{KeyScheme, KeyType};
use crate::typ::{EnumType, Fields, NamedField, SimpleEnumType, StructType, Typ};

fn leak_str(s: String) -> &'static str {
    Box::leak(s.into_boxed_str())
}

fn leak_slice<T>(v: Vec<T>) -> &'static [T] {
    Box::leak(v.into_boxed_slice())
}

// --- Typ ---

#[derive(Deserialize)]
#[serde(tag = "type", content = "data")]
enum TypWire {
    Bool,
    U8,
    U16,
    U32,
    U64,
    I32,
    I64,
    F32,
    F64,
    Str,
    Datetime,
    Timestamp,
    Decimal,
    Id32,
    Id64,
    Fuid,
    LowId,
    Bytes,
    ArrayBytes(u32),
    Array(u32, Box<TypWire>),
    Vec(Box<TypWire>),
    Optional(Box<TypWire>),
    SimpleEnum(SimpleEnumWire),
    Struct(StructWire),
    Enum(EnumWire),
    Void,
    Json,
    JsonBytes,
    Custom(String, Vec<TypWire>),
}

#[derive(Deserialize)]
struct StructWire {
    name: String,
    fields: FieldsWire,
}

#[derive(Deserialize)]
#[serde(tag = "type", content = "data")]
enum FieldsWire {
    Named(Vec<(String, TypWire)>),
    Unnamed(Vec<TypWire>),
}

#[derive(Deserialize)]
struct EnumWire {
    name: String,
    variants: serde_json::Map<String, serde_json::Value>,
}

#[derive(Deserialize)]
struct SimpleEnumWire {
    name: String,
    variants: serde_json::Map<String, serde_json::Value>,
}

fn wire_to_typ(w: TypWire) -> Typ {
    match w {
        TypWire::Bool => Typ::Bool,
        TypWire::U8 => Typ::U8,
        TypWire::U16 => Typ::U16,
        TypWire::U32 => Typ::U32,
        TypWire::U64 => Typ::U64,
        TypWire::I32 => Typ::I32,
        TypWire::I64 => Typ::I64,
        TypWire::F32 => Typ::F32,
        TypWire::F64 => Typ::F64,
        TypWire::Str => Typ::Str,
        TypWire::Datetime => Typ::Datetime,
        TypWire::Timestamp => Typ::Timestamp,
        TypWire::Decimal => Typ::Decimal,
        TypWire::Id32 => Typ::Id32,
        TypWire::Id64 => Typ::Id64,
        TypWire::Fuid => Typ::Fuid,
        TypWire::LowId => Typ::LowId,
        TypWire::Bytes => Typ::Bytes,
        TypWire::ArrayBytes(n) => Typ::ArrayBytes(n),
        TypWire::Array(n, inner) => Typ::Array(n, Box::leak(Box::new(wire_to_typ(*inner)))),
        TypWire::Vec(inner) => Typ::Vec(Box::leak(Box::new(wire_to_typ(*inner)))),
        TypWire::Optional(inner) => Typ::Optional(Box::leak(Box::new(wire_to_typ(*inner)))),
        TypWire::SimpleEnum(SimpleEnumWire { name, variants }) => {
            let v: Vec<(u8, &'static str)> = variants
                .into_iter()
                .map(|(k, value)| {
                    let tag: u8 = k.parse().expect("SimpleEnum variant key must be u8");
                    let name: &'static str = match value {
                        serde_json::Value::String(s) => leak_str(s),
                        _ => panic!("SimpleEnum variant value must be string"),
                    };
                    (tag, name)
                })
                .collect();
            Typ::SimpleEnum(SimpleEnumType {
                name: leak_str(name),
                variants: leak_slice(v),
            })
        }
        TypWire::Struct(StructWire { name, fields }) => Typ::Struct(StructType {
            name: leak_str(name),
            fields: wire_to_fields(fields),
        }),
        TypWire::Enum(EnumWire { name, variants }) => {
            let v: Vec<(u8, NamedField)> = variants
                .into_iter()
                .map(|(k, value)| {
                    let tag: u8 = k.parse().expect("Enum variant key must be u8");
                    let pair: (String, TypWire) =
                        serde_json::from_value(value).expect("Enum variant value shape");
                    (tag, (leak_str(pair.0), wire_to_typ(pair.1)))
                })
                .collect();
            Typ::Enum(EnumType {
                name: leak_str(name),
                variants: leak_slice(v),
            })
        }
        TypWire::Void => Typ::Void,
        TypWire::Json => Typ::RustJson,
        TypWire::JsonBytes => Typ::JsonBytes,
        TypWire::Custom(name, args) => {
            let args: Vec<Typ> = args.into_iter().map(wire_to_typ).collect();
            Typ::Custom(leak_str(name), leak_slice(args))
        }
    }
}

fn wire_to_fields(f: FieldsWire) -> Fields {
    match f {
        FieldsWire::Named(v) => {
            let arr: Vec<NamedField> = v
                .into_iter()
                .map(|(n, t)| (leak_str(n), wire_to_typ(t)))
                .collect();
            Fields::Named(leak_slice(arr))
        }
        FieldsWire::Unnamed(v) => {
            let arr: Vec<Typ> = v.into_iter().map(wire_to_typ).collect();
            Fields::Unnamed(leak_slice(arr))
        }
    }
}

impl<'de> Deserialize<'de> for Typ {
    fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
        let w = TypWire::deserialize(d)?;
        Ok(wire_to_typ(w))
    }
}

// --- KeyScheme / KeyType ---
//
// `KeyType` already derives `Deserialize` (it is `Copy` with no borrowed data).
// `KeyScheme::Typed(&'static [KeyType])` needs a manual impl that leaks the
// parsed `Vec<KeyType>` into a `'static` slice.

#[derive(Deserialize)]
#[serde(tag = "type", content = "data")]
enum KeySchemeWire {
    Typed(Vec<KeyType>),
    Bytes,
}

impl<'de> Deserialize<'de> for KeyScheme {
    fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
        let w = KeySchemeWire::deserialize(d)?;
        Ok(match w {
            KeySchemeWire::Typed(v) => KeyScheme::Typed(leak_slice(v)),
            KeySchemeWire::Bytes => KeyScheme::Bytes,
        })
    }
}