mirror of
https://codeberg.org/Toasterson/ips.git
synced 2026-04-10 21:30:41 +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
|
**/*.rs.bk
|
||||||
|
|
||||||
# Prototype directory
|
# Prototype directory
|
||||||
sample_data/**/build/prototype/i386
|
sample_data
|
||||||
|
|
||||||
.vagrant
|
.vagrant
|
||||||
/.output.txt
|
/.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]
|
[dependencies]
|
||||||
regex = "1.5.5"
|
regex = "1.5.5"
|
||||||
anyhow = "1.0.56"
|
thiserror = "1.0.50"
|
||||||
thiserror = "1.0.30"
|
miette = "7.6.0"
|
||||||
|
tracing = "0.1.37"
|
||||||
maplit = "0.1.6"
|
maplit = "0.1.6"
|
||||||
object = "0.23.0"
|
object = "0.23.0"
|
||||||
sha2 = "0.9.3"
|
sha2 = "0.9.3"
|
||||||
|
|
@ -32,6 +33,5 @@ flate2 = "1.0.28"
|
||||||
lz4 = "1.24.0"
|
lz4 = "1.24.0"
|
||||||
semver = { version = "1.0.20", features = ["serde"] }
|
semver = { version = "1.0.20", features = ["serde"] }
|
||||||
diff-struct = "0.5.3"
|
diff-struct = "0.5.3"
|
||||||
searchy = "0.5.0"
|
|
||||||
tantivy = { version = "0.24.2", features = ["mmap"] }
|
|
||||||
chrono = "0.4.41"
|
chrono = "0.4.41"
|
||||||
|
tempfile = "3.20.0"
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ use crate::digest::Digest;
|
||||||
use crate::fmri::Fmri;
|
use crate::fmri::Fmri;
|
||||||
use crate::payload::{Payload, PayloadError};
|
use crate::payload::{Payload, PayloadError};
|
||||||
use diff::Diff;
|
use diff::Diff;
|
||||||
|
use miette::Diagnostic;
|
||||||
use pest::Parser;
|
use pest::Parser;
|
||||||
use pest_derive::Parser;
|
use pest_derive::Parser;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
@ -22,21 +23,29 @@ use thiserror::Error;
|
||||||
|
|
||||||
type Result<T> = StdResult<T, ActionError>;
|
type Result<T> = StdResult<T, ActionError>;
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error, Diagnostic)]
|
||||||
pub enum ActionError {
|
pub enum ActionError {
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
|
#[diagnostic(transparent)]
|
||||||
PayloadError(#[from] PayloadError),
|
PayloadError(#[from] PayloadError),
|
||||||
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
|
#[diagnostic(transparent)]
|
||||||
FileError(#[from] FileError),
|
FileError(#[from] FileError),
|
||||||
|
|
||||||
#[error("value {0} is not a boolean")]
|
#[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),
|
NotBooleanValue(String),
|
||||||
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
|
#[diagnostic(code(ips::action_error::io))]
|
||||||
IOError(#[from] std::io::Error),
|
IOError(#[from] std::io::Error),
|
||||||
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
|
#[diagnostic(code(ips::action_error::parser))]
|
||||||
ParserError(#[from] pest::error::Error<Rule>),
|
ParserError(#[from] pest::error::Error<Rule>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -256,9 +265,13 @@ impl FacetedAction for File {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error, Diagnostic)]
|
||||||
pub enum FileError {
|
pub enum FileError {
|
||||||
#[error("file path is not a string")]
|
#[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,
|
FilePathIsNoStringError,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -538,8 +551,16 @@ impl Manifest {
|
||||||
|
|
||||||
pub fn parse_file<P: AsRef<Path>>(f: P) -> Result<Manifest> {
|
pub fn parse_file<P: AsRef<Path>>(f: P) -> Result<Manifest> {
|
||||||
let content = read_to_string(f)?;
|
let content = read_to_string(f)?;
|
||||||
|
|
||||||
|
// 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)
|
Manifest::parse_string(content)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn parse_string(content: String) -> Result<Manifest> {
|
pub fn parse_string(content: String) -> Result<Manifest> {
|
||||||
let mut m = Manifest::new();
|
let mut m = Manifest::new();
|
||||||
|
|
@ -630,11 +651,20 @@ impl Default for ActionKind {
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO Multierror and no failure for these cases
|
//TODO Multierror and no failure for these cases
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error, Diagnostic)]
|
||||||
pub enum ManifestError {
|
pub enum ManifestError {
|
||||||
#[error("unknown action {action:?} at line {line:?}")]
|
#[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 },
|
UnknownAction { line: usize, action: String },
|
||||||
|
|
||||||
#[error("action string \"{action:?}\" at line {line:?} is invalid: {message:?}")]
|
#[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 {
|
InvalidAction {
|
||||||
line: usize,
|
line: usize,
|
||||||
action: String,
|
action: String,
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
// obtain one at https://mozilla.org/MPL/2.0/.
|
// obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
use diff::Diff;
|
use diff::Diff;
|
||||||
|
use miette::Diagnostic;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sha2::Digest as Sha2Digest;
|
use sha2::Digest as Sha2Digest;
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
|
|
@ -146,10 +147,19 @@ impl Display for Digest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error, Diagnostic)]
|
||||||
pub enum DigestError {
|
pub enum DigestError {
|
||||||
#[error("hashing algorithm {algorithm:?} is not known by this library")]
|
#[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 },
|
UnknownAlgorithm { algorithm: String },
|
||||||
|
|
||||||
#[error("digest {digest:?} is not formatted properly: {details:?}")]
|
#[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 },
|
InvalidDigestFormat { digest: String, details: String },
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -58,25 +58,55 @@
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
use diff::Diff;
|
use diff::Diff;
|
||||||
|
use miette::Diagnostic;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
/// Errors that can occur when parsing an FMRI
|
/// Errors that can occur when parsing an FMRI
|
||||||
#[derive(Debug, Error, PartialEq)]
|
#[derive(Debug, Error, Diagnostic, PartialEq)]
|
||||||
pub enum FmriError {
|
pub enum FmriError {
|
||||||
#[error("invalid FMRI format")]
|
#[error("invalid FMRI format")]
|
||||||
|
#[diagnostic(
|
||||||
|
code(ips::fmri_error::invalid_format),
|
||||||
|
help("FMRI should be in the format: [scheme://][publisher/]name[@version]")
|
||||||
|
)]
|
||||||
InvalidFormat,
|
InvalidFormat,
|
||||||
|
|
||||||
#[error("invalid version format")]
|
#[error("invalid version format")]
|
||||||
|
#[diagnostic(
|
||||||
|
code(ips::fmri_error::invalid_version_format),
|
||||||
|
help("Version should be in the format: release[,branch][-build][:timestamp]")
|
||||||
|
)]
|
||||||
InvalidVersionFormat,
|
InvalidVersionFormat,
|
||||||
|
|
||||||
#[error("invalid release format")]
|
#[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,
|
InvalidReleaseFormat,
|
||||||
|
|
||||||
#[error("invalid branch format")]
|
#[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,
|
InvalidBranchFormat,
|
||||||
|
|
||||||
#[error("invalid build format")]
|
#[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,
|
InvalidBuildFormat,
|
||||||
|
|
||||||
#[error("invalid timestamp format")]
|
#[error("invalid timestamp format")]
|
||||||
|
#[diagnostic(
|
||||||
|
code(ips::fmri_error::invalid_timestamp_format),
|
||||||
|
help("Timestamp should be a hexadecimal string (e.g., 20200421T195136Z)")
|
||||||
|
)]
|
||||||
InvalidTimestampFormat,
|
InvalidTimestampFormat,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,27 @@
|
||||||
mod properties;
|
mod properties;
|
||||||
|
|
||||||
use properties::*;
|
use properties::*;
|
||||||
|
use miette::Diagnostic;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error, Diagnostic)]
|
||||||
pub enum ImageError {
|
pub enum ImageError {
|
||||||
//Implement derives for IO error and serde_json error
|
#[error("I/O error: {0}")]
|
||||||
#[error(transparent)]
|
#[diagnostic(
|
||||||
|
code(ips::image_error::io),
|
||||||
|
help("Check system resources and permissions")
|
||||||
|
)]
|
||||||
IO(#[from] std::io::Error),
|
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),
|
Json(#[from] serde_json::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ pub mod fmri;
|
||||||
pub mod image;
|
pub mod image;
|
||||||
pub mod payload;
|
pub mod payload;
|
||||||
pub mod repository;
|
pub mod repository;
|
||||||
|
mod test_json_manifest;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
use crate::digest::{Digest, DigestAlgorithm, DigestError, DigestSource};
|
use crate::digest::{Digest, DigestAlgorithm, DigestError, DigestSource};
|
||||||
use diff::Diff;
|
use diff::Diff;
|
||||||
|
use miette::Diagnostic;
|
||||||
use object::Object;
|
use object::Object;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::io::Error as IOError;
|
use std::io::Error as IOError;
|
||||||
|
|
@ -14,11 +15,17 @@ use thiserror::Error;
|
||||||
|
|
||||||
type Result<T> = StdResult<T, PayloadError>;
|
type Result<T> = StdResult<T, PayloadError>;
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error, Diagnostic)]
|
||||||
pub enum PayloadError {
|
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),
|
IOError(#[from] IOError),
|
||||||
#[error("digest error: {0}")]
|
|
||||||
|
#[error(transparent)]
|
||||||
|
#[diagnostic(transparent)]
|
||||||
DigestError(#[from] DigestError),
|
DigestError(#[from] DigestError),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,74 @@
|
||||||
// MPL was not distributed with this file, You can
|
// MPL was not distributed with this file, You can
|
||||||
// obtain one at https://mozilla.org/MPL/2.0/.
|
// obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
use anyhow::Result;
|
use miette::Diagnostic;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
use std::io;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::fmri::Fmri;
|
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
|
/// Format a SystemTime as an ISO-8601 'basic format' date in UTC
|
||||||
fn format_iso8601_basic(time: &SystemTime) -> String {
|
fn format_iso8601_basic(time: &SystemTime) -> String {
|
||||||
let datetime = convert_system_time_to_datetime(time);
|
let datetime = convert_system_time_to_datetime(time);
|
||||||
|
|
@ -393,7 +452,9 @@ impl CatalogManager {
|
||||||
self.parts.insert(name.to_string(), part);
|
self.parts.insert(name.to_string(), part);
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} 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)?;
|
part.save(&part_path)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(anyhow::anyhow!("Catalog part not loaded: {}", name))
|
Err(CatalogError::CatalogPartNotLoaded {
|
||||||
|
name: name.to_string(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -453,7 +516,9 @@ impl CatalogManager {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} 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);
|
self.update_logs.insert(name.to_string(), log);
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} 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
|
// MPL was not distributed with this file, You can
|
||||||
// obtain one at https://mozilla.org/MPL/2.0/.
|
// obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use super::{Result, RepositoryError};
|
||||||
use flate2::write::GzEncoder;
|
use flate2::write::GzEncoder;
|
||||||
use flate2::Compression as GzipCompression;
|
use flate2::Compression as GzipCompression;
|
||||||
use lz4::EncoderBuilder;
|
use lz4::EncoderBuilder;
|
||||||
|
|
@ -315,12 +315,12 @@ impl Transaction {
|
||||||
if temp_file_path.exists() {
|
if temp_file_path.exists() {
|
||||||
// If it exists, remove it to avoid any issues with existing content
|
// If it exists, remove it to avoid any issues with existing content
|
||||||
fs::remove_file(&temp_file_path)
|
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
|
// Read the file content
|
||||||
let file_content = fs::read(file_path)
|
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
|
// Create a payload with the hash information if it doesn't exist
|
||||||
let mut updated_file_action = file_action;
|
let mut updated_file_action = file_action;
|
||||||
|
|
@ -338,16 +338,16 @@ impl Transaction {
|
||||||
// Write the file content to the encoder
|
// Write the file content to the encoder
|
||||||
encoder
|
encoder
|
||||||
.write_all(&file_content)
|
.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
|
// Finish the compression and get the compressed data
|
||||||
let compressed_data = encoder
|
let compressed_data = encoder
|
||||||
.finish()
|
.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
|
// Write the compressed data to the temp file
|
||||||
fs::write(&temp_file_path, &compressed_data)
|
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
|
// Calculate hash of the compressed data
|
||||||
let mut hasher = Sha256::new();
|
let mut hasher = Sha256::new();
|
||||||
|
|
@ -358,19 +358,19 @@ impl Transaction {
|
||||||
// Create an LZ4 encoder with default compression level
|
// Create an LZ4 encoder with default compression level
|
||||||
let mut encoder = EncoderBuilder::new()
|
let mut encoder = EncoderBuilder::new()
|
||||||
.build(Vec::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
|
// Write the file content to the encoder
|
||||||
encoder
|
encoder
|
||||||
.write_all(&file_content)
|
.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
|
// Finish the compression and get the compressed data
|
||||||
let (compressed_data, _) = encoder.finish();
|
let (compressed_data, _) = encoder.finish();
|
||||||
|
|
||||||
// Write the compressed data to the temp file
|
// Write the compressed data to the temp file
|
||||||
fs::write(&temp_file_path, &compressed_data).map_err(|e| {
|
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
|
// Calculate hash of the compressed data
|
||||||
|
|
@ -510,7 +510,7 @@ impl ReadableRepository for FileBackend {
|
||||||
|
|
||||||
// Check if the repository directory exists
|
// Check if the repository directory exists
|
||||||
if !path.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
|
// Load the repository configuration
|
||||||
|
|
@ -595,7 +595,7 @@ impl ReadableRepository for FileBackend {
|
||||||
// Filter publishers if specified
|
// Filter publishers if specified
|
||||||
let publishers = if let Some(pub_name) = publisher {
|
let publishers = if let Some(pub_name) = publisher {
|
||||||
if !self.config.publishers.contains(&pub_name.to_string()) {
|
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()]
|
vec![pub_name.to_string()]
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -611,9 +611,8 @@ impl ReadableRepository for FileBackend {
|
||||||
if publisher_pkg_dir.exists() {
|
if publisher_pkg_dir.exists() {
|
||||||
// Verify that the publisher is in the config
|
// Verify that the publisher is in the config
|
||||||
if !self.config.publishers.contains(&pub_name) {
|
if !self.config.publishers.contains(&pub_name) {
|
||||||
return Err(anyhow!(
|
return Err(RepositoryError::Other(
|
||||||
"Publisher directory exists but is not in the repository configuration: {}",
|
format!("Publisher directory exists but is not in the repository configuration: {}", pub_name)
|
||||||
pub_name
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -746,7 +745,7 @@ impl ReadableRepository for FileBackend {
|
||||||
// Filter publishers if specified
|
// Filter publishers if specified
|
||||||
let publishers = if let Some(pub_name) = publisher {
|
let publishers = if let Some(pub_name) = publisher {
|
||||||
if !self.config.publishers.contains(&pub_name.to_string()) {
|
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()]
|
vec![pub_name.to_string()]
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -987,8 +986,13 @@ impl ReadableRepository for FileBackend {
|
||||||
publisher: Option<&str>,
|
publisher: Option<&str>,
|
||||||
limit: Option<usize>,
|
limit: Option<usize>,
|
||||||
) -> Result<Vec<PackageInfo>> {
|
) -> 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
|
// If no publisher is specified, use the default publisher if available
|
||||||
let publisher = publisher.or_else(|| self.config.default_publisher.as_deref());
|
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
|
// If still no publisher, we need to search all publishers
|
||||||
let publishers = if let Some(pub_name) = publisher {
|
let publishers = if let Some(pub_name) = publisher {
|
||||||
|
|
@ -996,34 +1000,55 @@ impl ReadableRepository for FileBackend {
|
||||||
} else {
|
} else {
|
||||||
self.config.publishers.clone()
|
self.config.publishers.clone()
|
||||||
};
|
};
|
||||||
|
println!("Publishers to search: {:?}", publishers);
|
||||||
|
|
||||||
let mut results = Vec::new();
|
let mut results = Vec::new();
|
||||||
|
|
||||||
// For each publisher, search the index
|
// For each publisher, search the index
|
||||||
for pub_name in publishers {
|
for pub_name in publishers {
|
||||||
|
println!("Searching publisher: {}", pub_name);
|
||||||
|
|
||||||
// Check if the index exists
|
// 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) {
|
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
|
// Search the index
|
||||||
let fmris = index.search(query, limit);
|
let fmris = index.search(query, limit);
|
||||||
|
println!("Search results (FMRIs): {:?}", fmris);
|
||||||
|
|
||||||
// Convert FMRIs to PackageInfo
|
// Convert FMRIs to PackageInfo
|
||||||
for fmri_str in fmris {
|
for fmri_str in fmris {
|
||||||
if let Ok(fmri) = Fmri::parse(&fmri_str) {
|
if let Ok(fmri) = Fmri::parse(&fmri_str) {
|
||||||
|
println!("Adding package to results: {}", fmri);
|
||||||
results.push(PackageInfo { fmri });
|
results.push(PackageInfo { fmri });
|
||||||
|
} else {
|
||||||
|
println!("Failed to parse FMRI: {}", fmri_str);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} 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
|
// If the index doesn't exist, fall back to the simple search
|
||||||
let all_packages = self.list_packages(Some(&pub_name), None)?;
|
let all_packages = self.list_packages(Some(&pub_name), None)?;
|
||||||
|
println!("All packages: {:?}", all_packages);
|
||||||
|
|
||||||
// Filter packages by the query string
|
// Filter packages by the query string
|
||||||
let matching_packages: Vec<PackageInfo> = all_packages
|
let matching_packages: Vec<PackageInfo> = all_packages
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|pkg| {
|
.filter(|pkg| {
|
||||||
// Match against package name
|
// 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();
|
.collect();
|
||||||
|
println!("Matching packages: {:?}", matching_packages);
|
||||||
|
|
||||||
// Add matching packages to the results
|
// Add matching packages to the results
|
||||||
results.extend(matching_packages);
|
results.extend(matching_packages);
|
||||||
|
|
@ -1035,6 +1060,7 @@ impl ReadableRepository for FileBackend {
|
||||||
results.truncate(max_results);
|
results.truncate(max_results);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
println!("Final search results: {:?}", results);
|
||||||
Ok(results)
|
Ok(results)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1111,13 +1137,13 @@ impl WritableRepository for FileBackend {
|
||||||
// Remove the catalog directory if it exists
|
// Remove the catalog directory if it exists
|
||||||
if catalog_dir.exists() {
|
if catalog_dir.exists() {
|
||||||
fs::remove_dir_all(&catalog_dir)
|
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
|
// Remove the package directory if it exists
|
||||||
if pkg_dir.exists() {
|
if pkg_dir.exists() {
|
||||||
fs::remove_dir_all(&pkg_dir)
|
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
|
// Save the updated configuration
|
||||||
|
|
@ -1146,7 +1172,7 @@ impl WritableRepository for FileBackend {
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
// Check if the publisher exists
|
// Check if the publisher exists
|
||||||
if !self.config.publishers.contains(&publisher.to_string()) {
|
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"
|
// Create the property key in the format "publisher/property"
|
||||||
|
|
@ -1166,7 +1192,7 @@ impl WritableRepository for FileBackend {
|
||||||
// Filter publishers if specified
|
// Filter publishers if specified
|
||||||
let publishers = if let Some(pub_name) = publisher {
|
let publishers = if let Some(pub_name) = publisher {
|
||||||
if !self.config.publishers.contains(&pub_name.to_string()) {
|
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()]
|
vec![pub_name.to_string()]
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1196,7 +1222,7 @@ impl WritableRepository for FileBackend {
|
||||||
// Filter publishers if specified
|
// Filter publishers if specified
|
||||||
let publishers = if let Some(pub_name) = publisher {
|
let publishers = if let Some(pub_name) = publisher {
|
||||||
if !self.config.publishers.contains(&pub_name.to_string()) {
|
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()]
|
vec![pub_name.to_string()]
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1235,7 +1261,7 @@ impl WritableRepository for FileBackend {
|
||||||
fn set_default_publisher(&mut self, publisher: &str) -> Result<()> {
|
fn set_default_publisher(&mut self, publisher: &str) -> Result<()> {
|
||||||
// Check if the publisher exists
|
// Check if the publisher exists
|
||||||
if !self.config.publishers.contains(&publisher.to_string()) {
|
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
|
// Set the default publisher
|
||||||
|
|
@ -1729,10 +1755,10 @@ impl FileBackend {
|
||||||
let actual_path = &transaction.manifest.files[0].path;
|
let actual_path = &transaction.manifest.files[0].path;
|
||||||
|
|
||||||
if actual_path != expected_path {
|
if actual_path != expected_path {
|
||||||
return Err(anyhow!(
|
return Err(RepositoryError::Other(
|
||||||
"Path in FileAction is incorrect. Expected: {}, Actual: {}",
|
format!("Path in FileAction is incorrect. Expected: {}, Actual: {}",
|
||||||
expected_path,
|
expected_path,
|
||||||
actual_path
|
actual_path)
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1744,14 +1770,14 @@ impl FileBackend {
|
||||||
let stored_file_path = self.path.join("file").join(&hash);
|
let stored_file_path = self.path.join("file").join(&hash);
|
||||||
|
|
||||||
if !stored_file_path.exists() {
|
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
|
// Verify the manifest was updated
|
||||||
let manifest_path = self.path.join("pkg").join("manifest");
|
let manifest_path = self.path.join("pkg").join("manifest");
|
||||||
|
|
||||||
if !manifest_path.exists() {
|
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!");
|
println!("File publishing test passed!");
|
||||||
|
|
@ -1770,15 +1796,15 @@ impl FileBackend {
|
||||||
|
|
||||||
// Check if the prototype directory exists
|
// Check if the prototype directory exists
|
||||||
if !proto_dir.exists() {
|
if !proto_dir.exists() {
|
||||||
return Err(anyhow!(
|
return Err(RepositoryError::NotFound(format!(
|
||||||
"Prototype directory does not exist: {}",
|
"Prototype directory does not exist: {}",
|
||||||
proto_dir.display()
|
proto_dir.display()
|
||||||
));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the publisher exists
|
// Check if the publisher exists
|
||||||
if !self.config.publishers.contains(&publisher.to_string()) {
|
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
|
// Begin a transaction
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,159 @@
|
||||||
// MPL was not distributed with this file, You can
|
// MPL was not distributed with this file, You can
|
||||||
// obtain one at https://mozilla.org/MPL/2.0/.
|
// obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
use anyhow::Result;
|
|
||||||
use std::collections::HashMap;
|
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 catalog;
|
||||||
mod file_backend;
|
mod file_backend;
|
||||||
|
|
@ -13,9 +163,11 @@ mod rest_backend;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
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 file_backend::FileBackend;
|
||||||
pub use rest_backend::RestBackend;
|
pub use rest_backend::RestBackend;
|
||||||
|
use crate::actions::ActionError;
|
||||||
|
use crate::digest::DigestError;
|
||||||
|
|
||||||
/// Repository configuration filename
|
/// Repository configuration filename
|
||||||
pub const REPOSITORY_CONFIG_FILENAME: &str = "pkg6.repository";
|
pub const REPOSITORY_CONFIG_FILENAME: &str = "pkg6.repository";
|
||||||
|
|
@ -77,12 +229,12 @@ impl Default for RepositoryVersion {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::convert::TryFrom<u32> 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 {
|
match value {
|
||||||
4 => Ok(RepositoryVersion::V4),
|
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
|
// MPL was not distributed with this file, You can
|
||||||
// obtain one at https://mozilla.org/MPL/2.0/.
|
// obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
PackageContents, PackageInfo, PublisherInfo, ReadableRepository, RepositoryConfig,
|
PackageContents, PackageInfo, PublisherInfo, ReadableRepository, RepositoryConfig,
|
||||||
RepositoryInfo, RepositoryVersion, WritableRepository,
|
RepositoryError, RepositoryInfo, RepositoryVersion, Result, WritableRepository,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Repository implementation that uses a REST API
|
/// Repository implementation that uses a REST API
|
||||||
|
|
@ -97,7 +96,7 @@ impl WritableRepository for RestBackend {
|
||||||
// Filter publishers if specified
|
// Filter publishers if specified
|
||||||
let publishers = if let Some(pub_name) = publisher {
|
let publishers = if let Some(pub_name) = publisher {
|
||||||
if !self.config.publishers.contains(&pub_name.to_string()) {
|
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()]
|
vec![pub_name.to_string()]
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -130,7 +129,7 @@ impl WritableRepository for RestBackend {
|
||||||
// Filter publishers if specified
|
// Filter publishers if specified
|
||||||
let publishers = if let Some(pub_name) = publisher {
|
let publishers = if let Some(pub_name) = publisher {
|
||||||
if !self.config.publishers.contains(&pub_name.to_string()) {
|
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()]
|
vec![pub_name.to_string()]
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -162,7 +161,7 @@ impl WritableRepository for RestBackend {
|
||||||
|
|
||||||
// Check if the publisher exists
|
// Check if the publisher exists
|
||||||
if !self.config.publishers.contains(&publisher.to_string()) {
|
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
|
// Set the default publisher
|
||||||
|
|
@ -199,7 +198,7 @@ impl WritableRepository for RestBackend {
|
||||||
|
|
||||||
// Check if the publisher exists
|
// Check if the publisher exists
|
||||||
if !self.config.publishers.contains(&publisher.to_string()) {
|
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"
|
// Create the property key in the format "publisher/property"
|
||||||
|
|
@ -274,7 +273,7 @@ impl ReadableRepository for RestBackend {
|
||||||
// Filter publishers if specified
|
// Filter publishers if specified
|
||||||
let publishers = if let Some(pub_name) = publisher {
|
let publishers = if let Some(pub_name) = publisher {
|
||||||
if !self.config.publishers.contains(&pub_name.to_string()) {
|
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()]
|
vec![pub_name.to_string()]
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,8 @@ mod tests {
|
||||||
use crate::actions::Manifest;
|
use crate::actions::Manifest;
|
||||||
use crate::fmri::Fmri;
|
use crate::fmri::Fmri;
|
||||||
use crate::repository::{
|
use crate::repository::{
|
||||||
CatalogManager, FileBackend, ReadableRepository, RepositoryVersion, WritableRepository,
|
CatalogManager, FileBackend, ReadableRepository, RepositoryError, RepositoryVersion, Result,
|
||||||
REPOSITORY_CONFIG_FILENAME,
|
WritableRepository, REPOSITORY_CONFIG_FILENAME,
|
||||||
};
|
};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
@ -72,7 +72,7 @@ mod tests {
|
||||||
manifest_path: &PathBuf,
|
manifest_path: &PathBuf,
|
||||||
prototype_dir: &PathBuf,
|
prototype_dir: &PathBuf,
|
||||||
publisher: &str,
|
publisher: &str,
|
||||||
) -> Result<(), anyhow::Error> {
|
) -> Result<()> {
|
||||||
println!(
|
println!(
|
||||||
"Publishing package from manifest: {}",
|
"Publishing package from manifest: {}",
|
||||||
manifest_path.display()
|
manifest_path.display()
|
||||||
|
|
@ -83,18 +83,16 @@ mod tests {
|
||||||
// Check if the manifest file exists
|
// Check if the manifest file exists
|
||||||
if !manifest_path.exists() {
|
if !manifest_path.exists() {
|
||||||
println!("Error: Manifest file does not exist");
|
println!("Error: Manifest file does not exist");
|
||||||
return Err(anyhow::anyhow!(
|
return Err(RepositoryError::FileReadError(
|
||||||
"Manifest file does not exist: {}",
|
format!("Manifest file does not exist: {}", manifest_path.display())
|
||||||
manifest_path.display()
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the prototype directory exists
|
// Check if the prototype directory exists
|
||||||
if !prototype_dir.exists() {
|
if !prototype_dir.exists() {
|
||||||
println!("Error: Prototype directory does not exist");
|
println!("Error: Prototype directory does not exist");
|
||||||
return Err(anyhow::anyhow!(
|
return Err(RepositoryError::NotFound(
|
||||||
"Prototype directory does not exist: {}",
|
format!("Prototype directory does not exist: {}", prototype_dir.display())
|
||||||
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