sfv 0.14.0

Structured Field Values for HTTP parser. Implementation of RFC 8941 and RFC 9651.
Documentation
use std::fmt::Write as _;

use crate::{utils, Date, Decimal, Integer, KeyRef, RefBareItem, StringRef, TokenRef};

pub(crate) struct Serializer;

impl Serializer {
    pub(crate) fn serialize_bare_item<'b>(value: impl Into<RefBareItem<'b>>, output: &mut String) {
        // https://httpwg.org/specs/rfc9651.html#ser-bare-item

        match value.into() {
            RefBareItem::Boolean(value) => Self::serialize_bool(value, output),
            RefBareItem::String(value) => Self::serialize_string(value, output),
            RefBareItem::ByteSequence(value) => Self::serialize_byte_sequence(value, output),
            RefBareItem::Token(value) => Self::serialize_token(value, output),
            RefBareItem::Integer(value) => Self::serialize_integer(value, output),
            RefBareItem::Decimal(value) => Self::serialize_decimal(value, output),
            RefBareItem::Date(value) => Self::serialize_date(value, output),
            RefBareItem::DisplayString(value) => Self::serialize_display_string(value, output),
        }
    }

    pub(crate) fn serialize_parameter<'b>(
        name: &KeyRef,
        value: impl Into<RefBareItem<'b>>,
        output: &mut String,
    ) {
        // https://httpwg.org/specs/rfc9651.html#ser-params
        output.push(';');
        Self::serialize_key(name, output);

        let value = value.into();
        if value != RefBareItem::Boolean(true) {
            output.push('=');
            Self::serialize_bare_item(value, output);
        }
    }

    pub(crate) fn serialize_key(input_key: &KeyRef, output: &mut String) {
        // https://httpwg.org/specs/rfc9651.html#ser-key

        output.push_str(input_key.as_str());
    }

    pub(crate) fn serialize_integer(value: Integer, output: &mut String) {
        //https://httpwg.org/specs/rfc9651.html#ser-integer

        write!(output, "{value}").unwrap();
    }

    pub(crate) fn serialize_decimal(value: Decimal, output: &mut String) {
        // https://httpwg.org/specs/rfc9651.html#ser-decimal

        write!(output, "{value}").unwrap();
    }

    pub(crate) fn serialize_string(value: &StringRef, output: &mut String) {
        // https://httpwg.org/specs/rfc9651.html#ser-string

        output.push('"');
        for char in value.as_str().chars() {
            if char == '\\' || char == '"' {
                output.push('\\');
            }
            output.push(char);
        }
        output.push('"');
    }

    pub(crate) fn serialize_token(value: &TokenRef, output: &mut String) {
        // https://httpwg.org/specs/rfc9651.html#ser-token

        output.push_str(value.as_str());
    }

    pub(crate) fn serialize_byte_sequence(value: &[u8], output: &mut String) {
        // https://httpwg.org/specs/rfc9651.html#ser-binary

        output.push(':');
        base64::Engine::encode_string(&utils::BASE64, value, output);
        output.push(':');
    }

    pub(crate) fn serialize_bool(value: bool, output: &mut String) {
        // https://httpwg.org/specs/rfc9651.html#ser-boolean

        output.push_str(if value { "?1" } else { "?0" });
    }

    pub(crate) fn serialize_date(value: Date, output: &mut String) {
        // https://httpwg.org/specs/rfc9651.html#ser-date

        write!(output, "{value}").unwrap();
    }

    pub(crate) fn serialize_display_string(value: &str, output: &mut String) {
        // https://httpwg.org/specs/rfc9651.html#ser-display

        output.push_str(r#"%""#);
        for c in value.bytes() {
            match c {
                b'%' | b'"' | 0x00..=0x1f | 0x7f..=0xff => {
                    output.push('%');
                    output.push(char::from_digit((u32::from(c) >> 4) & 0xf, 16).unwrap());
                    output.push(char::from_digit(u32::from(c) & 0xf, 16).unwrap());
                }
                _ => output.push(c as char),
            }
        }
        output.push('"');
    }
}