a3s-boot 0.1.3

Adapter-first modular Rust web framework for A3S inspired by Nest.js
Documentation
use super::request::BootRequest;
use crate::{BootError, Result};
use serde::de::DeserializeOwned;
use serde_json::Value;

impl BootRequest {
    pub fn body_field(&self, name: &str) -> Result<Option<Value>> {
        let body = self.json::<Value>()?;
        let Value::Object(fields) = body else {
            return Err(BootError::BadRequest(
                "expected JSON object body".to_string(),
            ));
        };

        Ok(fields.get(name).filter(|value| !value.is_null()).cloned())
    }

    pub fn body_field_as<T>(&self, name: &str) -> Result<T>
    where
        T: DeserializeOwned,
    {
        let Some(value) = self.body_field(name)? else {
            return Err(BootError::BadRequest(format!("missing body field: {name}")));
        };
        deserialize_body_field(name, value)
    }

    pub fn optional_body_field_as<T>(&self, name: &str) -> Result<Option<T>>
    where
        T: DeserializeOwned,
    {
        self.body_field(name)?
            .map(|value| deserialize_body_field(name, value))
            .transpose()
    }

    pub fn body_field_string(&self, name: &str) -> Result<String> {
        let Some(value) = self.body_field(name)? else {
            return Err(BootError::BadRequest(format!("missing body field: {name}")));
        };
        body_field_value_to_string(value)
    }

    pub fn optional_body_field_string(&self, name: &str) -> Result<Option<String>> {
        self.body_field(name)?
            .map(body_field_value_to_string)
            .transpose()
    }
}

fn deserialize_body_field<T>(name: &str, value: Value) -> Result<T>
where
    T: DeserializeOwned,
{
    serde_json::from_value(value)
        .map_err(|error| BootError::BadRequest(format!("invalid body field {name}: {error}")))
}

fn body_field_value_to_string(value: Value) -> Result<String> {
    match value {
        Value::String(value) => Ok(value),
        Value::Bool(value) => Ok(value.to_string()),
        Value::Number(value) => Ok(value.to_string()),
        Value::Array(_) | Value::Object(_) => {
            serde_json::to_string(&value).map_err(|error| BootError::BadRequest(error.to_string()))
        }
        Value::Null => Ok("null".to_string()),
    }
}