saddle-framework 0.3.0-alpha.7

The single business-facing facade for Saddle applications
use serde::Serialize;

use crate::profusegw_response::{ProfuseGwCode, ProfuseGwResponse};

const PREFIX: &[u8] = b"HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: ";
const SUFFIX: &[u8] = b"\r\nConnection: close\r\n\r\n";

/// Production-only projection from the framework's sole closed Profusegw
/// response into its fixed HTTP/1.1 carrier.
///
/// Legal Success and Failure values share HTTP 200. A response that cannot be
/// serialized is returned to the listener as a protocol error and is never
/// framed as a successful response.
///
/// A foreign serializable response cannot acquire this carrier:
///
/// ```compile_fail
/// use serde::Serialize;
/// #[derive(Serialize)]
/// struct ForeignResponse { success: bool }
/// let foreign = ForeignResponse { success: true };
/// let _ = saddle::__private::encode_profusegw_http1(&foreign);
/// ```
#[doc(hidden)]
pub fn encode_profusegw_http1<Data, Code>(
    response: &ProfuseGwResponse<Data, Code>,
) -> Result<Vec<u8>, serde_json::Error>
where
    Data: Serialize,
    Code: ProfuseGwCode,
{
    let body = serde_json::to_vec(response)?;
    let length = body.len().to_string();
    let mut framed = Vec::with_capacity(PREFIX.len() + length.len() + SUFFIX.len() + body.len());
    framed.extend_from_slice(PREFIX);
    framed.extend_from_slice(length.as_bytes());
    framed.extend_from_slice(SUFFIX);
    framed.extend_from_slice(&body);
    Ok(framed)
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::profusegw_response::{
        FailureMessage, ProfuseGwCode, ProfuseGwFailure, ProfuseGwResponse,
    };

    #[derive(Serialize)]
    struct Data {
        count: u64,
    }

    enum Code {
        Rejected,
    }

    impl ProfuseGwCode for Code {
        const REGISTERED_CODES: &'static [&'static str] = &["ACCOUNT_REJECTED"];

        fn stable_code(&self) -> &'static str {
            "ACCOUNT_REJECTED"
        }
    }

    #[test]
    fn legal_branches_share_exact_json_http_200_projection() {
        let success = ProfuseGwResponse::<_, Code>::success(Data { count: 7 });
        assert_eq!(
            encode_profusegw_http1(&success).unwrap(),
            b"HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: 35\r\nConnection: close\r\n\r\n{\"success\":true,\"data\":{\"count\":7}}"
        );

        let failure = ProfuseGwResponse::<Data, _>::failure(ProfuseGwFailure::new(
            Code::Rejected,
            FailureMessage::new("Account was rejected").unwrap(),
        ));
        assert_eq!(
            encode_profusegw_http1(&failure).unwrap(),
            b"HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: 88\r\nConnection: close\r\n\r\n{\"success\":false,\"failure\":{\"code\":\"ACCOUNT_REJECTED\",\"message\":\"Account was rejected\"}}"
        );
    }
}