ferripfs-config 0.1.0

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

//! Gateway configuration for HTTP gateway.

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

/// Gateway configuration section
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct Gateway {
    /// HTTP headers to include in responses
    #[serde(default, rename = "HTTPHeaders")]
    pub http_headers: HashMap<String, Vec<String>>,

    /// Root redirect URL
    #[serde(default)]
    pub root_redirect: String,

    /// Disable fetching content from network
    #[serde(default)]
    pub no_fetch: bool,

    /// Disable DNSLink resolution
    #[serde(default, rename = "NoDNSLink")]
    pub no_dns_link: bool,

    /// Enable deserialized responses
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub deserialized_responses: Option<Flag>,

    /// Disable HTML error pages
    #[serde(
        default,
        skip_serializing_if = "Option::is_none",
        rename = "DisableHTMLErrors"
    )]
    pub disable_html_errors: Option<Flag>,

    /// Public gateway configurations by hostname
    #[serde(default)]
    pub public_gateways: HashMap<String, GatewaySpec>,

    /// Expose routing API
    #[serde(
        default,
        skip_serializing_if = "Option::is_none",
        rename = "ExposeRoutingAPI"
    )]
    pub expose_routing_api: Option<Flag>,

    /// Content retrieval timeout
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub retrieval_timeout: Option<OptionalDuration>,

    /// Maximum concurrent requests
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub max_concurrent_requests: Option<OptionalInteger>,

    /// Maximum file size for range requests
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub max_range_request_file_size: Option<OptionalString>,

    /// Diagnostic service URL
    #[serde(
        default,
        skip_serializing_if = "Option::is_none",
        rename = "DiagnosticServiceURL"
    )]
    pub diagnostic_service_url: Option<OptionalString>,
}

/// Gateway specification for public gateways
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct GatewaySpec {
    /// Paths to serve (e.g., "/ipfs", "/ipns")
    #[serde(default)]
    pub paths: Vec<String>,

    /// Use subdomain-based gateway
    #[serde(default)]
    pub use_subdomains: bool,

    /// Disable DNSLink for this gateway
    #[serde(default, rename = "NoDNSLink")]
    pub no_dns_link: bool,

    /// Inline DNSLink resolution
    #[serde(
        default,
        skip_serializing_if = "Option::is_none",
        rename = "InlineDNSLink"
    )]
    pub inline_dns_link: Option<Flag>,

    /// Enable deserialized responses for this gateway
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub deserialized_responses: Option<Flag>,
}

impl Gateway {
    /// Default paths for public gateways
    pub fn default_paths() -> Vec<String> {
        vec!["/ipfs".to_string(), "/ipns".to_string()]
    }

    /// Get default HTTP headers
    pub fn default_http_headers() -> HashMap<String, Vec<String>> {
        let mut headers = HashMap::new();
        headers.insert(
            "Access-Control-Allow-Origin".to_string(),
            vec!["*".to_string()],
        );
        headers.insert(
            "Access-Control-Allow-Methods".to_string(),
            vec!["GET".to_string()],
        );
        headers.insert(
            "Access-Control-Allow-Headers".to_string(),
            vec![
                "X-Requested-With".to_string(),
                "Range".to_string(),
                "User-Agent".to_string(),
            ],
        );
        headers.insert(
            "Access-Control-Expose-Headers".to_string(),
            vec![
                "Content-Range".to_string(),
                "X-Chunked-Output".to_string(),
                "X-Stream-Output".to_string(),
            ],
        );
        headers
    }
}

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

    #[test]
    fn test_gateway_default() {
        let gw = Gateway::default();
        assert!(gw.http_headers.is_empty());
        assert!(!gw.no_fetch);
    }

    #[test]
    fn test_default_paths() {
        let paths = Gateway::default_paths();
        assert!(paths.contains(&"/ipfs".to_string()));
        assert!(paths.contains(&"/ipns".to_string()));
    }

    #[test]
    fn test_default_headers() {
        let headers = Gateway::default_http_headers();
        assert!(headers.contains_key("Access-Control-Allow-Origin"));
    }
}