ferripfs-pinning 0.1.0

IPFS content pinning - prevent blocks from garbage collection
Documentation
// Ported from: kubo/boxo/pinning/pinner
// Kubo version: v0.39.0
// Original: https://github.com/ipfs/kubo/tree/v0.39.0/boxo/pinning/pinner
//
// Original work: Copyright (c) Protocol Labs, Inc.
// Port: Copyright (c) 2026 ferripfs contributors
// SPDX-License-Identifier: MIT OR Apache-2.0

//! Pinning system for ferripfs, ported from Kubo's boxo/pinning.
//!
//! Pins prevent blocks from being garbage collected. There are three types:
//! - **Direct**: Only the root block is protected
//! - **Recursive**: The root and all blocks it references are protected
//! - **Indirect**: Created automatically when a block is referenced by a recursive pin

mod pinner;
mod store;

pub use pinner::*;
pub use store::*;

use thiserror::Error;

/// Pin mode (type of pin)
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub enum PinMode {
    /// Direct pin - only the root block is pinned
    Direct,
    /// Recursive pin - root and all referenced blocks are pinned
    Recursive,
    /// Indirect pin - block is referenced by a recursive pin
    Indirect,
}

impl PinMode {
    /// Parse pin mode from string
    pub fn parse(s: &str) -> Option<Self> {
        match s.to_lowercase().as_str() {
            "direct" => Some(PinMode::Direct),
            "recursive" => Some(PinMode::Recursive),
            "indirect" => Some(PinMode::Indirect),
            "all" => None, // Used for filtering, means all types
            _ => None,
        }
    }

    /// Convert to string
    pub fn as_str(&self) -> &'static str {
        match self {
            PinMode::Direct => "direct",
            PinMode::Recursive => "recursive",
            PinMode::Indirect => "indirect",
        }
    }
}

impl std::fmt::Display for PinMode {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.as_str())
    }
}

/// Information about a pin
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct PinInfo {
    /// CID of the pinned block
    pub cid: String,
    /// Pin mode
    pub mode: PinMode,
    /// Optional name for the pin
    pub name: Option<String>,
}

/// Pinning error type
#[derive(Debug, Error)]
pub enum PinError {
    #[error("Block not found: {0}")]
    BlockNotFound(String),

    #[error("CID not pinned: {0}")]
    NotPinned(String),

    #[error("CID already pinned: {0}")]
    AlreadyPinned(String),

    #[error("Cannot unpin indirect pin directly: {0}")]
    CannotUnpinIndirect(String),

    #[error("Blockstore error: {0}")]
    Blockstore(#[from] ferripfs_blockstore::BlockstoreError),

    #[error("IO error: {0}")]
    Io(#[from] std::io::Error),

    #[error("JSON error: {0}")]
    Json(#[from] serde_json::Error),

    #[error("CID parse error: {0}")]
    CidParse(String),

    #[error("Pin verification failed: {0}")]
    VerificationFailed(String),
}

/// Result type for pinning operations
pub type PinResult<T> = Result<T, PinError>;

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

    #[test]
    fn test_pin_mode_from_str() {
        assert_eq!(PinMode::parse("direct"), Some(PinMode::Direct));
        assert_eq!(PinMode::parse("recursive"), Some(PinMode::Recursive));
        assert_eq!(PinMode::parse("indirect"), Some(PinMode::Indirect));
        assert_eq!(PinMode::parse("all"), None);
        assert_eq!(PinMode::parse("invalid"), None);
    }

    #[test]
    fn test_pin_mode_display() {
        assert_eq!(PinMode::Direct.to_string(), "direct");
        assert_eq!(PinMode::Recursive.to_string(), "recursive");
        assert_eq!(PinMode::Indirect.to_string(), "indirect");
    }
}