spiral-sdk 0.1.8

Rust SDK for Spiral resource and table APIs
Documentation
//! Storage transports selected by the execution host.

use std::fmt::{self, Debug, Formatter};

use serde::{Deserialize, Serialize};

/// Resolved storage configuration, serialized across the lockstep native boundary.
///
/// This describes storage I/O, not resource lookup or publication. The runtime
/// owns transport policy and instrumentation. Additional transports can extend
/// this contract without exposing storage callbacks to SDK consumers.
#[derive(Clone, Serialize, Deserialize)]
#[serde(tag = "backend", rename_all = "snake_case", deny_unknown_fields)]
pub enum StorageConfig {
    /// Explicit local files. The host is responsible for authorizing this root.
    Local {
        /// Absolute `file://` directory URI, ending in `/`.
        root_uri: String,
    },
    /// Object storage accessed through the SPFS signing transport.
    Spfs {
        /// Plain HTTP(S) URL of the storage shim, not a control-plane endpoint.
        endpoint: String,
        /// Authorized `spfsm://` or `spfsu://` mount root, or a directory URI ending in `/`.
        root_uri: String,
        /// Initial operation-scoped credential. Renewal belongs to the host.
        credential: String,
        /// Host-resolved alias of the same root for Table-owned blob references.
        #[serde(default, skip_serializing_if = "Option::is_none")]
        root_alias: Option<String>,
    },
    /// Object storage at an S3 provider, accessed with a session the host vended for this root.
    S3 {
        /// Authorized `s3://bucket/prefix/` root, ending in `/`.
        root_uri: String,
        /// Initial session, as JSON [`S3Session`]. Renewals return the same shape.
        credential: String,
        /// The root's SPFS address, which the table's object references name.
        #[serde(default, skip_serializing_if = "Option::is_none")]
        root_alias: Option<String>,
    },
    /// Object storage in Google Cloud Storage, accessed with an access token the host vended for
    /// this root.
    Gcs {
        /// Authorized `gs://bucket/prefix/` root, ending in `/`.
        root_uri: String,
        /// Initial token, as JSON [`GcsSession`]. Renewals return the same shape.
        credential: String,
        /// The root's SPFS address, which the table's object references name.
        #[serde(default, skip_serializing_if = "Option::is_none")]
        root_alias: Option<String>,
    },
}

/// A short-lived Cloud Storage access token scoped to one root, as the host renews it.
#[derive(Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct GcsSession {
    pub access_token: String,
    /// When the token stops working, in Unix microseconds.
    pub expires_at_micros: i64,
}

impl Debug for GcsSession {
    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
        formatter
            .debug_struct("GcsSession")
            .field("expires_at_micros", &self.expires_at_micros)
            .finish_non_exhaustive()
    }
}

/// A short-lived S3 session scoped to one root, as the host renews it.
#[derive(Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct S3Session {
    pub access_key_id: String,
    pub secret_access_key: String,
    pub session_token: String,
    /// The region the session signs for.
    pub region: String,
    /// The S3-compatible endpoint that minted the session; absent for AWS.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub endpoint: Option<String>,
    /// When the session stops working, in Unix microseconds.
    pub expires_at_micros: i64,
}

impl Debug for S3Session {
    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
        formatter
            .debug_struct("S3Session")
            .field("region", &self.region)
            .field("endpoint", &self.endpoint)
            .field("expires_at_micros", &self.expires_at_micros)
            .finish_non_exhaustive()
    }
}

impl Debug for StorageConfig {
    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
        match self {
            Self::Local { root_uri } => formatter
                .debug_struct("Local")
                .field("root_uri", root_uri)
                .finish(),
            Self::Spfs {
                endpoint, root_uri, ..
            } => formatter
                .debug_struct("Spfs")
                .field("endpoint", endpoint)
                .field("root_uri", root_uri)
                .finish_non_exhaustive(),
            Self::S3 { root_uri, .. } => formatter
                .debug_struct("S3")
                .field("root_uri", root_uri)
                .finish_non_exhaustive(),
            Self::Gcs { root_uri, .. } => formatter
                .debug_struct("Gcs")
                .field("root_uri", root_uri)
                .finish_non_exhaustive(),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::StorageConfig;

    #[test]
    fn storage_configuration_is_typed_and_redacts_credentials() {
        let config = StorageConfig::Spfs {
            endpoint: "https://storage.example.test".into(),
            root_uri: "spfsm://mount/table/".into(),
            credential: "scoped-secret".into(),
            root_alias: None,
        };
        assert!(!format!("{config:?}").contains("scoped-secret"));
        let encoded = serde_json::to_value(config).unwrap();
        assert_eq!(encoded["backend"], "spfs");
        assert!(serde_json::from_value::<StorageConfig>(encoded).is_ok());
        assert!(
            serde_json::from_str::<StorageConfig>(
                r#"{"backend":"local","root_uri":"file:///tmp/","credential":"unexpected"}"#
            )
            .is_err()
        );
    }
}