mirror of
https://codeberg.org/Toasterson/ips.git
synced 2026-04-10 13:20:42 +00:00
Replace anyhow with custom RepositoryError for improved error specificity and consistency. Remove anyhow dependency.
This commit is contained in:
parent
40560e5960
commit
4649608408
15 changed files with 518 additions and 868 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -88,7 +88,7 @@ Cargo.lock
|
|||
**/*.rs.bk
|
||||
|
||||
# Prototype directory
|
||||
sample_data/**/build/prototype/i386
|
||||
sample_data
|
||||
|
||||
.vagrant
|
||||
/.output.txt
|
||||
|
|
|
|||
847
Cargo.lock
generated
847
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -17,8 +17,9 @@ keywords = ["packaging", "illumos"]
|
|||
|
||||
[dependencies]
|
||||
regex = "1.5.5"
|
||||
anyhow = "1.0.56"
|
||||
thiserror = "1.0.30"
|
||||
thiserror = "1.0.50"
|
||||
miette = "7.6.0"
|
||||
tracing = "0.1.37"
|
||||
maplit = "0.1.6"
|
||||
object = "0.23.0"
|
||||
sha2 = "0.9.3"
|
||||
|
|
@ -32,6 +33,5 @@ flate2 = "1.0.28"
|
|||
lz4 = "1.24.0"
|
||||
semver = { version = "1.0.20", features = ["serde"] }
|
||||
diff-struct = "0.5.3"
|
||||
searchy = "0.5.0"
|
||||
tantivy = { version = "0.24.2", features = ["mmap"] }
|
||||
chrono = "0.4.41"
|
||||
tempfile = "3.20.0"
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ use crate::digest::Digest;
|
|||
use crate::fmri::Fmri;
|
||||
use crate::payload::{Payload, PayloadError};
|
||||
use diff::Diff;
|
||||
use miette::Diagnostic;
|
||||
use pest::Parser;
|
||||
use pest_derive::Parser;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
|
@ -22,21 +23,29 @@ use thiserror::Error;
|
|||
|
||||
type Result<T> = StdResult<T, ActionError>;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[derive(Debug, Error, Diagnostic)]
|
||||
pub enum ActionError {
|
||||
#[error(transparent)]
|
||||
#[diagnostic(transparent)]
|
||||
PayloadError(#[from] PayloadError),
|
||||
|
||||
#[error(transparent)]
|
||||
#[diagnostic(transparent)]
|
||||
FileError(#[from] FileError),
|
||||
|
||||
#[error("value {0} is not a boolean")]
|
||||
#[diagnostic(
|
||||
code(ips::action_error::invalid_boolean),
|
||||
help("Boolean values must be 'true', 'false', 't', or 'f'.")
|
||||
)]
|
||||
NotBooleanValue(String),
|
||||
|
||||
#[error(transparent)]
|
||||
#[diagnostic(code(ips::action_error::io))]
|
||||
IOError(#[from] std::io::Error),
|
||||
|
||||
#[error(transparent)]
|
||||
#[diagnostic(code(ips::action_error::parser))]
|
||||
ParserError(#[from] pest::error::Error<Rule>),
|
||||
}
|
||||
|
||||
|
|
@ -256,9 +265,13 @@ impl FacetedAction for File {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[derive(Debug, Error, Diagnostic)]
|
||||
pub enum FileError {
|
||||
#[error("file path is not a string")]
|
||||
#[diagnostic(
|
||||
code(ips::action_error::file::invalid_path),
|
||||
help("The file path must be convertible to a valid string.")
|
||||
)]
|
||||
FilePathIsNoStringError,
|
||||
}
|
||||
|
||||
|
|
@ -538,7 +551,15 @@ impl Manifest {
|
|||
|
||||
pub fn parse_file<P: AsRef<Path>>(f: P) -> Result<Manifest> {
|
||||
let content = read_to_string(f)?;
|
||||
Manifest::parse_string(content)
|
||||
|
||||
// Try to parse as JSON first
|
||||
match serde_json::from_str::<Manifest>(&content) {
|
||||
Ok(manifest) => Ok(manifest),
|
||||
Err(_) => {
|
||||
// If JSON parsing fails, fall back to string format
|
||||
Manifest::parse_string(content)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_string(content: String) -> Result<Manifest> {
|
||||
|
|
@ -630,11 +651,20 @@ impl Default for ActionKind {
|
|||
}
|
||||
|
||||
//TODO Multierror and no failure for these cases
|
||||
#[derive(Debug, Error)]
|
||||
#[derive(Debug, Error, Diagnostic)]
|
||||
pub enum ManifestError {
|
||||
#[error("unknown action {action:?} at line {line:?}")]
|
||||
#[diagnostic(
|
||||
code(ips::action_error::manifest::unknown_action),
|
||||
help("Check the action name and make sure it's one of the supported action types.")
|
||||
)]
|
||||
UnknownAction { line: usize, action: String },
|
||||
|
||||
#[error("action string \"{action:?}\" at line {line:?} is invalid: {message:?}")]
|
||||
#[diagnostic(
|
||||
code(ips::action_error::manifest::invalid_action),
|
||||
help("Check the action format and fix the syntax error.")
|
||||
)]
|
||||
InvalidAction {
|
||||
line: usize,
|
||||
action: String,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
// obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
use diff::Diff;
|
||||
use miette::Diagnostic;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::Digest as Sha2Digest;
|
||||
#[allow(unused_imports)]
|
||||
|
|
@ -146,10 +147,19 @@ impl Display for Digest {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[derive(Debug, Error, Diagnostic)]
|
||||
pub enum DigestError {
|
||||
#[error("hashing algorithm {algorithm:?} is not known by this library")]
|
||||
#[diagnostic(
|
||||
code(ips::digest_error::unknown_algorithm),
|
||||
help("Use one of the supported algorithms: sha1, sha256t, sha512t, sha512t_256, sha3256t, sha3512t_256, sha3512t")
|
||||
)]
|
||||
UnknownAlgorithm { algorithm: String },
|
||||
|
||||
#[error("digest {digest:?} is not formatted properly: {details:?}")]
|
||||
#[diagnostic(
|
||||
code(ips::digest_error::invalid_format),
|
||||
help("Digest should be in the format: source:algorithm:hash")
|
||||
)]
|
||||
InvalidDigestFormat { digest: String, details: String },
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,25 +58,55 @@
|
|||
//! ```
|
||||
|
||||
use diff::Diff;
|
||||
use miette::Diagnostic;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
use thiserror::Error;
|
||||
|
||||
/// Errors that can occur when parsing an FMRI
|
||||
#[derive(Debug, Error, PartialEq)]
|
||||
#[derive(Debug, Error, Diagnostic, PartialEq)]
|
||||
pub enum FmriError {
|
||||
#[error("invalid FMRI format")]
|
||||
#[diagnostic(
|
||||
code(ips::fmri_error::invalid_format),
|
||||
help("FMRI should be in the format: [scheme://][publisher/]name[@version]")
|
||||
)]
|
||||
InvalidFormat,
|
||||
|
||||
#[error("invalid version format")]
|
||||
#[diagnostic(
|
||||
code(ips::fmri_error::invalid_version_format),
|
||||
help("Version should be in the format: release[,branch][-build][:timestamp]")
|
||||
)]
|
||||
InvalidVersionFormat,
|
||||
|
||||
#[error("invalid release format")]
|
||||
#[diagnostic(
|
||||
code(ips::fmri_error::invalid_release_format),
|
||||
help("Release should be a dot-separated vector of digits (e.g., 5.11)")
|
||||
)]
|
||||
InvalidReleaseFormat,
|
||||
|
||||
#[error("invalid branch format")]
|
||||
#[diagnostic(
|
||||
code(ips::fmri_error::invalid_branch_format),
|
||||
help("Branch should be a dot-separated vector of digits (e.g., 1)")
|
||||
)]
|
||||
InvalidBranchFormat,
|
||||
|
||||
#[error("invalid build format")]
|
||||
#[diagnostic(
|
||||
code(ips::fmri_error::invalid_build_format),
|
||||
help("Build should be a dot-separated vector of digits (e.g., 2020.0.1.0)")
|
||||
)]
|
||||
InvalidBuildFormat,
|
||||
|
||||
#[error("invalid timestamp format")]
|
||||
#[diagnostic(
|
||||
code(ips::fmri_error::invalid_timestamp_format),
|
||||
help("Timestamp should be a hexadecimal string (e.g., 20200421T195136Z)")
|
||||
)]
|
||||
InvalidTimestampFormat,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,18 +1,27 @@
|
|||
mod properties;
|
||||
|
||||
use properties::*;
|
||||
use miette::Diagnostic;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
use std::path::{Path, PathBuf};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[derive(Debug, Error, Diagnostic)]
|
||||
pub enum ImageError {
|
||||
//Implement derives for IO error and serde_json error
|
||||
#[error(transparent)]
|
||||
#[error("I/O error: {0}")]
|
||||
#[diagnostic(
|
||||
code(ips::image_error::io),
|
||||
help("Check system resources and permissions")
|
||||
)]
|
||||
IO(#[from] std::io::Error),
|
||||
#[error(transparent)]
|
||||
|
||||
#[error("JSON error: {0}")]
|
||||
#[diagnostic(
|
||||
code(ips::image_error::json),
|
||||
help("Check the JSON format and try again")
|
||||
)]
|
||||
Json(#[from] serde_json::Error),
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ pub mod fmri;
|
|||
pub mod image;
|
||||
pub mod payload;
|
||||
pub mod repository;
|
||||
mod test_json_manifest;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
use crate::digest::{Digest, DigestAlgorithm, DigestError, DigestSource};
|
||||
use diff::Diff;
|
||||
use miette::Diagnostic;
|
||||
use object::Object;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::Error as IOError;
|
||||
|
|
@ -14,11 +15,17 @@ use thiserror::Error;
|
|||
|
||||
type Result<T> = StdResult<T, PayloadError>;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[derive(Debug, Error, Diagnostic)]
|
||||
pub enum PayloadError {
|
||||
#[error("io error: {0}")]
|
||||
#[error("I/O error: {0}")]
|
||||
#[diagnostic(
|
||||
code(ips::payload_error::io),
|
||||
help("Check system resources and permissions")
|
||||
)]
|
||||
IOError(#[from] IOError),
|
||||
#[error("digest error: {0}")]
|
||||
|
||||
#[error(transparent)]
|
||||
#[diagnostic(transparent)]
|
||||
DigestError(#[from] DigestError),
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,15 +3,74 @@
|
|||
// MPL was not distributed with this file, You can
|
||||
// obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
use anyhow::Result;
|
||||
use miette::Diagnostic;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::SystemTime;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::fmri::Fmri;
|
||||
|
||||
/// Errors that can occur in catalog operations
|
||||
#[derive(Debug, Error, Diagnostic)]
|
||||
pub enum CatalogError {
|
||||
#[error("catalog part does not exist: {name}")]
|
||||
#[diagnostic(
|
||||
code(ips::repository_error::catalog::part_not_found),
|
||||
help("Check that the catalog part exists and is accessible")
|
||||
)]
|
||||
CatalogPartNotFound {
|
||||
name: String,
|
||||
},
|
||||
|
||||
#[error("catalog part not loaded: {name}")]
|
||||
#[diagnostic(
|
||||
code(ips::repository_error::catalog::part_not_loaded),
|
||||
help("Load the catalog part before attempting to save it")
|
||||
)]
|
||||
CatalogPartNotLoaded {
|
||||
name: String,
|
||||
},
|
||||
|
||||
#[error("update log not loaded: {name}")]
|
||||
#[diagnostic(
|
||||
code(ips::repository_error::catalog::update_log_not_loaded),
|
||||
help("Load the update log before attempting to save it")
|
||||
)]
|
||||
UpdateLogNotLoaded {
|
||||
name: String,
|
||||
},
|
||||
|
||||
#[error("update log does not exist: {name}")]
|
||||
#[diagnostic(
|
||||
code(ips::repository_error::catalog::update_log_not_found),
|
||||
help("Check that the update log exists and is accessible")
|
||||
)]
|
||||
UpdateLogNotFound {
|
||||
name: String,
|
||||
},
|
||||
|
||||
#[error("failed to serialize JSON: {0}")]
|
||||
#[diagnostic(
|
||||
code(ips::repository_error::catalog::json_serialize),
|
||||
help("This is likely a bug in the code")
|
||||
)]
|
||||
JsonSerializationError(#[from] serde_json::Error),
|
||||
|
||||
#[error("I/O error: {0}")]
|
||||
#[diagnostic(
|
||||
code(ips::repository_error::catalog::io),
|
||||
help("Check system resources and permissions")
|
||||
)]
|
||||
IoError(#[from] io::Error),
|
||||
}
|
||||
|
||||
/// Result type for catalog operations
|
||||
pub type Result<T> = std::result::Result<T, CatalogError>;
|
||||
|
||||
/// Format a SystemTime as an ISO-8601 'basic format' date in UTC
|
||||
fn format_iso8601_basic(time: &SystemTime) -> String {
|
||||
let datetime = convert_system_time_to_datetime(time);
|
||||
|
|
@ -393,7 +452,9 @@ impl CatalogManager {
|
|||
self.parts.insert(name.to_string(), part);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(anyhow::anyhow!("Catalog part does not exist: {}", name))
|
||||
Err(CatalogError::CatalogPartNotFound {
|
||||
name: name.to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -404,7 +465,9 @@ impl CatalogManager {
|
|||
part.save(&part_path)?;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(anyhow::anyhow!("Catalog part not loaded: {}", name))
|
||||
Err(CatalogError::CatalogPartNotLoaded {
|
||||
name: name.to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -453,7 +516,9 @@ impl CatalogManager {
|
|||
|
||||
Ok(())
|
||||
} else {
|
||||
Err(anyhow::anyhow!("Update log not loaded: {}", name))
|
||||
Err(CatalogError::UpdateLogNotLoaded {
|
||||
name: name.to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -465,7 +530,9 @@ impl CatalogManager {
|
|||
self.update_logs.insert(name.to_string(), log);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(anyhow::anyhow!("Update log does not exist: {}", name))
|
||||
Err(CatalogError::UpdateLogNotFound {
|
||||
name: name.to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
// MPL was not distributed with this file, You can
|
||||
// obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use super::{Result, RepositoryError};
|
||||
use flate2::write::GzEncoder;
|
||||
use flate2::Compression as GzipCompression;
|
||||
use lz4::EncoderBuilder;
|
||||
|
|
@ -315,12 +315,12 @@ impl Transaction {
|
|||
if temp_file_path.exists() {
|
||||
// If it exists, remove it to avoid any issues with existing content
|
||||
fs::remove_file(&temp_file_path)
|
||||
.map_err(|e| anyhow!("Failed to remove existing temp file: {}", e))?;
|
||||
.map_err(|e| RepositoryError::FileWriteError(format!("Failed to remove existing temp file: {}", e)))?;
|
||||
}
|
||||
|
||||
// Read the file content
|
||||
let file_content = fs::read(file_path)
|
||||
.map_err(|e| anyhow!("Failed to read file {}: {}", file_path.display(), e))?;
|
||||
.map_err(|e| RepositoryError::FileReadError(format!("Failed to read file {}: {}", file_path.display(), e)))?;
|
||||
|
||||
// Create a payload with the hash information if it doesn't exist
|
||||
let mut updated_file_action = file_action;
|
||||
|
|
@ -338,16 +338,16 @@ impl Transaction {
|
|||
// Write the file content to the encoder
|
||||
encoder
|
||||
.write_all(&file_content)
|
||||
.map_err(|e| anyhow!("Failed to write data to Gzip encoder: {}", e))?;
|
||||
.map_err(|e| RepositoryError::Other(format!("Failed to write data to Gzip encoder: {}", e)))?;
|
||||
|
||||
// Finish the compression and get the compressed data
|
||||
let compressed_data = encoder
|
||||
.finish()
|
||||
.map_err(|e| anyhow!("Failed to finish Gzip compression: {}", e))?;
|
||||
.map_err(|e| RepositoryError::Other(format!("Failed to finish Gzip compression: {}", e)))?;
|
||||
|
||||
// Write the compressed data to the temp file
|
||||
fs::write(&temp_file_path, &compressed_data)
|
||||
.map_err(|e| anyhow!("Failed to write compressed data to temp file: {}", e))?;
|
||||
.map_err(|e| RepositoryError::FileWriteError(format!("Failed to write compressed data to temp file: {}", e)))?;
|
||||
|
||||
// Calculate hash of the compressed data
|
||||
let mut hasher = Sha256::new();
|
||||
|
|
@ -358,19 +358,19 @@ impl Transaction {
|
|||
// Create an LZ4 encoder with default compression level
|
||||
let mut encoder = EncoderBuilder::new()
|
||||
.build(Vec::new())
|
||||
.map_err(|e| anyhow!("Failed to create LZ4 encoder: {}", e))?;
|
||||
.map_err(|e| RepositoryError::Other(format!("Failed to create LZ4 encoder: {}", e)))?;
|
||||
|
||||
// Write the file content to the encoder
|
||||
encoder
|
||||
.write_all(&file_content)
|
||||
.map_err(|e| anyhow!("Failed to write data to LZ4 encoder: {}", e))?;
|
||||
.map_err(|e| RepositoryError::Other(format!("Failed to write data to LZ4 encoder: {}", e)))?;
|
||||
|
||||
// Finish the compression and get the compressed data
|
||||
let (compressed_data, _) = encoder.finish();
|
||||
|
||||
// Write the compressed data to the temp file
|
||||
fs::write(&temp_file_path, &compressed_data).map_err(|e| {
|
||||
anyhow!("Failed to write LZ4 compressed data to temp file: {}", e)
|
||||
RepositoryError::FileWriteError(format!("Failed to write LZ4 compressed data to temp file: {}", e))
|
||||
})?;
|
||||
|
||||
// Calculate hash of the compressed data
|
||||
|
|
@ -510,7 +510,7 @@ impl ReadableRepository for FileBackend {
|
|||
|
||||
// Check if the repository directory exists
|
||||
if !path.exists() {
|
||||
return Err(anyhow!("Repository does not exist: {}", path.display()));
|
||||
return Err(RepositoryError::NotFound(path.display().to_string()));
|
||||
}
|
||||
|
||||
// Load the repository configuration
|
||||
|
|
@ -595,7 +595,7 @@ impl ReadableRepository for FileBackend {
|
|||
// Filter publishers if specified
|
||||
let publishers = if let Some(pub_name) = publisher {
|
||||
if !self.config.publishers.contains(&pub_name.to_string()) {
|
||||
return Err(anyhow!("Publisher does not exist: {}", pub_name));
|
||||
return Err(RepositoryError::PublisherNotFound(pub_name.to_string()));
|
||||
}
|
||||
vec![pub_name.to_string()]
|
||||
} else {
|
||||
|
|
@ -611,9 +611,8 @@ impl ReadableRepository for FileBackend {
|
|||
if publisher_pkg_dir.exists() {
|
||||
// Verify that the publisher is in the config
|
||||
if !self.config.publishers.contains(&pub_name) {
|
||||
return Err(anyhow!(
|
||||
"Publisher directory exists but is not in the repository configuration: {}",
|
||||
pub_name
|
||||
return Err(RepositoryError::Other(
|
||||
format!("Publisher directory exists but is not in the repository configuration: {}", pub_name)
|
||||
));
|
||||
}
|
||||
|
||||
|
|
@ -746,7 +745,7 @@ impl ReadableRepository for FileBackend {
|
|||
// Filter publishers if specified
|
||||
let publishers = if let Some(pub_name) = publisher {
|
||||
if !self.config.publishers.contains(&pub_name.to_string()) {
|
||||
return Err(anyhow!("Publisher does not exist: {}", pub_name));
|
||||
return Err(RepositoryError::PublisherNotFound(pub_name.to_string()));
|
||||
}
|
||||
vec![pub_name.to_string()]
|
||||
} else {
|
||||
|
|
@ -987,8 +986,13 @@ impl ReadableRepository for FileBackend {
|
|||
publisher: Option<&str>,
|
||||
limit: Option<usize>,
|
||||
) -> Result<Vec<PackageInfo>> {
|
||||
println!("Searching for packages with query: {}", query);
|
||||
println!("Publisher: {:?}", publisher);
|
||||
println!("Limit: {:?}", limit);
|
||||
|
||||
// If no publisher is specified, use the default publisher if available
|
||||
let publisher = publisher.or_else(|| self.config.default_publisher.as_deref());
|
||||
println!("Effective publisher: {:?}", publisher);
|
||||
|
||||
// If still no publisher, we need to search all publishers
|
||||
let publishers = if let Some(pub_name) = publisher {
|
||||
|
|
@ -996,34 +1000,55 @@ impl ReadableRepository for FileBackend {
|
|||
} else {
|
||||
self.config.publishers.clone()
|
||||
};
|
||||
println!("Publishers to search: {:?}", publishers);
|
||||
|
||||
let mut results = Vec::new();
|
||||
|
||||
// For each publisher, search the index
|
||||
for pub_name in publishers {
|
||||
println!("Searching publisher: {}", pub_name);
|
||||
|
||||
// Check if the index exists
|
||||
let index_path = self.path.join("index").join(&pub_name).join("search.json");
|
||||
println!("Index path: {}", index_path.display());
|
||||
println!("Index exists: {}", index_path.exists());
|
||||
|
||||
if let Ok(Some(index)) = self.get_search_index(&pub_name) {
|
||||
println!("Got search index for publisher: {}", pub_name);
|
||||
println!("Index terms: {:?}", index.terms.keys().collect::<Vec<_>>());
|
||||
|
||||
// Search the index
|
||||
let fmris = index.search(query, limit);
|
||||
println!("Search results (FMRIs): {:?}", fmris);
|
||||
|
||||
// Convert FMRIs to PackageInfo
|
||||
for fmri_str in fmris {
|
||||
if let Ok(fmri) = Fmri::parse(&fmri_str) {
|
||||
println!("Adding package to results: {}", fmri);
|
||||
results.push(PackageInfo { fmri });
|
||||
} else {
|
||||
println!("Failed to parse FMRI: {}", fmri_str);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
println!("No search index found for publisher: {}", pub_name);
|
||||
println!("Falling back to simple search");
|
||||
|
||||
// If the index doesn't exist, fall back to the simple search
|
||||
let all_packages = self.list_packages(Some(&pub_name), None)?;
|
||||
println!("All packages: {:?}", all_packages);
|
||||
|
||||
// Filter packages by the query string
|
||||
let matching_packages: Vec<PackageInfo> = all_packages
|
||||
.into_iter()
|
||||
.filter(|pkg| {
|
||||
// Match against package name
|
||||
pkg.fmri.stem().contains(query)
|
||||
let matches = pkg.fmri.stem().contains(query);
|
||||
println!("Package: {}, Matches: {}", pkg.fmri.stem(), matches);
|
||||
matches
|
||||
})
|
||||
.collect();
|
||||
println!("Matching packages: {:?}", matching_packages);
|
||||
|
||||
// Add matching packages to the results
|
||||
results.extend(matching_packages);
|
||||
|
|
@ -1035,6 +1060,7 @@ impl ReadableRepository for FileBackend {
|
|||
results.truncate(max_results);
|
||||
}
|
||||
|
||||
println!("Final search results: {:?}", results);
|
||||
Ok(results)
|
||||
}
|
||||
}
|
||||
|
|
@ -1111,13 +1137,13 @@ impl WritableRepository for FileBackend {
|
|||
// Remove the catalog directory if it exists
|
||||
if catalog_dir.exists() {
|
||||
fs::remove_dir_all(&catalog_dir)
|
||||
.map_err(|e| anyhow!("Failed to remove catalog directory: {}", e))?;
|
||||
.map_err(|e| RepositoryError::Other(format!("Failed to remove catalog directory: {}", e)))?;
|
||||
}
|
||||
|
||||
// Remove the package directory if it exists
|
||||
if pkg_dir.exists() {
|
||||
fs::remove_dir_all(&pkg_dir)
|
||||
.map_err(|e| anyhow!("Failed to remove package directory: {}", e))?;
|
||||
.map_err(|e| RepositoryError::Other(format!("Failed to remove package directory: {}", e)))?;
|
||||
}
|
||||
|
||||
// Save the updated configuration
|
||||
|
|
@ -1146,7 +1172,7 @@ impl WritableRepository for FileBackend {
|
|||
) -> Result<()> {
|
||||
// Check if the publisher exists
|
||||
if !self.config.publishers.contains(&publisher.to_string()) {
|
||||
return Err(anyhow!("Publisher does not exist: {}", publisher));
|
||||
return Err(RepositoryError::PublisherNotFound(publisher.to_string()));
|
||||
}
|
||||
|
||||
// Create the property key in the format "publisher/property"
|
||||
|
|
@ -1166,7 +1192,7 @@ impl WritableRepository for FileBackend {
|
|||
// Filter publishers if specified
|
||||
let publishers = if let Some(pub_name) = publisher {
|
||||
if !self.config.publishers.contains(&pub_name.to_string()) {
|
||||
return Err(anyhow!("Publisher does not exist: {}", pub_name));
|
||||
return Err(RepositoryError::PublisherNotFound(pub_name.to_string()));
|
||||
}
|
||||
vec![pub_name.to_string()]
|
||||
} else {
|
||||
|
|
@ -1196,7 +1222,7 @@ impl WritableRepository for FileBackend {
|
|||
// Filter publishers if specified
|
||||
let publishers = if let Some(pub_name) = publisher {
|
||||
if !self.config.publishers.contains(&pub_name.to_string()) {
|
||||
return Err(anyhow!("Publisher does not exist: {}", pub_name));
|
||||
return Err(RepositoryError::PublisherNotFound(pub_name.to_string()));
|
||||
}
|
||||
vec![pub_name.to_string()]
|
||||
} else {
|
||||
|
|
@ -1235,7 +1261,7 @@ impl WritableRepository for FileBackend {
|
|||
fn set_default_publisher(&mut self, publisher: &str) -> Result<()> {
|
||||
// Check if the publisher exists
|
||||
if !self.config.publishers.contains(&publisher.to_string()) {
|
||||
return Err(anyhow!("Publisher does not exist: {}", publisher));
|
||||
return Err(RepositoryError::PublisherNotFound(publisher.to_string()));
|
||||
}
|
||||
|
||||
// Set the default publisher
|
||||
|
|
@ -1729,10 +1755,10 @@ impl FileBackend {
|
|||
let actual_path = &transaction.manifest.files[0].path;
|
||||
|
||||
if actual_path != expected_path {
|
||||
return Err(anyhow!(
|
||||
"Path in FileAction is incorrect. Expected: {}, Actual: {}",
|
||||
return Err(RepositoryError::Other(
|
||||
format!("Path in FileAction is incorrect. Expected: {}, Actual: {}",
|
||||
expected_path,
|
||||
actual_path
|
||||
actual_path)
|
||||
));
|
||||
}
|
||||
|
||||
|
|
@ -1744,14 +1770,14 @@ impl FileBackend {
|
|||
let stored_file_path = self.path.join("file").join(&hash);
|
||||
|
||||
if !stored_file_path.exists() {
|
||||
return Err(anyhow!("File was not stored correctly"));
|
||||
return Err(RepositoryError::Other("File was not stored correctly".to_string()));
|
||||
}
|
||||
|
||||
// Verify the manifest was updated
|
||||
let manifest_path = self.path.join("pkg").join("manifest");
|
||||
|
||||
if !manifest_path.exists() {
|
||||
return Err(anyhow!("Manifest was not created"));
|
||||
return Err(RepositoryError::Other("Manifest was not created".to_string()));
|
||||
}
|
||||
|
||||
println!("File publishing test passed!");
|
||||
|
|
@ -1770,15 +1796,15 @@ impl FileBackend {
|
|||
|
||||
// Check if the prototype directory exists
|
||||
if !proto_dir.exists() {
|
||||
return Err(anyhow!(
|
||||
return Err(RepositoryError::NotFound(format!(
|
||||
"Prototype directory does not exist: {}",
|
||||
proto_dir.display()
|
||||
));
|
||||
)));
|
||||
}
|
||||
|
||||
// Check if the publisher exists
|
||||
if !self.config.publishers.contains(&publisher.to_string()) {
|
||||
return Err(anyhow!("Publisher does not exist: {}", publisher));
|
||||
return Err(RepositoryError::PublisherNotFound(publisher.to_string()));
|
||||
}
|
||||
|
||||
// Begin a transaction
|
||||
|
|
|
|||
|
|
@ -3,9 +3,159 @@
|
|||
// MPL was not distributed with this file, You can
|
||||
// obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
use anyhow::Result;
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
use std::io;
|
||||
use std::path::{Path, StripPrefixError};
|
||||
use miette::Diagnostic;
|
||||
use thiserror::Error;
|
||||
|
||||
/// Result type for repository operations
|
||||
pub type Result<T> = std::result::Result<T, RepositoryError>;
|
||||
|
||||
/// 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<serde_json::Error> for RepositoryError {
|
||||
fn from(err: serde_json::Error) -> Self {
|
||||
RepositoryError::JsonError(err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DigestError> for RepositoryError {
|
||||
fn from(err: DigestError) -> Self {
|
||||
RepositoryError::DigestError(err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl From<StripPrefixError> for RepositoryError {
|
||||
fn from(err: StripPrefixError) -> Self {
|
||||
RepositoryError::PathPrefixError(err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
mod catalog;
|
||||
mod file_backend;
|
||||
|
|
@ -13,9 +163,11 @@ mod rest_backend;
|
|||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
pub use catalog::{CatalogAttrs, CatalogManager, CatalogOperationType, CatalogPart, UpdateLog};
|
||||
pub use catalog::{CatalogAttrs, CatalogError, CatalogManager, CatalogOperationType, CatalogPart, UpdateLog};
|
||||
pub use file_backend::FileBackend;
|
||||
pub use rest_backend::RestBackend;
|
||||
use crate::actions::ActionError;
|
||||
use crate::digest::DigestError;
|
||||
|
||||
/// Repository configuration filename
|
||||
pub const REPOSITORY_CONFIG_FILENAME: &str = "pkg6.repository";
|
||||
|
|
@ -77,12 +229,12 @@ impl Default for RepositoryVersion {
|
|||
}
|
||||
|
||||
impl std::convert::TryFrom<u32> for RepositoryVersion {
|
||||
type Error = anyhow::Error;
|
||||
type Error = RepositoryError;
|
||||
|
||||
fn try_from(value: u32) -> Result<Self, Self::Error> {
|
||||
fn try_from(value: u32) -> std::result::Result<Self, Self::Error> {
|
||||
match value {
|
||||
4 => Ok(RepositoryVersion::V4),
|
||||
_ => Err(anyhow::anyhow!("Unsupported repository version: {}", value)),
|
||||
_ => Err(RepositoryError::UnsupportedVersion(value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,12 +3,11 @@
|
|||
// MPL was not distributed with this file, You can
|
||||
// obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use super::{
|
||||
PackageContents, PackageInfo, PublisherInfo, ReadableRepository, RepositoryConfig,
|
||||
RepositoryInfo, RepositoryVersion, WritableRepository,
|
||||
RepositoryError, RepositoryInfo, RepositoryVersion, Result, WritableRepository,
|
||||
};
|
||||
|
||||
/// Repository implementation that uses a REST API
|
||||
|
|
@ -97,7 +96,7 @@ impl WritableRepository for RestBackend {
|
|||
// Filter publishers if specified
|
||||
let publishers = if let Some(pub_name) = publisher {
|
||||
if !self.config.publishers.contains(&pub_name.to_string()) {
|
||||
return Err(anyhow!("Publisher does not exist: {}", pub_name));
|
||||
return Err(RepositoryError::PublisherNotFound(pub_name.to_string()));
|
||||
}
|
||||
vec![pub_name.to_string()]
|
||||
} else {
|
||||
|
|
@ -130,7 +129,7 @@ impl WritableRepository for RestBackend {
|
|||
// Filter publishers if specified
|
||||
let publishers = if let Some(pub_name) = publisher {
|
||||
if !self.config.publishers.contains(&pub_name.to_string()) {
|
||||
return Err(anyhow!("Publisher does not exist: {}", pub_name));
|
||||
return Err(RepositoryError::PublisherNotFound(pub_name.to_string()));
|
||||
}
|
||||
vec![pub_name.to_string()]
|
||||
} else {
|
||||
|
|
@ -162,7 +161,7 @@ impl WritableRepository for RestBackend {
|
|||
|
||||
// Check if the publisher exists
|
||||
if !self.config.publishers.contains(&publisher.to_string()) {
|
||||
return Err(anyhow!("Publisher does not exist: {}", publisher));
|
||||
return Err(RepositoryError::PublisherNotFound(publisher.to_string()));
|
||||
}
|
||||
|
||||
// Set the default publisher
|
||||
|
|
@ -199,7 +198,7 @@ impl WritableRepository for RestBackend {
|
|||
|
||||
// Check if the publisher exists
|
||||
if !self.config.publishers.contains(&publisher.to_string()) {
|
||||
return Err(anyhow!("Publisher does not exist: {}", publisher));
|
||||
return Err(RepositoryError::PublisherNotFound(publisher.to_string()));
|
||||
}
|
||||
|
||||
// Create the property key in the format "publisher/property"
|
||||
|
|
@ -274,7 +273,7 @@ impl ReadableRepository for RestBackend {
|
|||
// Filter publishers if specified
|
||||
let publishers = if let Some(pub_name) = publisher {
|
||||
if !self.config.publishers.contains(&pub_name.to_string()) {
|
||||
return Err(anyhow!("Publisher does not exist: {}", pub_name));
|
||||
return Err(RepositoryError::PublisherNotFound(pub_name.to_string()));
|
||||
}
|
||||
vec![pub_name.to_string()]
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ mod tests {
|
|||
use crate::actions::Manifest;
|
||||
use crate::fmri::Fmri;
|
||||
use crate::repository::{
|
||||
CatalogManager, FileBackend, ReadableRepository, RepositoryVersion, WritableRepository,
|
||||
REPOSITORY_CONFIG_FILENAME,
|
||||
CatalogManager, FileBackend, ReadableRepository, RepositoryError, RepositoryVersion, Result,
|
||||
WritableRepository, REPOSITORY_CONFIG_FILENAME,
|
||||
};
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
|
@ -72,7 +72,7 @@ mod tests {
|
|||
manifest_path: &PathBuf,
|
||||
prototype_dir: &PathBuf,
|
||||
publisher: &str,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
) -> Result<()> {
|
||||
println!(
|
||||
"Publishing package from manifest: {}",
|
||||
manifest_path.display()
|
||||
|
|
@ -83,18 +83,16 @@ mod tests {
|
|||
// Check if the manifest file exists
|
||||
if !manifest_path.exists() {
|
||||
println!("Error: Manifest file does not exist");
|
||||
return Err(anyhow::anyhow!(
|
||||
"Manifest file does not exist: {}",
|
||||
manifest_path.display()
|
||||
return Err(RepositoryError::FileReadError(
|
||||
format!("Manifest file does not exist: {}", manifest_path.display())
|
||||
));
|
||||
}
|
||||
|
||||
// Check if the prototype directory exists
|
||||
if !prototype_dir.exists() {
|
||||
println!("Error: Prototype directory does not exist");
|
||||
return Err(anyhow::anyhow!(
|
||||
"Prototype directory does not exist: {}",
|
||||
prototype_dir.display()
|
||||
return Err(RepositoryError::NotFound(
|
||||
format!("Prototype directory does not exist: {}", prototype_dir.display())
|
||||
));
|
||||
}
|
||||
|
||||
|
|
|
|||
60
libips/src/test_json_manifest.rs
Normal file
60
libips/src/test_json_manifest.rs
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::actions::Manifest;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use tempfile::tempdir;
|
||||
|
||||
#[test]
|
||||
fn test_parse_json_manifest() {
|
||||
// Create a temporary directory for the test
|
||||
let temp_dir = tempdir().unwrap();
|
||||
let manifest_path = temp_dir.path().join("test_manifest.json");
|
||||
|
||||
// Create a simple manifest
|
||||
let mut manifest = Manifest::new();
|
||||
|
||||
// Add some attributes
|
||||
let mut attr = crate::actions::Attr::default();
|
||||
attr.key = "pkg.fmri".to_string();
|
||||
attr.values = vec!["pkg://test/example@1.0.0".to_string()];
|
||||
manifest.attributes.push(attr);
|
||||
|
||||
// Serialize the manifest to JSON
|
||||
let manifest_json = serde_json::to_string_pretty(&manifest).unwrap();
|
||||
|
||||
// Write the JSON to a file
|
||||
let mut file = File::create(&manifest_path).unwrap();
|
||||
file.write_all(manifest_json.as_bytes()).unwrap();
|
||||
|
||||
// Parse the JSON manifest
|
||||
let parsed_manifest = Manifest::parse_file(&manifest_path).unwrap();
|
||||
|
||||
// Verify that the parsed manifest matches the original
|
||||
assert_eq!(parsed_manifest.attributes.len(), 1);
|
||||
assert_eq!(parsed_manifest.attributes[0].key, "pkg.fmri");
|
||||
assert_eq!(parsed_manifest.attributes[0].values[0], "pkg://test/example@1.0.0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_string_manifest() {
|
||||
// Create a temporary directory for the test
|
||||
let temp_dir = tempdir().unwrap();
|
||||
let manifest_path = temp_dir.path().join("test_manifest.p5m");
|
||||
|
||||
// Create a simple string-formatted manifest
|
||||
let manifest_string = "set name=pkg.fmri value=pkg://test/example@1.0.0\n";
|
||||
|
||||
// Write the string to a file
|
||||
let mut file = File::create(&manifest_path).unwrap();
|
||||
file.write_all(manifest_string.as_bytes()).unwrap();
|
||||
|
||||
// Parse the string manifest
|
||||
let parsed_manifest = Manifest::parse_file(&manifest_path).unwrap();
|
||||
|
||||
// Verify that the parsed manifest has the expected attributes
|
||||
assert_eq!(parsed_manifest.attributes.len(), 1);
|
||||
assert_eq!(parsed_manifest.attributes[0].key, "name");
|
||||
assert_eq!(parsed_manifest.attributes[0].values[0], "pkg.fmri");
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue