Refactor repository traits into ReadableRepository and WritableRepository, introduce Repository to combine traits for backward compatibility, and update relevant modules and tests to use the new structure.

This commit is contained in:
Till Wegmueller 2025-07-26 10:34:45 +02:00
parent a0fcc13033
commit ead9ad12af
No known key found for this signature in database
6 changed files with 1007 additions and 784 deletions

File diff suppressed because it is too large Load diff

View file

@ -105,47 +105,20 @@ impl Default for RepositoryConfig {
}
}
/// Repository trait defining the interface for all repository backends
pub trait Repository {
/// Create a new repository at the specified path
fn create<P: AsRef<Path>>(path: P, version: RepositoryVersion) -> Result<Self> where Self: Sized;
/// Repository trait for read-only operations
pub trait ReadableRepository {
/// Open an existing repository
fn open<P: AsRef<Path>>(path: P) -> Result<Self> 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<()>;
/// Get repository information
fn get_info(&self) -> Result<RepositoryInfo>;
/// 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<()>;
/// List packages in the repository
fn list_packages(&self, publisher: Option<&str>, pattern: Option<&str>) -> Result<Vec<PackageInfo>>;
/// Show contents of packages
fn show_contents(&self, publisher: Option<&str>, pattern: Option<&str>, action_types: Option<&[String]>) -> Result<Vec<PackageContents>>;
/// 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<()>;
/// Search for packages in the repository
///
/// This method searches for packages in the repository using the search index.
@ -157,4 +130,40 @@ pub trait Repository {
/// * `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<usize>) -> Result<Vec<PackageInfo>>;
}
}
/// Repository trait for write operations
pub trait WritableRepository {
/// Create a new repository at the specified path
fn create<P: AsRef<Path>>(path: P, version: RepositoryVersion) -> Result<Self> 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 {}

View file

@ -6,7 +6,10 @@
use anyhow::{anyhow, Result};
use std::path::{Path, PathBuf};
use super::{Repository, RepositoryConfig, RepositoryVersion, PublisherInfo, RepositoryInfo, PackageInfo, PackageContents};
use super::{
PackageContents, PackageInfo, PublisherInfo, ReadableRepository, RepositoryConfig,
RepositoryInfo, RepositoryVersion, WritableRepository,
};
/// Repository implementation that uses a REST API
pub struct RestBackend {
@ -15,161 +18,82 @@ pub struct RestBackend {
pub local_cache_path: Option<PathBuf>,
}
impl Repository for RestBackend {
impl WritableRepository for RestBackend {
/// Create a new repository at the specified URI
fn create<P: AsRef<Path>>(uri: P, version: RepositoryVersion) -> Result<Self> {
// This is a stub implementation
// In a real implementation, we would make a REST API call to create the repository
let uri_str = uri.as_ref().to_string_lossy().to_string();
// Create the repository configuration
let config = RepositoryConfig {
version,
..Default::default()
};
// Create the repository structure
let repo = RestBackend {
uri: uri_str,
config,
local_cache_path: None,
};
// In a real implementation, we would make a REST API call to create the repository structure
Ok(repo)
}
/// Open an existing repository
fn open<P: AsRef<Path>>(uri: P) -> Result<Self> {
// This is a stub implementation
// In a real implementation, we would make a REST API call to get the repository configuration
let uri_str = uri.as_ref().to_string_lossy().to_string();
// In a real implementation, we would fetch the repository configuration from the REST API
// For now, we'll just create a default configuration
let config = RepositoryConfig::default();
Ok(RestBackend {
uri: uri_str,
config,
local_cache_path: None,
})
}
/// Save the repository configuration
fn save_config(&self) -> Result<()> {
// This is a stub implementation
// In a real implementation, we would make a REST API call to save the repository configuration
// For now, just return Ok
Ok(())
}
/// Add a publisher to the repository
fn add_publisher(&mut self, publisher: &str) -> Result<()> {
// This is a stub implementation
// In a real implementation, we would make a REST API call to add the publisher
if !self.config.publishers.contains(&publisher.to_string()) {
self.config.publishers.push(publisher.to_string());
// In a real implementation, we would make a REST API call to create publisher-specific resources
// Save the updated configuration
self.save_config()?;
}
Ok(())
}
/// Remove a publisher from the repository
fn remove_publisher(&mut self, publisher: &str, dry_run: bool) -> Result<()> {
// This is a stub implementation
// In a real implementation, we would make a REST API call to remove the publisher
if let Some(pos) = self.config.publishers.iter().position(|p| p == publisher) {
if !dry_run {
self.config.publishers.remove(pos);
// In a real implementation, we would make a REST API call to remove publisher-specific resources
// Save the updated configuration
self.save_config()?;
}
}
Ok(())
}
/// Get repository information
fn get_info(&self) -> Result<RepositoryInfo> {
/// Rebuild repository metadata
fn rebuild(&self, publisher: Option<&str>, no_catalog: bool, no_index: bool) -> Result<()> {
// This is a stub implementation
// In a real implementation, we would make a REST API call to get repository information
let mut publishers = Vec::new();
for publisher_name in &self.config.publishers {
// In a real implementation, we would get this information from the REST API
let package_count = 0;
let status = "online".to_string();
let updated = "2025-07-21T18:46:00.000000Z".to_string();
// Create a PublisherInfo struct and add it to the list
publishers.push(PublisherInfo {
name: publisher_name.clone(),
package_count,
status,
updated,
});
}
// Create and return a RepositoryInfo struct
Ok(RepositoryInfo { publishers })
}
/// Set a repository property
fn set_property(&mut self, property: &str, value: &str) -> Result<()> {
// This is a stub implementation
// In a real implementation, we would make a REST API call to set the property
self.config.properties.insert(property.to_string(), value.to_string());
self.save_config()?;
Ok(())
}
/// Set a publisher property
fn set_publisher_property(&mut self, publisher: &str, property: &str, value: &str) -> Result<()> {
// This is a stub implementation
// In a real implementation, we would make a REST API call to set the publisher property
// Check if the publisher exists
if !self.config.publishers.contains(&publisher.to_string()) {
return Err(anyhow!("Publisher does not exist: {}", publisher));
}
// Create the property key in the format "publisher/property"
let key = format!("{}/{}", publisher, property);
// Set the property
self.config.properties.insert(key, value.to_string());
// Save the updated configuration
self.save_config()?;
Ok(())
}
/// List packages in the repository
fn list_packages(&self, publisher: Option<&str>, pattern: Option<&str>) -> Result<Vec<PackageInfo>> {
// This is a stub implementation
// In a real implementation, we would make a REST API call to list packages
let mut packages = Vec::new();
// In a real implementation, we would make a REST API call to rebuild metadata
// Filter publishers if specified
let publishers = if let Some(pub_name) = publisher {
if !self.config.publishers.contains(&pub_name.to_string()) {
@ -179,38 +103,220 @@ impl Repository for RestBackend {
} else {
self.config.publishers.clone()
};
// For each publisher, rebuild metadata
for pub_name in publishers {
println!("Rebuilding metadata for publisher: {}", pub_name);
if !no_catalog {
println!("Rebuilding catalog...");
// In a real implementation, we would make a REST API call to rebuild the catalog
}
if !no_index {
println!("Rebuilding search index...");
// In a real implementation, we would make a REST API call to rebuild the search index
}
}
Ok(())
}
/// Refresh repository metadata
fn refresh(&self, publisher: Option<&str>, no_catalog: bool, no_index: bool) -> Result<()> {
// This is a stub implementation
// In a real implementation, we would make a REST API call to refresh metadata
// 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));
}
vec![pub_name.to_string()]
} else {
self.config.publishers.clone()
};
// For each publisher, refresh metadata
for pub_name in publishers {
println!("Refreshing metadata for publisher: {}", pub_name);
if !no_catalog {
println!("Refreshing catalog...");
// In a real implementation, we would make a REST API call to refresh the catalog
}
if !no_index {
println!("Refreshing search index...");
// In a real implementation, we would make a REST API call to refresh the search index
}
}
Ok(())
}
/// Set the default publisher for the repository
fn set_default_publisher(&mut self, publisher: &str) -> Result<()> {
// This is a stub implementation
// In a real implementation, we would make a REST API call to set the default publisher
// Check if the publisher exists
if !self.config.publishers.contains(&publisher.to_string()) {
return Err(anyhow!("Publisher does not exist: {}", publisher));
}
// Set the default publisher
self.config.default_publisher = Some(publisher.to_string());
// Save the updated configuration
self.save_config()?;
Ok(())
}
/// Set a repository property
fn set_property(&mut self, property: &str, value: &str) -> Result<()> {
// This is a stub implementation
// In a real implementation, we would make a REST API call to set the property
self.config
.properties
.insert(property.to_string(), value.to_string());
self.save_config()?;
Ok(())
}
/// Set a publisher property
fn set_publisher_property(
&mut self,
publisher: &str,
property: &str,
value: &str,
) -> Result<()> {
// This is a stub implementation
// In a real implementation, we would make a REST API call to set the publisher property
// Check if the publisher exists
if !self.config.publishers.contains(&publisher.to_string()) {
return Err(anyhow!("Publisher does not exist: {}", publisher));
}
// Create the property key in the format "publisher/property"
let key = format!("{}/{}", publisher, property);
// Set the property
self.config.properties.insert(key, value.to_string());
// Save the updated configuration
self.save_config()?;
Ok(())
}
}
impl ReadableRepository for RestBackend {
/// Open an existing repository
fn open<P: AsRef<Path>>(uri: P) -> Result<Self> {
// This is a stub implementation
// In a real implementation, we would make a REST API call to get the repository configuration
let uri_str = uri.as_ref().to_string_lossy().to_string();
// In a real implementation, we would fetch the repository configuration from the REST API
// For now, we'll just create a default configuration
let config = RepositoryConfig::default();
Ok(RestBackend {
uri: uri_str,
config,
local_cache_path: None,
})
}
/// Get repository information
fn get_info(&self) -> Result<RepositoryInfo> {
// This is a stub implementation
// In a real implementation, we would make a REST API call to get repository information
let mut publishers = Vec::new();
for publisher_name in &self.config.publishers {
// In a real implementation, we would get this information from the REST API
let package_count = 0;
let status = "online".to_string();
let updated = "2025-07-21T18:46:00.000000Z".to_string();
// Create a PublisherInfo struct and add it to the list
publishers.push(PublisherInfo {
name: publisher_name.clone(),
package_count,
status,
updated,
});
}
// Create and return a RepositoryInfo struct
Ok(RepositoryInfo { publishers })
}
/// List packages in the repository
fn list_packages(
&self,
publisher: Option<&str>,
pattern: Option<&str>,
) -> Result<Vec<PackageInfo>> {
// This is a stub implementation
// In a real implementation, we would make a REST API call to list packages
let mut packages = Vec::new();
// 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));
}
vec![pub_name.to_string()]
} else {
self.config.publishers.clone()
};
// For each publisher, list packages
for pub_name in publishers {
// In a real implementation, we would make a REST API call to get package information
// The API call would return a list of packages with their names, versions, and other metadata
// We would then parse this information and create PackageInfo structs
// For now, we return an empty list since we don't want to return placeholder data
// and we don't have a real API to call
// If pattern filtering is needed, it would be applied here to the results from the API
// When implementing, use the regex crate to handle user-provided regexp patterns properly,
// similar to the implementation in file_backend.rs
}
Ok(packages)
}
/// Show contents of packages
fn show_contents(&self, publisher: Option<&str>, pattern: Option<&str>, action_types: Option<&[String]>) -> Result<Vec<PackageContents>> {
fn show_contents(
&self,
publisher: Option<&str>,
pattern: Option<&str>,
action_types: Option<&[String]>,
) -> Result<Vec<PackageContents>> {
// This is a stub implementation
// In a real implementation, we would make a REST API call to get package contents
// Get the list of packages
let packages = self.list_packages(publisher, pattern)?;
// For each package, create a PackageContents struct
let mut package_contents = Vec::new();
for pkg_info in packages {
// In a real implementation, we would get this information from the REST API
// Format the package identifier using the FMRI
let version = pkg_info.fmri.version();
let pkg_id = if !version.is_empty() {
@ -218,12 +324,14 @@ impl Repository for RestBackend {
} else {
pkg_info.fmri.stem().to_string()
};
// Example content for each type
// In a real implementation, we would get this information from the REST API
// Files
let files = if action_types.is_none() || action_types.as_ref().unwrap().contains(&"file".to_string()) {
let files = if action_types.is_none()
|| action_types.as_ref().unwrap().contains(&"file".to_string())
{
Some(vec![
"/usr/bin/example".to_string(),
"/usr/lib/example.so".to_string(),
@ -231,9 +339,11 @@ impl Repository for RestBackend {
} else {
None
};
// Directories
let directories = if action_types.is_none() || action_types.as_ref().unwrap().contains(&"dir".to_string()) {
let directories = if action_types.is_none()
|| action_types.as_ref().unwrap().contains(&"dir".to_string())
{
Some(vec![
"/usr/share/doc/example".to_string(),
"/usr/share/man/man1".to_string(),
@ -241,34 +351,40 @@ impl Repository for RestBackend {
} else {
None
};
// Links
let links = if action_types.is_none() || action_types.as_ref().unwrap().contains(&"link".to_string()) {
Some(vec![
"/usr/bin/example-link".to_string(),
])
let links = if action_types.is_none()
|| action_types.as_ref().unwrap().contains(&"link".to_string())
{
Some(vec!["/usr/bin/example-link".to_string()])
} else {
None
};
// Dependencies
let dependencies = if action_types.is_none() || action_types.as_ref().unwrap().contains(&"depend".to_string()) {
Some(vec![
"pkg:/system/library@0.5.11".to_string(),
])
let dependencies = if action_types.is_none()
|| action_types
.as_ref()
.unwrap()
.contains(&"depend".to_string())
{
Some(vec!["pkg:/system/library@0.5.11".to_string()])
} else {
None
};
// Licenses
let licenses = if action_types.is_none() || action_types.as_ref().unwrap().contains(&"license".to_string()) {
Some(vec![
"/usr/share/licenses/example/LICENSE".to_string(),
])
let licenses = if action_types.is_none()
|| action_types
.as_ref()
.unwrap()
.contains(&"license".to_string())
{
Some(vec!["/usr/share/licenses/example/LICENSE".to_string()])
} else {
None
};
// Add the package contents to the result
package_contents.push(PackageContents {
package_id: pkg_id,
@ -279,96 +395,16 @@ impl Repository for RestBackend {
licenses,
});
}
Ok(package_contents)
}
/// Rebuild repository metadata
fn rebuild(&self, publisher: Option<&str>, no_catalog: bool, no_index: bool) -> Result<()> {
// This is a stub implementation
// In a real implementation, we would make a REST API call to rebuild metadata
// 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));
}
vec![pub_name.to_string()]
} else {
self.config.publishers.clone()
};
// For each publisher, rebuild metadata
for pub_name in publishers {
println!("Rebuilding metadata for publisher: {}", pub_name);
if !no_catalog {
println!("Rebuilding catalog...");
// In a real implementation, we would make a REST API call to rebuild the catalog
}
if !no_index {
println!("Rebuilding search index...");
// In a real implementation, we would make a REST API call to rebuild the search index
}
}
Ok(())
}
/// Refresh repository metadata
fn refresh(&self, publisher: Option<&str>, no_catalog: bool, no_index: bool) -> Result<()> {
// This is a stub implementation
// In a real implementation, we would make a REST API call to refresh metadata
// 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));
}
vec![pub_name.to_string()]
} else {
self.config.publishers.clone()
};
// For each publisher, refresh metadata
for pub_name in publishers {
println!("Refreshing metadata for publisher: {}", pub_name);
if !no_catalog {
println!("Refreshing catalog...");
// In a real implementation, we would make a REST API call to refresh the catalog
}
if !no_index {
println!("Refreshing search index...");
// In a real implementation, we would make a REST API call to refresh the search index
}
}
Ok(())
}
/// Set the default publisher for the repository
fn set_default_publisher(&mut self, publisher: &str) -> Result<()> {
// This is a stub implementation
// In a real implementation, we would make a REST API call to set the default publisher
// Check if the publisher exists
if !self.config.publishers.contains(&publisher.to_string()) {
return Err(anyhow!("Publisher does not exist: {}", publisher));
}
// Set the default publisher
self.config.default_publisher = Some(publisher.to_string());
// Save the updated configuration
self.save_config()?;
Ok(())
}
fn search(&self, query: &str, publisher: Option<&str>, limit: Option<usize>) -> Result<Vec<PackageInfo>> {
fn search(
&self,
query: &str,
publisher: Option<&str>,
limit: Option<usize>,
) -> Result<Vec<PackageInfo>> {
todo!()
}
}
@ -379,4 +415,4 @@ impl RestBackend {
self.local_cache_path = Some(path.as_ref().to_path_buf());
Ok(())
}
}
}

View file

@ -1,6 +1,6 @@
use clap::{Parser, Subcommand};
use libips::actions::{ActionError, File, Manifest};
use libips::repository::{Repository, FileBackend};
use libips::repository::{ReadableRepository, WritableRepository, FileBackend};
use anyhow::{Result, anyhow};
use std::collections::HashMap;

View file

@ -1,9 +1,9 @@
use anyhow::{anyhow, Result};
use clap::{Parser, Subcommand};
use anyhow::{Result, anyhow};
use std::path::PathBuf;
use std::convert::TryFrom;
use std::path::PathBuf;
use libips::repository::{Repository, RepositoryVersion, FileBackend, PublisherInfo, RepositoryInfo, PackageInfo, PackageContents};
use libips::repository::{FileBackend, ReadableRepository, RepositoryVersion, WritableRepository};
#[cfg(test)]
mod tests;

View file

@ -1,6 +1,6 @@
#[cfg(test)]
mod tests {
use libips::repository::{Repository, RepositoryVersion, FileBackend, REPOSITORY_CONFIG_FILENAME, PublisherInfo, RepositoryInfo};
use libips::repository::{ReadableRepository, WritableRepository, RepositoryVersion, FileBackend, REPOSITORY_CONFIG_FILENAME, PublisherInfo, RepositoryInfo};
use std::path::PathBuf;
use std::fs;