saddle-framework 0.3.36

The single business-facing facade for Saddle applications
//! Generated input-codec consumer fixture. Admission below only supplies the
//! test lifecycle. The second case consumes the actual managed boundary and
//! Facade decoder; TCP listener coverage remains a separate fixture.
use prost::Message;
use saddle::Input;
use serde::{Deserialize, Serialize};
#[derive(Input, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
struct InputRequest {
    display_name: String,
    rows: Vec<Vec<Option<String>>>,
    choice: Choice,
    missing: Option<String>,
}
#[derive(Input, Deserialize)]
#[serde(rename_all = "lowercase")]
enum Choice {
    Left,
    Right,
}
#[derive(Serialize)]
struct UnchangedResponse {
    name: String,
}
#[derive(Clone, PartialEq, Message)]
struct UnchangedProto {
    #[prost(string, tag = "1")]
    name: String,
}
fn main() {}

#[cfg(test)]
mod tests {
    use super::*;
    use saddle_admission::{ProcessLedger, ResourceConfig};
    use std::{
        future::Future,
        task::{Context, Poll, Waker},
    };

    async fn handler(request: &InputRequest) {
        let before = request.display_name.as_str();
        let mut first = true;
        std::future::poll_fn(move |_| {
            if first {
                first = false;
                Poll::Pending
            } else {
                Poll::Ready(())
            }
        })
        .await;
        assert_eq!(before, "read\n中😀");
        assert_eq!(request.rows[0][0].as_deref(), Some("a"));
        assert!(request.rows[0][1].is_none());
        assert!(request.missing.is_none());
        assert!(matches!(request.choice, Choice::Right));
    }
    fn ledger() -> ProcessLedger {
        let state = ProcessLedger::minimum_process_state_reserve(1).unwrap();
        ProcessLedger::new(ResourceConfig {
            managed_capacity: 32768,
            entry_reserve: 64,
            framework_reserve: 16384,
            task_reserve: 8192,
            process_state_reserve: state,
            system_estimate: 64,
            safety_margin: 64,
            process_limit: 32768 + 64 + 16384 + 8192 + state + 64 + 64,
            max_active_requests: 1,
        })
        .unwrap()
    }
    #[test]
    fn generated_codec_borrows_across_await_and_cancel() {
        for cancel in [false, true] {
            let ledger = ledger();
            let request=ledger.try_envelope(32768,8192,|memory| {
                let raw=memory.try_bytes(br#"{"displayName":"read\n\u4e2d\ud83d\ude00","rows":[["a",null]],"choice":"right"}"#).unwrap();
                let document=memory.decode_input(&raw).unwrap();
                let input=memory.read_only_input::<InputRequest>(document.root()).unwrap();
                // The DTO owns its independent strings/arrays. Neither the
                // source document nor raw bytes are needed across the await.
                drop(document); drop(raw);
                async move { handler(input.get()).await; }
            }).unwrap();
            let mut request = Box::pin(request);
            let mut cx = Context::from_waker(Waker::noop());
            assert!(request.as_mut().poll(&mut cx).is_pending());
            assert!(ledger.snapshot().charged > 0);
            if !cancel {
                assert!(matches!(request.as_mut().poll(&mut cx), Poll::Ready(Ok(_))));
            }
            drop(request);
            assert_eq!(ledger.snapshot().charged, 0);
            assert_eq!(ledger.snapshot().active_accounts, 0);
            assert!(ledger.snapshot().healthy);
            ledger.try_shutdown().unwrap();
        }
    }
    #[test]
    fn managed_boundary_to_typed_borrow_and_cancel() {
        use saddle_boundary::ingress::*;
        for cancel in [false,true] {
            let ledger=ledger();
            let identity=IngressIdentity::new("request","call",1_800_000_000_000).unwrap();
            let adapter=ProfuseGwListenerAdapter::new("app").unwrap();
            let request=ledger.try_envelope(32768,8192,|memory| {
                let source=br#"HEAD{"target":{"app":"app","interfaceId":"route"},"profuseGwContext":{"userInfo":{"userId":"u"},"traceInfo":{"rpcId":"0"},"ldcInfo":{"zone":"z","idc":"i","env":"test"}},"requestData":{"displayName":"read\n\u4e2d\ud83d\ude00","rows":[["a",null]],"choice":"right"}}"#;
                let raw=memory.try_bytes(source).unwrap();
                let document=memory.decode_input_range(&raw,4..raw.len()).unwrap();
                let accepted=adapter.accept_managed(METHOD,PATH,MEDIA_TYPE,&identity,&raw.as_slice()[4..],document).unwrap();
                assert_eq!(accepted.trace_id,"saddle-request");
                assert!(matches!(&accepted.request_data,AcceptedRequestData::Managed(_)));
                drop(raw);
                let (input,context)=saddle::__private::decode_accepted_profusegw::<InputRequest>(accepted).unwrap();
                assert_eq!(context.trace_info().trace_id(),"saddle-request");
                async move { handler(input.get()).await; }
            }).unwrap();
            let mut request=Box::pin(request);
            let mut cx=Context::from_waker(Waker::noop());
            assert!(request.as_mut().poll(&mut cx).is_pending());
            assert!(ledger.snapshot().charged>0);
            if !cancel { assert!(matches!(request.as_mut().poll(&mut cx),Poll::Ready(Ok(_)))); }
            drop(request);
            assert_eq!(ledger.snapshot().charged,0);
            assert_eq!(ledger.snapshot().active_accounts,0);
            assert!(ledger.snapshot().healthy);
            ledger.try_shutdown().unwrap();
        }
    }

}