awaken-server-contract 0.6.0

Server and store boundary contracts for Awaken
Documentation
use super::*;

#[derive(Default)]
struct FakeRegistryStore {
    records: std::sync::Mutex<Vec<VersionedRecord<Value>>>,
}

#[async_trait]
impl VersionedRegistryStore for FakeRegistryStore {
    async fn resource_state(
        &self,
        _scope_id: &str,
        _kind: &str,
        _id: &str,
    ) -> Result<Option<VersionedResourceState>, VersionedRegistryError> {
        Ok(None)
    }

    async fn current(
        &self,
        _scope_id: &str,
        kind: &str,
        id: &str,
    ) -> Result<Option<VersionedRecord<Value>>, VersionedRegistryError> {
        Ok(self
            .records
            .lock()
            .map_err(|error| VersionedRegistryError::Backend(error.to_string()))?
            .iter()
            .rev()
            .find(|record| record.kind == kind && record.id == id)
            .cloned())
    }

    async fn get(
        &self,
        _scope_id: &str,
        kind: &str,
        id: &str,
        version: u64,
    ) -> Result<Option<VersionedRecord<Value>>, VersionedRegistryError> {
        Ok(self
            .records
            .lock()
            .map_err(|error| VersionedRegistryError::Backend(error.to_string()))?
            .iter()
            .find(|record| record.kind == kind && record.id == id && record.version == version)
            .cloned())
    }

    async fn list_versions(
        &self,
        _scope_id: &str,
        kind: &str,
        id: &str,
    ) -> Result<Vec<VersionedRecord<Value>>, VersionedRegistryError> {
        Ok(self
            .records
            .lock()
            .map_err(|error| VersionedRegistryError::Backend(error.to_string()))?
            .iter()
            .filter(|record| record.kind == kind && record.id == id)
            .cloned()
            .collect())
    }

    async fn publish_resource(
        &self,
        _scope_id: &str,
        kind: &str,
        id: &str,
        value: Value,
        value_schema_version: u32,
        metadata: Value,
    ) -> Result<PublishOutcome<Value>, VersionedRegistryError> {
        let (content_hash, bytes) = registry_content_hash(value_schema_version, &value)?;
        let mut records = self
            .records
            .lock()
            .map_err(|error| VersionedRegistryError::Backend(error.to_string()))?;
        let record = VersionedRecord {
            kind: kind.to_string(),
            id: id.to_string(),
            version: records.len() as u64 + 1,
            content_hash,
            value_schema_version,
            value,
            canonical_json_bytes: bytes,
            created_at_ms: 0,
            metadata,
        };
        records.push(record.clone());
        Ok(PublishOutcome::Created(record))
    }

    async fn rollback_resource(
        &self,
        _scope_id: &str,
        _kind: &str,
        _id: &str,
        _to_version: u64,
        _metadata: Value,
    ) -> Result<VersionedRecord<Value>, VersionedRegistryError> {
        Err(VersionedRegistryError::Backend(
            "not implemented in test".into(),
        ))
    }

    async fn archive_resource(
        &self,
        _scope_id: &str,
        _kind: &str,
        _id: &str,
    ) -> Result<(), VersionedRegistryError> {
        Ok(())
    }

    async fn unarchive_resource(
        &self,
        _scope_id: &str,
        _kind: &str,
        _id: &str,
    ) -> Result<(), VersionedRegistryError> {
        Ok(())
    }

    async fn create_publication(
        &self,
        _scope_id: &str,
        _publication_id: &str,
        _entries: Vec<VersionRef>,
        _source_config_revisions: Vec<ConfigRevisionRef>,
        _created_by: Option<String>,
        _metadata: Value,
    ) -> Result<RegistryPublication, VersionedRegistryError> {
        Err(VersionedRegistryError::Backend(
            "not implemented in test".into(),
        ))
    }

    async fn publish_resources_and_create_publication(
        &self,
        _scope_id: &str,
        _publication_id: &str,
        _resources: Vec<RegistryResourcePublish>,
        _source_config_revisions: Vec<ConfigRevisionRef>,
        _created_by: Option<String>,
        _metadata: Value,
    ) -> Result<RegistryPublication, VersionedRegistryError> {
        Err(VersionedRegistryError::Backend(
            "not implemented in test".into(),
        ))
    }

    async fn latest_publication(
        &self,
        _scope_id: &str,
    ) -> Result<Option<RegistryPublication>, VersionedRegistryError> {
        Ok(None)
    }

    async fn get_publication(
        &self,
        _scope_id: &str,
        _snapshot_version: u64,
    ) -> Result<Option<RegistryPublication>, VersionedRegistryError> {
        Ok(None)
    }
}

#[test]
fn typed_wrapper_can_bind_validated_scope_id() {
    let store = std::sync::Arc::new(FakeRegistryStore::default());
    let typed: ScopedVersionedRegistry<Value> = ScopedVersionedRegistry::new_scoped(
        store.clone(),
        ScopeId::new("scope-a").unwrap(),
        "tool",
    );

    assert_eq!(typed.scope_id(), "scope-a");
    assert!(matches!(
        ScopedVersionedRegistry::<Value>::try_new(store, " ", "tool"),
        Err(ScopeError::Empty)
    ));
}

#[tokio::test]
async fn typed_wrapper_rejects_incompatible_schema_versions() {
    let store = std::sync::Arc::new(FakeRegistryStore::default());
    let typed: TypedVersionedRegistry<Value> =
        TypedVersionedRegistry::new(store.clone(), "default", "tool")
            .with_supported_schema_versions([1, 2]);
    let err = typed
        .publish("t1", json!({}), 3, json!({}))
        .await
        .unwrap_err();
    assert!(matches!(
        err,
        VersionedRegistryError::IncompatibleSchema { stored: 3, .. }
    ));

    store
        .publish_resource("default", "tool", "t2", json!({"v": 1}), 1, json!({}))
        .await
        .unwrap();
    let typed: TypedVersionedRegistry<Value> =
        TypedVersionedRegistry::new(store, "default", "tool").with_supported_schema_versions([2]);
    let err = typed.get("t2", 1).await.unwrap_err();
    assert!(matches!(
        err,
        VersionedRegistryError::IncompatibleSchema { stored: 1, .. }
    ));
}