dat 4.7.1

DAT - Distributed Access Token
Documentation
use std::collections::BTreeMap;

#[derive(Debug)]
pub enum Json {
    Null,
    Bool,
    Number(String),
    String(String),
    Array(Vec<Json>),
    Object(BTreeMap<String, Json>),
}

impl Json {
    pub fn get(&self, key: &str) -> &Json {
        self.as_object()
            .get(key)
            .unwrap_or_else(|| panic!("missing JSON key {key:?}"))
    }

    pub fn as_object(&self) -> &BTreeMap<String, Json> {
        match self {
            Json::Object(value) => value,
            _ => panic!("expected JSON object, got {self:?}"),
        }
    }

    pub fn as_array(&self) -> &[Json] {
        match self {
            Json::Array(value) => value,
            _ => panic!("expected JSON array, got {self:?}"),
        }
    }

    pub fn as_str(&self) -> &str {
        match self {
            Json::String(value) | Json::Number(value) => value,
            _ => panic!("expected JSON string or number, got {self:?}"),
        }
    }

    pub fn as_u16(&self) -> u16 {
        self.as_str().parse().expect("expected JSON u16")
    }

    pub fn optional_str(&self) -> Option<&str> {
        match self {
            Json::Null => None,
            Json::String(value) => Some(value),
            _ => panic!("expected JSON string or null, got {self:?}"),
        }
    }
}

pub fn parse(input: &str) -> Json {
    let mut parser = Parser {
        input: input.as_bytes(),
        cursor: 0,
    };
    let value = parser.value();
    parser.whitespace();
    assert_eq!(parser.cursor, parser.input.len(), "trailing JSON data");
    value
}

struct Parser<'a> {
    input: &'a [u8],
    cursor: usize,
}

impl Parser<'_> {
    fn value(&mut self) -> Json {
        self.whitespace();
        match self.peek() {
            Some(b'{') => self.object(),
            Some(b'[') => self.array(),
            Some(b'"') => Json::String(self.string()),
            Some(b't') => {
                self.literal(b"true");
                Json::Bool
            }
            Some(b'f') => {
                self.literal(b"false");
                Json::Bool
            }
            Some(b'n') => {
                self.literal(b"null");
                Json::Null
            }
            Some(b'-' | b'0'..=b'9') => Json::Number(self.number()),
            byte => panic!("unexpected JSON byte {byte:?} at {}", self.cursor),
        }
    }

    fn object(&mut self) -> Json {
        self.take(b'{');
        let mut values = BTreeMap::new();
        self.whitespace();
        if self.consume(b'}') {
            return Json::Object(values);
        }
        loop {
            self.whitespace();
            let key = self.string();
            self.whitespace();
            self.take(b':');
            assert!(values.insert(key, self.value()).is_none());
            self.whitespace();
            if self.consume(b'}') {
                return Json::Object(values);
            }
            self.take(b',');
        }
    }

    fn array(&mut self) -> Json {
        self.take(b'[');
        let mut values = Vec::new();
        self.whitespace();
        if self.consume(b']') {
            return Json::Array(values);
        }
        loop {
            values.push(self.value());
            self.whitespace();
            if self.consume(b']') {
                return Json::Array(values);
            }
            self.take(b',');
        }
    }

    fn string(&mut self) -> String {
        self.take(b'"');
        let mut value = Vec::new();
        loop {
            match self.next().expect("unterminated JSON string") {
                b'"' => return String::from_utf8(value).expect("JSON string is UTF-8"),
                b'\\' => match self.next().expect("unterminated JSON escape") {
                    b'"' => value.push(b'"'),
                    b'\\' => value.push(b'\\'),
                    b'/' => value.push(b'/'),
                    b'b' => value.push(8),
                    b'f' => value.push(12),
                    b'n' => value.push(b'\n'),
                    b'r' => value.push(b'\r'),
                    b't' => value.push(b'\t'),
                    escape => panic!("unsupported JSON escape {escape:?}"),
                },
                byte if byte < 0x20 => panic!("control byte in JSON string"),
                byte => value.push(byte),
            }
        }
    }

    fn number(&mut self) -> String {
        let start = self.cursor;
        while matches!(
            self.peek(),
            Some(b'-' | b'+' | b'.' | b'e' | b'E' | b'0'..=b'9')
        ) {
            self.cursor += 1;
        }
        std::str::from_utf8(&self.input[start..self.cursor])
            .unwrap()
            .to_string()
    }

    fn literal(&mut self, literal: &[u8]) {
        assert_eq!(
            self.input.get(self.cursor..self.cursor + literal.len()),
            Some(literal)
        );
        self.cursor += literal.len();
    }

    fn whitespace(&mut self) {
        while matches!(self.peek(), Some(b' ' | b'\n' | b'\r' | b'\t')) {
            self.cursor += 1;
        }
    }

    fn peek(&self) -> Option<u8> {
        self.input.get(self.cursor).copied()
    }

    fn next(&mut self) -> Option<u8> {
        let value = self.peek()?;
        self.cursor += 1;
        Some(value)
    }

    fn take(&mut self, expected: u8) {
        assert_eq!(self.next(), Some(expected), "at byte {}", self.cursor);
    }

    fn consume(&mut self, expected: u8) -> bool {
        if self.peek() == Some(expected) {
            self.cursor += 1;
            true
        } else {
            false
        }
    }
}

pub fn expectation<'a>(case: &'a Json, profile: &str) -> &'a Json {
    let case = case.as_object();
    if let Some(expect) = case.get("expect") {
        expect
    } else {
        case.get("expect_by_profile")
            .expect("fixture case expectation")
            .get(profile)
    }
}

pub fn certificate_wire<'a>(fixture: &'a Json, name: &str) -> &'a str {
    fixture
        .get("certificates")
        .get(name)
        .get("wire_ascii")
        .as_str()
}

pub fn body(fixture: &Json, segments: &Json) -> Vec<u8> {
    let mut body = Vec::new();
    for segment in segments.as_array() {
        let segment = segment.as_array();
        assert_eq!(segment.len(), 2);
        match segment[0].as_str() {
            "ascii" => body.extend_from_slice(segment[1].as_str().as_bytes()),
            "certificate" => {
                body.extend_from_slice(certificate_wire(fixture, segment[1].as_str()).as_bytes())
            }
            "hex" => {
                let hex = segment[1].as_str().as_bytes();
                assert_eq!(hex.len() % 2, 0);
                for pair in hex.chunks_exact(2) {
                    body.push((hex_digit(pair[0]) << 4) | hex_digit(pair[1]));
                }
            }
            kind => panic!("unknown fixture body segment {kind:?}"),
        }
    }
    body
}

fn hex_digit(byte: u8) -> u8 {
    match byte {
        b'0'..=b'9' => byte - b'0',
        b'a'..=b'f' => byte - b'a' + 10,
        _ => panic!("invalid fixture hex digit"),
    }
}