amoeba 0.1.0

A lightweight HTTP API library for Rust
Documentation
use crate::{ContentType, FromBytes, HttpError, IntoBytes};

mod lexer;
pub mod parser;

pub enum JsonValue {
    JsonNull,
    JsonBool(bool),
    JsonChar(char),
    JsonUint(u64),
    JsonInt(i64),
    JsonFloat(f64),
    JsonString(String),
    JsonList(Vec<JsonValue>),
    JsonObject(Vec<(String, JsonValue)>),
}

impl JsonValue {
    pub fn to_string(&self) -> String {
        match self {
            JsonValue::JsonNull => "null".to_string(),
            JsonValue::JsonBool(b) => b.to_string(),
            JsonValue::JsonChar(c) => format!("\"{}\"", c),
            JsonValue::JsonUint(u) => u.to_string(),
            JsonValue::JsonInt(i) => i.to_string(),
            JsonValue::JsonFloat(f) => f.to_string(),
            JsonValue::JsonString(s) => format!("\"{}\"", s),
            JsonValue::JsonList(v) => {
                let mut res = String::from("[");
                let mut iter = v.iter().peekable();

                loop {
                    if let Some(json_value) = iter.next() {
                        res.push_str(&json_value.to_string());

                        if iter.peek().is_some() {
                            res.push_str(", ");
                        }
                    } else {
                        break;
                    }
                }

                res.push(']');
                res
            }
            JsonValue::JsonObject(fields) => {
                let mut res = String::from("{");
                let mut iter = fields.iter().peekable();

                loop {
                    if let Some((key, json_value)) = iter.next() {
                        let s = format!("\"{}\": {}", key, json_value.to_string());
                        res.push_str(&s);

                        if iter.peek().is_some() {
                            res.push_str(", ");
                        }
                    } else {
                        break;
                    }
                }
                res.push('}');
                res
            }
        }
    }
}

pub trait FromJson {
    fn from_json(json: JsonValue) -> Self;
}

pub trait IntoJson {
    fn into_json(self) -> JsonValue;
}

impl FromBytes for JsonValue {
    fn from_bytes(bytes: Vec<u8>) -> Result<Self, HttpError> {
        let Ok(s) = String::from_utf8(bytes) else {
            return Err(HttpError::new(400, "request body is not valid utf-8"));
        };
        parser::parse(&s).map_err(|e| HttpError::new(400, &e))
    }
}

impl IntoBytes for JsonValue {
    fn into_bytes(self) -> Vec<u8> {
        self.to_string().into_bytes()
    }
}

pub struct Json<T>(pub T);

impl<T: FromJson> FromBytes for Json<T> {
    fn from_bytes(bytes: Vec<u8>) -> Result<Self, HttpError> {
        Ok(Json(T::from_json(JsonValue::from_bytes(bytes)?)))
    }
}

impl<T: IntoJson> IntoBytes for Json<T> {
    fn into_bytes(self) -> Vec<u8> {
        let Json(body) = self;
        body.into_json().into_bytes()
    }
}

impl<T> ContentType for Json<T> {
    fn content_type(&self) -> &'static str {
        "application/json; charset=utf-8"
    }
}

impl IntoJson for String {
    fn into_json(self) -> JsonValue {
        JsonValue::JsonString(self)
    }
}