systemprompt-files 0.2.1

File storage, metadata, and access control for systemprompt.io AI governance infrastructure. Governed file operations for the MCP governance pipeline.
Documentation
use serde::{Deserialize, Serialize};

pub use super::image_metadata::ImageMetadata;

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct FileMetadata {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub checksums: Option<FileChecksums>,

    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub type_specific: Option<TypeSpecificMetadata>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum TypeSpecificMetadata {
    Image(ImageMetadata),
    Document(DocumentMetadata),
    Audio(AudioMetadata),
    Video(VideoMetadata),
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct DocumentMetadata {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub title: Option<String>,

    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub author: Option<String>,

    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub page_count: Option<u32>,
}

#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
pub struct AudioMetadata {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub duration_seconds: Option<f32>,

    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub sample_rate: Option<u32>,

    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub channels: Option<u8>,
}

#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
pub struct VideoMetadata {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub width: Option<u32>,

    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub height: Option<u32>,

    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub duration_seconds: Option<f32>,

    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub frame_rate: Option<f32>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct FileChecksums {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub md5: Option<String>,

    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub sha256: Option<String>,
}

impl FileMetadata {
    pub const fn new() -> Self {
        Self {
            checksums: None,
            type_specific: None,
        }
    }

    pub fn with_image(mut self, image: ImageMetadata) -> Self {
        self.type_specific = Some(TypeSpecificMetadata::Image(image));
        self
    }

    pub fn with_document(mut self, doc: DocumentMetadata) -> Self {
        self.type_specific = Some(TypeSpecificMetadata::Document(doc));
        self
    }

    pub fn with_audio(mut self, audio: AudioMetadata) -> Self {
        self.type_specific = Some(TypeSpecificMetadata::Audio(audio));
        self
    }

    pub fn with_video(mut self, video: VideoMetadata) -> Self {
        self.type_specific = Some(TypeSpecificMetadata::Video(video));
        self
    }

    pub fn with_checksums(mut self, checksums: FileChecksums) -> Self {
        self.checksums = Some(checksums);
        self
    }
}

impl DocumentMetadata {
    pub const fn new() -> Self {
        Self {
            title: None,
            author: None,
            page_count: None,
        }
    }

    pub fn with_title(mut self, title: impl Into<String>) -> Self {
        self.title = Some(title.into());
        self
    }

    pub fn with_author(mut self, author: impl Into<String>) -> Self {
        self.author = Some(author.into());
        self
    }

    pub const fn with_page_count(mut self, page_count: u32) -> Self {
        self.page_count = Some(page_count);
        self
    }
}

impl AudioMetadata {
    pub const fn new() -> Self {
        Self {
            duration_seconds: None,
            sample_rate: None,
            channels: None,
        }
    }

    pub const fn with_duration_seconds(mut self, duration: f32) -> Self {
        self.duration_seconds = Some(duration);
        self
    }

    pub const fn with_sample_rate(mut self, sample_rate: u32) -> Self {
        self.sample_rate = Some(sample_rate);
        self
    }

    pub const fn with_channels(mut self, channels: u8) -> Self {
        self.channels = Some(channels);
        self
    }
}

impl VideoMetadata {
    pub const fn new() -> Self {
        Self {
            width: None,
            height: None,
            duration_seconds: None,
            frame_rate: None,
        }
    }

    pub const fn with_dimensions(mut self, width: u32, height: u32) -> Self {
        self.width = Some(width);
        self.height = Some(height);
        self
    }

    pub const fn with_duration_seconds(mut self, duration: f32) -> Self {
        self.duration_seconds = Some(duration);
        self
    }

    pub const fn with_frame_rate(mut self, frame_rate: f32) -> Self {
        self.frame_rate = Some(frame_rate);
        self
    }
}

impl FileChecksums {
    pub const fn new() -> Self {
        Self {
            md5: None,
            sha256: None,
        }
    }

    pub fn with_md5(mut self, md5: impl Into<String>) -> Self {
        self.md5 = Some(md5.into());
        self
    }

    pub fn with_sha256(mut self, sha256: impl Into<String>) -> Self {
        self.sha256 = Some(sha256.into());
        self
    }
}