cloud-sdk 0.38.0

no_std-first provider-neutral cloud SDK foundations.
Documentation
use super::{
    ContentTypePolicy, RequestIdPolicy, ResponseBodyPolicy, ResponsePolicy, ResponsePolicyError,
};
use crate::Method;
use crate::transport::{
    BlockingTransport, HeaderSensitivity, MediaType, RequestTarget, ResponseBuffer,
    ResponseMetadata, ResponseWriter, StatusCode, TransportRequest,
};

static OK_STATUS: [StatusCode; 1] = [StatusCode::OK];
static JSON_MEDIA: [MediaType<'static>; 1] = [MediaType::JSON];

#[test]
fn response_policy_classifies_every_rejection_before_decoding() {
    let required = json_response_policy(4);
    assert!(required.is_ok());
    let Ok(required) = required else { return };
    let status = StatusCode::new(201).unwrap_or(StatusCode::OK);
    assert!(matches!(
        validate_fixture(required, status, b"{}", None),
        Err(ResponsePolicyError::UnexpectedStatus)
    ));
    assert!(matches!(
        validate_fixture(required, StatusCode::OK, b"12345", None),
        Err(ResponsePolicyError::BodyTooLarge)
    ));
    assert!(matches!(
        validate_fixture(required, StatusCode::OK, b"", None),
        Err(ResponsePolicyError::MissingBody)
    ));
    assert!(matches!(
        validate_fixture(required, StatusCode::OK, b"{}", None),
        Err(ResponsePolicyError::MissingContentType)
    ));
    assert!(matches!(
        validate_fixture(required, StatusCode::OK, b"{}", Some(b"text/plain")),
        Err(ResponsePolicyError::UnexpectedContentType)
    ));
    assert_eq!(
        validate_fixture(
            required,
            StatusCode::OK,
            b"{}",
            Some(b"application/json; charset=utf-8"),
        ),
        Ok(2)
    );

    let forbidden = ResponsePolicy::new(
        &OK_STATUS,
        ContentTypePolicy::Forbidden,
        ResponseBodyPolicy::Forbidden,
        0,
    );
    assert!(forbidden.is_ok());
    if let Ok(forbidden) = forbidden {
        assert!(matches!(
            validate_fixture(forbidden, StatusCode::OK, b"x", None),
            Err(ResponsePolicyError::ForbiddenBody)
        ));
        assert!(matches!(
            validate_fixture(forbidden, StatusCode::OK, b"", Some(b"application/json")),
            Err(ResponsePolicyError::ForbiddenContentType)
        ));
        for malformed in [
            b"application/json; charset".as_slice(),
            b"application/json\xff".as_slice(),
        ] {
            assert!(matches!(
                validate_fixture(forbidden, StatusCode::OK, b"", Some(malformed)),
                Err(ResponsePolicyError::InvalidContentType)
            ));
        }
    }

    let optional = ResponsePolicy::new(
        &OK_STATUS,
        ContentTypePolicy::Optional(&JSON_MEDIA),
        ResponseBodyPolicy::Optional,
        4,
    );
    assert!(optional.is_ok());
    if let Ok(optional) = optional {
        for malformed in [
            b"application/json; charset".as_slice(),
            b"application/json\xff".as_slice(),
        ] {
            assert!(matches!(
                validate_fixture(optional, StatusCode::OK, b"{}", Some(malformed)),
                Err(ResponsePolicyError::InvalidContentType)
            ));
        }
    }
}

fn json_response_policy(
    max_body_bytes: usize,
) -> Result<ResponsePolicy, super::ResponsePolicyValidationError> {
    ResponsePolicy::new(
        &OK_STATUS,
        ContentTypePolicy::Required(&JSON_MEDIA),
        ResponseBodyPolicy::Required,
        max_body_bytes,
    )
}

fn validate_fixture(
    policy: ResponsePolicy,
    status: StatusCode,
    body: &[u8],
    content_type: Option<&[u8]>,
) -> Result<usize, ResponsePolicyError> {
    let mut storage = [0_u8; 32];
    let mut header_storage = [0_u8; 8192];
    let mut response = ResponseBuffer::new(&mut storage, 32, &mut header_storage);
    let target = RequestTarget::new("/").map_err(|_| ResponsePolicyError::UncommittedResponse)?;
    let transport = FixtureTransport {
        status,
        body,
        content_type,
    };
    BlockingTransport::send(
        &transport,
        TransportRequest::new(Method::Get, target),
        response.writer(),
    )
    .map_err(|_| ResponsePolicyError::UncommittedResponse)?;
    policy
        .validate(response, RequestIdPolicy::Discard)
        .map(|checked| checked.with_borrowed(|view| view.body().len()))
}

struct FixtureTransport<'a> {
    status: StatusCode,
    body: &'a [u8],
    content_type: Option<&'a [u8]>,
}

impl BlockingTransport for FixtureTransport<'_> {
    type Error = ();

    fn send(
        &self,
        _request: TransportRequest<'_>,
        response: &mut ResponseWriter<'_>,
    ) -> Result<(), Self::Error> {
        if let Some(content_type) = self.content_type {
            response
                .headers_mut()
                .map_err(|_| ())?
                .try_push("content-type", content_type, HeaderSensitivity::Public)
                .map_err(|_| ())?;
        }
        response
            .body_mut()
            .map_err(|_| ())?
            .get_mut(..self.body.len())
            .ok_or(())?
            .copy_from_slice(self.body);
        response
            .commit(self.status, self.body.len(), ResponseMetadata::EMPTY)
            .map_err(|_| ())
    }
}