oxen-server 0.56.1

Oxen server is a fast data version control backend, supporting local disk and S3. Self host your repositories on your own storage, or use the hosted platform on Oxen.ai. Stores, syncs, and serves versioned datasets, model checkpoints, game assets, studio media, and any large data. Use the oxen CLI to push and pull from the oxen server.
//! Server-side configuration.
//!
//! - [`Config`] — the TOML deserialize target for `--config`.
//! - [`StoragePolicy`] — the version store storage policy for the server.
//! - [`IdentityPolicy`] — who assigns the UUIDs repositories are identified by.

pub mod identity_policy;
pub mod storage_policy;

use crate::config::identity_policy::IdentityPolicy;
use crate::config::storage_policy::StoragePolicy;
use serde::Deserialize;

/// TOML deserialization target for the server's `--config` flag.
///
/// Future top-level keys (auth options, telemetry settings, etc.) land here without restructuring.
/// An unrecognized table fails startup rather than being skipped: a misspelled section would
/// otherwise leave the server silently running the default policy the admin meant to replace.
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Config {
    #[serde(default)]
    pub storage: StoragePolicy,
    #[serde(default)]
    pub identity: IdentityPolicy,
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::config::identity_policy::IdentitySource;

    #[test]
    fn parse_empty_file_uses_defaults() {
        let f: Config = toml::from_str("").expect("an empty file parses");
        assert_eq!(f.storage, StoragePolicy::default());
        assert_eq!(f.identity, IdentityPolicy::default());
    }

    #[test]
    fn parse_identity_section() {
        let f: Config = toml::from_str(
            r#"
            [identity]
            repo_uuids_assigned_by = "auth-provider"
        "#,
        )
        .unwrap();
        assert_eq!(
            f.identity.repo_uuids_assigned_by(),
            IdentitySource::AuthProvider
        );
    }

    #[test]
    fn parse_minimal_valid_file_round_trips() {
        let toml_str = r#"
            [storage]
            backends = ["local", "s3"]
            s3_bucket = "my-bucket"
            s3_region = "us-west-1"
        "#;
        let f: Config = toml::from_str(toml_str).unwrap();
        let expected: StoragePolicy = toml::from_str(
            r#"
            backends = ["local", "s3"]
            s3_bucket = "my-bucket"
            s3_region = "us-west-1"
        "#,
        )
        .unwrap();
        assert_eq!(f.storage, expected);
    }

    #[test]
    fn parse_storage_section_validates() {
        // S3 in backends without a bucket — the nested `StoragePolicy` deserializer's
        // `TryFrom` impl fires and the error bubbles up through the parent.
        let err = toml::from_str::<Config>(
            r#"
            [storage]
            backends = ["s3"]
        "#,
        )
        .unwrap_err();
        assert!(
            err.to_string().contains("s3 bucket cannot be empty"),
            "expected EmptyS3Bucket message, got: {err}",
        );
    }

    /// A misspelled section must stop startup rather than leaving the server on a default the
    /// admin meant to replace.
    #[test]
    fn rejects_an_unrecognized_section() {
        let err = toml::from_str::<Config>("[storaje]\nbackends = [\"local\"]\n")
            .expect_err("an unrecognized section must be rejected");
        assert!(
            err.to_string().contains("unknown field"),
            "expected an unknown-field error, got: {err}",
        );
    }
}