// This Source Code Form is subject to the terms of // the Mozilla Public License, v. 2.0. If a copy of the // MPL was not distributed with this file, You can // obtain one at https://mozilla.org/MPL/2.0/. use miette::Diagnostic; use std::collections::HashMap; use std::io; use std::path::{Path, StripPrefixError}; use thiserror::Error; /// Result type for repository operations pub type Result = std::result::Result; /// Errors that can occur in repository operations #[derive(Debug, Error, Diagnostic)] pub enum RepositoryError { #[error("unsupported repository version: {0}")] #[diagnostic( code(ips::repository_error::unsupported_version), help("Supported repository versions: 4") )] UnsupportedVersion(u32), #[error("repository not found at {0}")] #[diagnostic( code(ips::repository_error::not_found), help("Check that the repository path exists and is accessible") )] NotFound(String), #[error("publisher {0} not found")] #[diagnostic( code(ips::repository_error::publisher_not_found), help("Check that the publisher name is correct and exists in the repository") )] PublisherNotFound(String), #[error("publisher {0} already exists")] #[diagnostic( code(ips::repository_error::publisher_exists), help("Use a different publisher name or remove the existing publisher first") )] PublisherExists(String), #[error("failed to read repository configuration: {0}")] #[diagnostic( code(ips::repository_error::config_read), help("Check that the repository configuration file exists and is valid") )] ConfigReadError(String), #[error("failed to write repository configuration: {0}")] #[diagnostic( code(ips::repository_error::config_write), help("Check that the repository directory is writable") )] ConfigWriteError(String), #[error("failed to create directory: {0}")] #[diagnostic( code(ips::repository_error::directory_create), help("Check that the parent directory exists and is writable") )] DirectoryCreateError(String), #[error("failed to read file: {0}")] #[diagnostic( code(ips::repository_error::file_read), help("Check that the file exists and is readable") )] FileReadError(String), #[error("failed to write file: {0}")] #[diagnostic( code(ips::repository_error::file_write), help("Check that the directory is writable") )] FileWriteError(String), #[error("failed to parse JSON: {0}")] #[diagnostic( code(ips::repository_error::json_parse), help("Check that the JSON file is valid") )] JsonParseError(String), #[error("failed to serialize JSON: {0}")] #[diagnostic( code(ips::repository_error::json_serialize), help("This is likely a bug in the code") )] JsonSerializeError(String), #[error("I/O error: {0}")] #[diagnostic( code(ips::repository_error::io), help("Check system resources and permissions") )] IoError(#[from] io::Error), #[error("other error: {0}")] #[diagnostic( code(ips::repository_error::other), help("See error message for details") )] Other(String), #[error("JSON error: {0}")] #[diagnostic( code(ips::repository_error::json_error), help("Check the JSON format and try again") )] JsonError(String), #[error("digest error: {0}")] #[diagnostic( code(ips::repository_error::digest_error), help("Check the digest format and try again") )] DigestError(String), #[error(transparent)] #[diagnostic(transparent)] ActionError(#[from] ActionError), #[error(transparent)] #[diagnostic(transparent)] CatalogError(#[from] catalog::CatalogError), #[error("path prefix error: {0}")] #[diagnostic( code(ips::repository_error::path_prefix), help("Check that the path is valid and within the expected directory") )] PathPrefixError(String), } // Implement From for common error types impl From for RepositoryError { fn from(err: serde_json::Error) -> Self { RepositoryError::JsonError(err.to_string()) } } impl From for RepositoryError { fn from(err: DigestError) -> Self { RepositoryError::DigestError(err.to_string()) } } impl From for RepositoryError { fn from(err: StripPrefixError) -> Self { RepositoryError::PathPrefixError(err.to_string()) } } // Implement From for redb error types impl From for RepositoryError { fn from(err: redb::Error) -> Self { RepositoryError::Other(format!("Database error: {}", err)) } } impl From for RepositoryError { fn from(err: redb::DatabaseError) -> Self { RepositoryError::Other(format!("Database error: {}", err)) } } impl From for RepositoryError { fn from(err: redb::TransactionError) -> Self { RepositoryError::Other(format!("Transaction error: {}", err)) } } impl From for RepositoryError { fn from(err: redb::TableError) -> Self { RepositoryError::Other(format!("Table error: {}", err)) } } impl From for RepositoryError { fn from(err: redb::StorageError) -> Self { RepositoryError::Other(format!("Storage error: {}", err)) } } impl From for RepositoryError { fn from(err: redb::CommitError) -> Self { RepositoryError::Other(format!("Commit error: {}", err)) } } impl From for RepositoryError { fn from(err: bincode::Error) -> Self { RepositoryError::Other(format!("Serialization error: {}", err)) } } pub mod catalog; mod file_backend; mod obsoleted; pub mod progress; mod rest_backend; #[cfg(test)] mod tests; use crate::actions::ActionError; use crate::digest::DigestError; pub use catalog::{ CatalogAttrs, CatalogError, CatalogManager, CatalogOperationType, CatalogPart, UpdateLog, }; pub use file_backend::FileBackend; pub use obsoleted::{ObsoletedPackageManager, ObsoletedPackageMetadata}; pub use progress::{ProgressInfo, ProgressReporter, NoopProgressReporter}; pub use rest_backend::RestBackend; /// Repository configuration filename pub const REPOSITORY_CONFIG_FILENAME: &str = "pkg6.repository"; /// Information about a publisher in a repository #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct PublisherInfo { /// Name of the publisher pub name: String, /// Number of packages from this publisher pub package_count: usize, /// Status of the publisher (e.g., "online", "offline") pub status: String, /// Last updated timestamp in ISO 8601 format pub updated: String, } /// Information about a repository #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct RepositoryInfo { /// Information about publishers in the repository pub publishers: Vec, } /// Information about a package in a repository #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct PackageInfo { /// FMRI (Fault Management Resource Identifier) of the package pub fmri: crate::fmri::Fmri, } /// Contents of a package #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct PackageContents { /// Package identifier (name and version) pub package_id: String, /// Files in the package pub files: Option>, /// Directories in the package pub directories: Option>, /// Links in the package pub links: Option>, /// Dependencies of the package pub dependencies: Option>, /// Licenses in the package pub licenses: Option>, } /// Repository version #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub enum RepositoryVersion { V4 = 4, } impl Default for RepositoryVersion { fn default() -> Self { RepositoryVersion::V4 } } impl std::convert::TryFrom for RepositoryVersion { type Error = RepositoryError; fn try_from(value: u32) -> std::result::Result { match value { 4 => Ok(RepositoryVersion::V4), _ => Err(RepositoryError::UnsupportedVersion(value)), } } } /// Repository configuration #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct RepositoryConfig { pub version: RepositoryVersion, pub publishers: Vec, pub properties: HashMap, pub default_publisher: Option, } impl Default for RepositoryConfig { fn default() -> Self { RepositoryConfig { version: RepositoryVersion::default(), publishers: Vec::new(), properties: HashMap::new(), default_publisher: None, } } } /// Repository trait for read-only operations pub trait ReadableRepository { /// Open an existing repository fn open>(path: P) -> Result where Self: Sized; /// Get repository information fn get_info(&self) -> Result; /// List packages in the repository fn list_packages( &self, publisher: Option<&str>, pattern: Option<&str>, ) -> Result>; /// Show contents of packages fn show_contents( &self, publisher: Option<&str>, pattern: Option<&str>, action_types: Option<&[String]>, ) -> Result>; /// Search for packages in the repository /// /// This method searches for packages in the repository using the search index. /// It returns a list of packages that match the search query. /// /// # Arguments /// /// * `query` - The search query /// * `publisher` - Optional publisher to limit the search to /// * `limit` - Optional maximum number of results to return fn search( &self, query: &str, publisher: Option<&str>, limit: Option, ) -> Result>; } /// Repository trait for write operations pub trait WritableRepository { /// Create a new repository at the specified path fn create>(path: P, version: RepositoryVersion) -> Result where Self: Sized; /// Save the repository configuration fn save_config(&self) -> Result<()>; /// Add a publisher to the repository fn add_publisher(&mut self, publisher: &str) -> Result<()>; /// Remove a publisher from the repository fn remove_publisher(&mut self, publisher: &str, dry_run: bool) -> Result<()>; /// Set a repository property fn set_property(&mut self, property: &str, value: &str) -> Result<()>; /// Set a publisher property fn set_publisher_property( &mut self, publisher: &str, property: &str, value: &str, ) -> Result<()>; /// Rebuild repository metadata fn rebuild(&self, publisher: Option<&str>, no_catalog: bool, no_index: bool) -> Result<()>; /// Refresh repository metadata fn refresh(&self, publisher: Option<&str>, no_catalog: bool, no_index: bool) -> Result<()>; /// Set the default publisher for the repository fn set_default_publisher(&mut self, publisher: &str) -> Result<()>; } /// Repository trait defining the interface for all repository backends /// /// This trait combines both ReadableRepository and WritableRepository traits /// for backward compatibility. pub trait Repository: ReadableRepository + WritableRepository {}