ferripfs-config 0.1.0

IPFS node configuration types, compatible with Kubo config format
Documentation
// Ported from: kubo/config/datastore.go
// Kubo version: v0.39.0
// Original: https://github.com/ipfs/kubo/blob/v0.39.0/config/datastore.go
//
// Original work: Copyright (c) Protocol Labs, Inc.
// Port: Copyright (c) 2026 ferripfs contributors
// SPDX-License-Identifier: MIT OR Apache-2.0

//! Datastore configuration.

use crate::{Flag, OptionalInteger};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

/// Default datastore directory name
pub const DEFAULT_DATASTORE_DIRECTORY: &str = "datastore";

/// Default blocks directory name
pub const DEFAULT_BLOCKS_DIRECTORY: &str = "blocks";

/// Default storage maximum
pub const DEFAULT_STORAGE_MAX: &str = "10GB";

/// Default GC watermark (percentage)
pub const DEFAULT_STORAGE_GC_WATERMARK: i64 = 90;

/// Default GC period
pub const DEFAULT_GC_PERIOD: &str = "1h";

/// Datastore configuration section
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct Datastore {
    /// Maximum storage size (e.g., "10GB")
    #[serde(default)]
    pub storage_max: String,

    /// GC watermark percentage (0-100)
    #[serde(default, rename = "StorageGCWatermark")]
    pub storage_gc_watermark: i64,

    /// GC period (e.g., "1h")
    #[serde(default, rename = "GCPeriod")]
    pub gc_period: String,

    /// Datastore specification (type-dependent configuration)
    #[serde(default)]
    pub spec: HashMap<String, serde_json::Value>,

    /// Whether to verify block hashes on read
    #[serde(default)]
    pub hash_on_read: bool,

    /// Bloom filter size for blockstore
    #[serde(default)]
    pub bloom_filter_size: i32,

    /// Block key cache size
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub block_key_cache_size: Option<OptionalInteger>,

    /// Whether to write through cache
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub write_through: Option<Flag>,
}

impl Datastore {
    /// Create default datastore configuration
    pub fn default_config() -> Self {
        Self {
            storage_max: DEFAULT_STORAGE_MAX.to_string(),
            storage_gc_watermark: DEFAULT_STORAGE_GC_WATERMARK,
            gc_period: DEFAULT_GC_PERIOD.to_string(),
            spec: Self::default_spec(),
            hash_on_read: false,
            bloom_filter_size: 0,
            block_key_cache_size: None,
            write_through: None,
        }
    }

    /// Get the default FlatFS datastore spec
    pub fn default_spec() -> HashMap<String, serde_json::Value> {
        let mut spec = HashMap::new();
        spec.insert("type".to_string(), serde_json::json!("mount"));
        spec.insert(
            "mounts".to_string(),
            serde_json::json!([
                {
                    "mountpoint": "/blocks",
                    "type": "measure",
                    "prefix": "flatfs.datastore",
                    "child": {
                        "type": "flatfs",
                        "path": "blocks",
                        "sync": true,
                        "shardFunc": "/repo/flatfs/shard/v1/next-to-last/2"
                    }
                },
                {
                    "mountpoint": "/",
                    "type": "measure",
                    "prefix": "leveldb.datastore",
                    "child": {
                        "type": "levelds",
                        "path": "datastore",
                        "compression": "none"
                    }
                }
            ]),
        );
        spec
    }
}

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

    #[test]
    fn test_datastore_defaults() {
        let ds = Datastore::default_config();
        assert_eq!(ds.storage_max, "10GB");
        assert_eq!(ds.storage_gc_watermark, 90);
    }

    #[test]
    fn test_default_spec() {
        let spec = Datastore::default_spec();
        assert!(spec.contains_key("type"));
        assert!(spec.contains_key("mounts"));
    }
}