superdoc-sdk 0.1.0-next.beta.1

Typed Rust client for the SuperDoc document runtime
Documentation
use crate::{
    generated,
    invoke::{Error, InvokeOptions, Invoker, Result},
};
use serde_json::Value;
use std::sync::Mutex;

#[derive(Default)]
struct Recorder(Mutex<Vec<(String, Value)>>);

impl Invoker for Recorder {
    async fn invoke(&self, operation_id: &str, params: Value, _: InvokeOptions) -> Result<Value> {
        self.0
            .lock()
            .unwrap()
            .push((operation_id.to_owned(), params));
        // The operation mapping, rather than synthetic result generation, is the
        // oracle here. Typed result semantics have separate fixture tests.
        Err(Error::new("RECORDED", "recorded invocation"))
    }
}

#[tokio::test]
async fn every_generated_callable_invokes_exactly_its_contract_operation() {
    let recorder = Recorder::default();
    generated::operations::exercise_callables(&recorder).await;
    let recorded = recorder.0.into_inner().unwrap();
    let contract: Value = serde_json::from_str(generated::CONTRACT_JSON).unwrap();
    for (id, params) in &recorded {
        let operation = &contract["operations"][id];
        let declared = operation["params"].as_array().unwrap();
        for field in params.as_object().unwrap().keys() {
            assert!(
                declared.iter().any(|param| param["name"] == *field),
                "{} sent undeclared parameter {}",
                id,
                field
            );
            if operation["sdkSurface"] == "document" {
                assert!(
                    !["doc", "sessionId"].contains(&field.as_str()),
                    "bound calls must inject session identity in their runtime"
                );
            }
        }
    }
    let actual: Vec<_> = recorded.into_iter().map(|(id, _)| id).collect();
    let mut expected: Vec<_> = contract["operations"]
        .as_object()
        .unwrap()
        .keys()
        .cloned()
        .collect();
    expected.sort();
    let mut sorted = actual.clone();
    sorted.sort();
    assert_eq!(
        sorted, expected,
        "each operation must be called exactly once"
    );
    let inventory: Vec<Value> = serde_json::from_str(generated::OPERATION_INVENTORY_JSON).unwrap();
    assert_eq!(
        actual,
        inventory
            .iter()
            .map(|row| row["operationId"].as_str().unwrap().to_owned())
            .collect::<Vec<_>>(),
        "each method must preserve its own operation identity"
    );
}