amoeba 0.1.0

A lightweight HTTP API library for Rust
Documentation
use amoeba::{
    FromBytes, FromJson, HttpError, IntoJson, Json, JsonValue, Request, Response, Route, Server,
};

fn take_field(fields: &mut Vec<(String, JsonValue)>, key: &str) -> JsonValue {
    let pos = fields
        .iter()
        .position(|(k, _)| k == key)
        .unwrap_or_else(|| panic!("missing field: {key}"));
    fields.remove(pos).1
}

struct Car {
    brand: String,
    model: String,
    doors: u8,
    year: u16,
}

impl IntoJson for Car {
    fn into_json(self) -> JsonValue {
        JsonValue::JsonObject(vec![
            ("brand".into(), JsonValue::JsonString(self.brand)),
            ("model".into(), JsonValue::JsonString(self.model)),
            ("doors".into(), JsonValue::JsonUint(self.doors.into())),
            ("year".into(), JsonValue::JsonUint(self.year.into())),
        ])
    }
}

impl FromJson for Car {
    fn from_json(json: JsonValue) -> Self {
        let JsonValue::JsonObject(mut fields) = json else {
            panic!("expected object for Car");
        };
        let JsonValue::JsonString(brand) = take_field(&mut fields, "brand") else {
            panic!("expected string for Car.brand");
        };
        let JsonValue::JsonString(model) = take_field(&mut fields, "model") else {
            panic!("expected string for Car.model");
        };
        let JsonValue::JsonUint(doors) = take_field(&mut fields, "doors") else {
            panic!("expected uint for Car.doors");
        };
        let JsonValue::JsonUint(year) = take_field(&mut fields, "year") else {
            panic!("expected uint for Car.year");
        };
        Car {
            brand,
            model,
            doors: doors as u8,
            year: year as u16,
        }
    }
}

struct Person {
    name: String,
    age: u64,
    gender: String,
    occupation: String,
    car: Car,
}

impl IntoJson for Person {
    fn into_json(self) -> JsonValue {
        JsonValue::JsonObject(vec![
            ("name".into(), JsonValue::JsonString(self.name)),
            ("age".into(), JsonValue::JsonUint(self.age)),
            ("gender".into(), JsonValue::JsonString(self.gender)),
            ("occupation".into(), JsonValue::JsonString(self.occupation)),
            ("car".into(), self.car.into_json()),
        ])
    }
}

impl FromJson for Person {
    fn from_json(json: JsonValue) -> Self {
        let JsonValue::JsonObject(mut fields) = json else {
            panic!("expected object for Person");
        };
        let JsonValue::JsonString(name) = take_field(&mut fields, "name") else {
            panic!("expected string for Person.name");
        };
        let JsonValue::JsonUint(age) = take_field(&mut fields, "age") else {
            panic!("expected uint for Person.age");
        };
        let JsonValue::JsonString(gender) = take_field(&mut fields, "gender") else {
            panic!("expected string for Person.gender");
        };
        let JsonValue::JsonString(occupation) = take_field(&mut fields, "occupation") else {
            panic!("expected string for Person.occupation");
        };
        let car = Car::from_json(take_field(&mut fields, "car"));
        Person {
            name,
            age,
            gender,
            occupation,
            car,
        }
    }
}

fn get_person(_: &mut (), _: Request) -> Result<Response, HttpError> {
    Ok(Response::ok(Json(Person {
        name: "James Smith".into(),
        age: 30,
        gender: "Male".into(),
        occupation: "Blacksmith".into(),
        car: Car {
            brand: "Ford".into(),
            model: "Fiesta".into(),
            doors: 5,
            year: 2007,
        },
    })))
}

fn post_person(_: &mut (), req: Request) -> Result<Response, HttpError> {
    let body = req
        .body
        .ok_or_else(|| HttpError::new(400, "missing request body"))?;
    let Json(mut person) = Json::<Person>::from_bytes(body)?;
    person.name = "New John".into();
    Ok(Response::ok(Json(person)))
}

fn main() {
    Server::new("localhost:8080", ())
        .route(Route::new("GET", "/person", get_person))
        .route(Route::new("POST", "/person", post_person))
        .run();
}