floe-core 0.3.7

Core library for Floe, a YAML-driven technical ingestion tool.
Documentation
use std::path::PathBuf;

use floe_core::config::{
    ReportConfig, RootConfig, StorageDefinition, StorageResolver, StoragesConfig,
};
use floe_core::io::storage::Target;
use floe_core::report::ReportWriter;

fn build_config(definition: StorageDefinition, report: ReportConfig) -> RootConfig {
    RootConfig {
        version: "0.1".to_string(),
        metadata: None,
        storages: Some(StoragesConfig {
            default: Some(definition.name.clone()),
            definitions: vec![definition],
        }),
        catalogs: None,
        env: None,
        domains: Vec::new(),
        report: Some(report),
        entities: Vec::new(),
    }
}

fn resolver_for(config: &RootConfig) -> StorageResolver {
    let config_path = PathBuf::from("/tmp/floe-config.yml");
    StorageResolver::from_path(config, &config_path).expect("storage resolver")
}

#[test]
fn report_base_resolves_under_prefix() {
    let definition = StorageDefinition {
        name: "s3_test".to_string(),
        fs_type: "s3".to_string(),
        bucket: Some("my-bucket".to_string()),
        region: Some("eu-west-1".to_string()),
        account: None,
        container: None,
        prefix: Some("lakehouse".to_string()),
    };
    let report = ReportConfig {
        path: "report".to_string(),
        formatter: None,
        storage: None,
    };
    let config = build_config(definition, report);
    let resolver = resolver_for(&config);
    let resolved = resolver
        .resolve_report_path(None, "report")
        .expect("resolve report path");
    assert_eq!(resolved.uri, "s3://my-bucket/lakehouse/report");
}

#[test]
fn report_uri_builds_with_run_layout() {
    let definition = StorageDefinition {
        name: "gcs_test".to_string(),
        fs_type: "gcs".to_string(),
        bucket: Some("my-bucket".to_string()),
        region: None,
        account: None,
        container: None,
        prefix: Some("lakehouse/report".to_string()),
    };
    let report = ReportConfig {
        path: "reports".to_string(),
        formatter: None,
        storage: None,
    };
    let config = build_config(definition, report);
    let resolver = resolver_for(&config);
    let resolved = resolver
        .resolve_report_path(None, "reports")
        .expect("resolve report path");
    let target = Target::from_resolved(&resolved).expect("target");
    let relative = ReportWriter::report_relative_path("2026-01-19T10-23-45Z", "customer");
    let uri = target.join_relative(&relative);
    assert_eq!(
        uri,
        "gs://my-bucket/lakehouse/report/reports/run_2026-01-19T10-23-45Z/customer/run.json"
    );
}

#[test]
fn report_uri_normalizes_prefix_and_path() {
    let definition = StorageDefinition {
        name: "adls_test".to_string(),
        fs_type: "adls".to_string(),
        bucket: None,
        region: None,
        account: Some("acct".to_string()),
        container: Some("container".to_string()),
        prefix: Some("/lakehouse/".to_string()),
    };
    let report = ReportConfig {
        path: "/report/".to_string(),
        formatter: None,
        storage: None,
    };
    let config = build_config(definition, report);
    let resolver = resolver_for(&config);
    let resolved = resolver
        .resolve_report_path(None, "/report/")
        .expect("resolve report path");
    assert_eq!(
        resolved.uri,
        "abfs://container@acct.dfs.core.windows.net/lakehouse/report/"
    );
}

#[test]
fn local_report_paths_and_uris_are_normalized() {
    let config = RootConfig {
        version: "0.1".to_string(),
        metadata: None,
        storages: Some(StoragesConfig {
            default: Some("local".to_string()),
            definitions: vec![StorageDefinition {
                name: "local".to_string(),
                fs_type: "local".to_string(),
                bucket: None,
                region: None,
                account: None,
                container: None,
                prefix: None,
            }],
        }),
        catalogs: None,
        env: None,
        domains: Vec::new(),
        report: Some(ReportConfig {
            path: "./reports/../reports_out/./base".to_string(),
            formatter: None,
            storage: None,
        }),
        entities: Vec::new(),
    };
    let resolver = resolver_for(&config);
    let resolved = resolver
        .resolve_report_path(None, "./reports/../reports_out/./base")
        .expect("resolve report path");
    assert_eq!(
        resolved.local_path.as_ref().expect("local path"),
        &PathBuf::from("/tmp/reports_out/base")
    );
    assert_eq!(resolved.uri, "local:///tmp/reports_out/base");

    let target = Target::from_resolved(&resolved).expect("target");
    let report_file = target.join_relative(&ReportWriter::report_relative_path("run1", "orders"));
    assert_eq!(
        report_file,
        "/tmp/reports_out/base/run_run1/orders/run.json"
    );
    assert!(!report_file.contains("/./"));
    assert!(!report_file.contains("/../"));
}