refactor: add upm application facade and module api
This commit is contained in:
parent
005d6ebfdb
commit
e2a01d3095
36 changed files with 1058 additions and 607 deletions
|
|
@ -17,6 +17,8 @@ serde.workspace = true
|
|||
serde_yaml.workspace = true
|
||||
sha2.workspace = true
|
||||
toml.workspace = true
|
||||
upm-appimage = { path = "../upm-appimage" }
|
||||
upm-module-api = { path = "../upm-module-api" }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile.workspace = true
|
||||
|
|
|
|||
|
|
@ -1,72 +1 @@
|
|||
use crate::domain::source::{ResolvedRelease, SourceKind, SourceRef};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub struct AdapterCapabilities {
|
||||
pub supports_search: bool,
|
||||
pub supports_exact_resolution: bool,
|
||||
}
|
||||
|
||||
impl AdapterCapabilities {
|
||||
pub fn exact_resolution_only() -> Self {
|
||||
Self {
|
||||
supports_search: false,
|
||||
supports_exact_resolution: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct AdapterResolution {
|
||||
pub source: SourceRef,
|
||||
pub release: ResolvedRelease,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum AdapterResolveOutcome {
|
||||
Resolved(AdapterResolution),
|
||||
NoInstallableArtifact { source: SourceRef },
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum AdapterError {
|
||||
UnsupportedQuery,
|
||||
UnsupportedSource,
|
||||
ResolutionFailed(String),
|
||||
}
|
||||
|
||||
pub trait SourceAdapter {
|
||||
fn id(&self) -> &'static str;
|
||||
|
||||
fn capabilities(&self) -> AdapterCapabilities;
|
||||
|
||||
fn repository_source_kind(&self) -> Option<SourceKind> {
|
||||
None
|
||||
}
|
||||
|
||||
fn exact_source_kind(&self) -> Option<SourceKind> {
|
||||
None
|
||||
}
|
||||
|
||||
fn normalize(&self, query: &str) -> Result<SourceRef, AdapterError>;
|
||||
|
||||
fn resolve(&self, source: &SourceRef) -> Result<AdapterResolution, AdapterError>;
|
||||
|
||||
fn resolve_supported_source(
|
||||
&self,
|
||||
source: &SourceRef,
|
||||
) -> Result<AdapterResolveOutcome, AdapterError> {
|
||||
self.resolve(source).map(AdapterResolveOutcome::Resolved)
|
||||
}
|
||||
|
||||
fn supports_source(&self, source: &SourceRef) -> bool {
|
||||
crate::adapters::supports_source(self, source)
|
||||
}
|
||||
|
||||
fn resolve_source(&self, source: &SourceRef) -> Result<AdapterResolveOutcome, AdapterError> {
|
||||
if !self.supports_source(source) {
|
||||
return Err(AdapterError::UnsupportedSource);
|
||||
}
|
||||
|
||||
self.resolve_supported_source(source)
|
||||
}
|
||||
}
|
||||
pub use upm_module_api::adapters::traits::*;
|
||||
|
|
|
|||
182
crates/upm-core/src/app/application.rs
Normal file
182
crates/upm-core/src/app/application.rs
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
use std::path::Path;
|
||||
|
||||
use crate::app::add::{
|
||||
AddPlan, AddSecurityPolicy, BuildAddPlanError, InstalledApp,
|
||||
build_add_plan_with_registered_providers,
|
||||
build_add_plan_with_reporter_and_registered_providers, install_app_with_reporter,
|
||||
};
|
||||
use crate::app::list::{ListRow, build_list_rows};
|
||||
use crate::app::progress::ProgressReporter;
|
||||
use crate::app::providers::ProviderRegistry;
|
||||
use crate::app::remove::{RemovalResult, RemoveRegisteredAppError, remove_registered_app};
|
||||
use crate::app::search::{SearchError, SearchProvider, build_search_results_with};
|
||||
use crate::app::show::build_show_result_with;
|
||||
use crate::app::update::{
|
||||
BuildUpdatePlanError, ExecuteUpdatesError, build_update_plan,
|
||||
execute_updates_with_reporter_and_policy,
|
||||
};
|
||||
use crate::domain::app::{AppRecord, InstallScope};
|
||||
use crate::domain::search::{SearchQuery, SearchResults};
|
||||
use crate::domain::show::{InstalledShow, ShowResult, ShowResultError};
|
||||
use crate::domain::update::{UpdateExecutionResult, UpdatePlan};
|
||||
use crate::source::github::{GitHubTransport, default_transport};
|
||||
|
||||
pub struct UpmApp<'a> {
|
||||
github_transport: Box<dyn GitHubTransport>,
|
||||
providers: ProviderRegistry<'a>,
|
||||
}
|
||||
|
||||
pub struct UpmAppBuilder<'a> {
|
||||
github_transport: Option<Box<dyn GitHubTransport>>,
|
||||
providers: ProviderRegistry<'a>,
|
||||
}
|
||||
|
||||
impl UpmApp<'static> {
|
||||
pub fn new() -> Self {
|
||||
Self::builder()
|
||||
.with_provider_registry(default_provider_registry())
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for UpmApp<'static> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> UpmApp<'a> {
|
||||
pub fn builder() -> UpmAppBuilder<'a> {
|
||||
UpmAppBuilder {
|
||||
github_transport: None,
|
||||
providers: ProviderRegistry::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn search(
|
||||
&self,
|
||||
query: &SearchQuery,
|
||||
installed_apps: &[AppRecord],
|
||||
) -> Result<SearchResults, SearchError> {
|
||||
let github_provider =
|
||||
crate::app::search::GitHubSearchProvider::new(self.github_transport.as_ref());
|
||||
let mut resolved_providers = vec![&github_provider as &dyn SearchProvider];
|
||||
resolved_providers.extend(
|
||||
self.providers
|
||||
.search_providers
|
||||
.iter()
|
||||
.map(|provider| provider.as_ref() as &dyn SearchProvider),
|
||||
);
|
||||
build_search_results_with(query, installed_apps, &resolved_providers)
|
||||
}
|
||||
|
||||
pub fn build_add_plan(
|
||||
&self,
|
||||
query: &str,
|
||||
policy: AddSecurityPolicy,
|
||||
) -> Result<AddPlan, BuildAddPlanError> {
|
||||
build_add_plan_with_registered_providers(
|
||||
query,
|
||||
self.github_transport.as_ref(),
|
||||
&self.providers,
|
||||
policy,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn build_add_plan_with_reporter(
|
||||
&self,
|
||||
query: &str,
|
||||
reporter: &mut impl ProgressReporter,
|
||||
policy: AddSecurityPolicy,
|
||||
) -> Result<AddPlan, BuildAddPlanError> {
|
||||
build_add_plan_with_reporter_and_registered_providers(
|
||||
query,
|
||||
self.github_transport.as_ref(),
|
||||
reporter,
|
||||
&self.providers,
|
||||
policy,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn install_app(
|
||||
&self,
|
||||
query: &str,
|
||||
plan: &AddPlan,
|
||||
install_home: &Path,
|
||||
requested_scope: InstallScope,
|
||||
reporter: &mut impl ProgressReporter,
|
||||
) -> Result<InstalledApp, crate::app::add::InstallAppError> {
|
||||
install_app_with_reporter(query, plan, install_home, requested_scope, reporter)
|
||||
}
|
||||
|
||||
pub fn show(
|
||||
&self,
|
||||
query: &str,
|
||||
installed_apps: &[AppRecord],
|
||||
) -> Result<ShowResult, ShowResultError> {
|
||||
build_show_result_with(query, installed_apps, self.github_transport.as_ref())
|
||||
}
|
||||
|
||||
pub fn show_all(&self, installed_apps: &[AppRecord]) -> Vec<InstalledShow> {
|
||||
crate::app::show::build_installed_show_results(installed_apps)
|
||||
}
|
||||
|
||||
pub fn list(&self, apps: &[AppRecord]) -> Vec<ListRow> {
|
||||
build_list_rows(apps)
|
||||
}
|
||||
|
||||
pub fn build_update_plan(
|
||||
&self,
|
||||
apps: &[AppRecord],
|
||||
) -> Result<UpdatePlan, BuildUpdatePlanError> {
|
||||
build_update_plan(apps)
|
||||
}
|
||||
|
||||
pub fn execute_updates(
|
||||
&self,
|
||||
apps: &[AppRecord],
|
||||
install_home: &Path,
|
||||
reporter: &mut impl ProgressReporter,
|
||||
policy: AddSecurityPolicy,
|
||||
) -> Result<UpdateExecutionResult, ExecuteUpdatesError> {
|
||||
execute_updates_with_reporter_and_policy(apps, install_home, reporter, policy)
|
||||
}
|
||||
|
||||
pub fn remove_registered_app(
|
||||
&self,
|
||||
query: &str,
|
||||
apps: &[AppRecord],
|
||||
install_home: &Path,
|
||||
) -> Result<RemovalResult, RemoveRegisteredAppError> {
|
||||
remove_registered_app(query, apps, install_home)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> UpmAppBuilder<'a> {
|
||||
pub fn with_github_transport(mut self, github_transport: Box<dyn GitHubTransport>) -> Self {
|
||||
self.github_transport = Some(github_transport);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_provider_registry(mut self, providers: ProviderRegistry<'a>) -> Self {
|
||||
self.providers = providers;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> UpmApp<'a> {
|
||||
UpmApp {
|
||||
github_transport: self.github_transport.unwrap_or_else(default_transport),
|
||||
providers: self.providers,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn default_provider_registry() -> ProviderRegistry<'static> {
|
||||
ProviderRegistry::default()
|
||||
.with_search_provider(upm_appimage::AppImageHubSearchProvider::new(
|
||||
upm_appimage::source::appimagehub::default_transport(),
|
||||
))
|
||||
.with_external_add_provider(upm_appimage::AppImageHubAddProvider::new(
|
||||
upm_appimage::source::appimagehub::default_transport(),
|
||||
))
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
pub mod add;
|
||||
pub mod application;
|
||||
pub mod identity;
|
||||
pub mod interaction;
|
||||
pub mod list;
|
||||
|
|
@ -10,3 +11,5 @@ pub mod scope;
|
|||
pub mod search;
|
||||
pub mod show;
|
||||
pub mod update;
|
||||
|
||||
pub use application::{UpmApp, UpmAppBuilder};
|
||||
|
|
|
|||
|
|
@ -1,24 +1 @@
|
|||
use crate::adapters::traits::{AdapterError, AdapterResolution};
|
||||
use crate::app::search::SearchProvider;
|
||||
use crate::domain::source::SourceRef;
|
||||
use crate::domain::update::{ArtifactCandidate, UpdateStrategy};
|
||||
|
||||
pub trait ExternalAddProvider {
|
||||
fn id(&self) -> &'static str;
|
||||
|
||||
fn resolve(&self, source: &SourceRef) -> Result<Option<ExternalAddResolution>, AdapterError>;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct ExternalAddResolution {
|
||||
pub resolution: AdapterResolution,
|
||||
pub selected_artifact: ArtifactCandidate,
|
||||
pub update_strategy: UpdateStrategy,
|
||||
pub display_name_hint: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ProviderRegistry<'a> {
|
||||
pub search_providers: Vec<&'a dyn SearchProvider>,
|
||||
pub external_add_providers: Vec<&'a dyn ExternalAddProvider>,
|
||||
}
|
||||
pub use upm_module_api::app::providers::*;
|
||||
|
|
|
|||
|
|
@ -9,25 +9,7 @@ use crate::source::github::{
|
|||
search_github_repositories_with,
|
||||
};
|
||||
use std::collections::HashSet;
|
||||
|
||||
pub trait SearchProvider {
|
||||
fn search(&self, query: &SearchQuery) -> Result<Vec<SearchResult>, SearchProviderError>;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct SearchProviderError {
|
||||
pub provider_id: String,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
impl SearchProviderError {
|
||||
pub fn new(provider_id: &str, message: &str) -> Self {
|
||||
Self {
|
||||
provider_id: provider_id.to_owned(),
|
||||
message: message.to_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
pub use upm_module_api::app::search::{SearchProvider, SearchProviderError};
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum SearchError {
|
||||
|
|
@ -53,7 +35,12 @@ pub fn build_search_results_with_registered_providers(
|
|||
let github_transport = default_transport();
|
||||
let github_provider = GitHubSearchProvider::new(github_transport.as_ref());
|
||||
let mut resolved_providers = vec![&github_provider as &dyn SearchProvider];
|
||||
resolved_providers.extend(providers.search_providers.iter().copied());
|
||||
resolved_providers.extend(
|
||||
providers
|
||||
.search_providers
|
||||
.iter()
|
||||
.map(|provider| provider.as_ref() as &dyn SearchProvider),
|
||||
);
|
||||
|
||||
build_search_results_with(query, installed_apps, &resolved_providers)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,68 +1 @@
|
|||
pub const DEFAULT_REMOTE_LIMIT: usize = 10;
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum SearchInstallStatus {
|
||||
Available,
|
||||
Installed {
|
||||
installed_version: Option<String>,
|
||||
},
|
||||
UpdateAvailable {
|
||||
installed_version: Option<String>,
|
||||
latest_version: Option<String>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct SearchQuery {
|
||||
pub text: String,
|
||||
pub remote_limit: usize,
|
||||
}
|
||||
|
||||
impl SearchQuery {
|
||||
pub fn new(text: &str) -> Self {
|
||||
Self {
|
||||
text: text.to_owned(),
|
||||
remote_limit: DEFAULT_REMOTE_LIMIT,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_remote_limit(text: &str, remote_limit: usize) -> Self {
|
||||
Self {
|
||||
text: text.to_owned(),
|
||||
remote_limit,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct SearchResult {
|
||||
pub provider_id: String,
|
||||
pub display_name: String,
|
||||
pub description: Option<String>,
|
||||
pub source_locator: String,
|
||||
pub install_query: String,
|
||||
pub canonical_locator: String,
|
||||
pub version: Option<String>,
|
||||
pub install_status: SearchInstallStatus,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct InstalledSearchMatch {
|
||||
pub stable_id: String,
|
||||
pub display_name: String,
|
||||
pub installed_version: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct SearchWarning {
|
||||
pub provider_id: Option<String>,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct SearchResults {
|
||||
pub query_text: String,
|
||||
pub remote_hits: Vec<SearchResult>,
|
||||
pub installed_matches: Vec<InstalledSearchMatch>,
|
||||
pub warnings: Vec<SearchWarning>,
|
||||
}
|
||||
pub use upm_module_api::domain::search::*;
|
||||
|
|
|
|||
|
|
@ -1,117 +1 @@
|
|||
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||
pub enum SourceKind {
|
||||
GitHub,
|
||||
GitLab,
|
||||
AppImageHub,
|
||||
SourceForge,
|
||||
DirectUrl,
|
||||
File,
|
||||
}
|
||||
|
||||
impl SourceKind {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::GitHub => "github",
|
||||
Self::GitLab => "gitlab",
|
||||
Self::AppImageHub => "appimagehub",
|
||||
Self::SourceForge => "sourceforge",
|
||||
Self::DirectUrl => "direct-url",
|
||||
Self::File => "file",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||
pub enum SourceInputKind {
|
||||
RepoShorthand,
|
||||
GitHubRepositoryUrl,
|
||||
GitHubReleaseUrl,
|
||||
GitHubReleaseAssetUrl,
|
||||
GitLabUrl,
|
||||
AppImageHubUrl,
|
||||
AppImageHubShorthand,
|
||||
SourceForgeUrl,
|
||||
DirectUrl,
|
||||
File,
|
||||
}
|
||||
|
||||
impl SourceInputKind {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::RepoShorthand => "repo-shorthand",
|
||||
Self::GitHubRepositoryUrl => "github-repository-url",
|
||||
Self::GitHubReleaseUrl => "github-release-url",
|
||||
Self::GitHubReleaseAssetUrl => "github-release-asset-url",
|
||||
Self::GitLabUrl => "gitlab-url",
|
||||
Self::AppImageHubUrl => "appimagehub-url",
|
||||
Self::AppImageHubShorthand => "appimagehub-shorthand",
|
||||
Self::SourceForgeUrl => "sourceforge-url",
|
||||
Self::DirectUrl => "direct-url",
|
||||
Self::File => "file",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||
pub enum NormalizedSourceKind {
|
||||
GitHubRepository,
|
||||
GitHubRelease,
|
||||
GitHubReleaseAsset,
|
||||
GitLab,
|
||||
GitLabCandidate,
|
||||
AppImageHub,
|
||||
SourceForge,
|
||||
SourceForgeCandidate,
|
||||
DirectUrl,
|
||||
File,
|
||||
}
|
||||
|
||||
impl NormalizedSourceKind {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::GitHubRepository => "github-repository",
|
||||
Self::GitHubRelease => "github-release",
|
||||
Self::GitHubReleaseAsset => "github-release-asset",
|
||||
Self::GitLab => "gitlab",
|
||||
Self::GitLabCandidate => "gitlab-candidate",
|
||||
Self::AppImageHub => "appimagehub",
|
||||
Self::SourceForge => "sourceforge",
|
||||
Self::SourceForgeCandidate => "sourceforge-candidate",
|
||||
Self::DirectUrl => "direct-url",
|
||||
Self::File => "file",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||
pub struct SourceRef {
|
||||
pub kind: SourceKind,
|
||||
pub locator: String,
|
||||
#[serde(default = "default_source_input_kind")]
|
||||
pub input_kind: SourceInputKind,
|
||||
#[serde(default = "default_normalized_source_kind")]
|
||||
pub normalized_kind: NormalizedSourceKind,
|
||||
#[serde(default)]
|
||||
pub canonical_locator: Option<String>,
|
||||
#[serde(default)]
|
||||
pub requested_tag: Option<String>,
|
||||
#[serde(default)]
|
||||
pub requested_asset_name: Option<String>,
|
||||
#[serde(default)]
|
||||
pub tracks_latest: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||
pub struct ResolvedRelease {
|
||||
pub version: String,
|
||||
#[serde(default)]
|
||||
pub prerelease: bool,
|
||||
}
|
||||
|
||||
const fn default_source_input_kind() -> SourceInputKind {
|
||||
SourceInputKind::DirectUrl
|
||||
}
|
||||
|
||||
const fn default_normalized_source_kind() -> NormalizedSourceKind {
|
||||
NormalizedSourceKind::DirectUrl
|
||||
}
|
||||
pub use upm_module_api::domain::source::*;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
use crate::domain::app::AppRecord;
|
||||
pub use upm_module_api::domain::update::{
|
||||
ArtifactCandidate, ChannelPreference, UpdateChannelKind, UpdateStrategy,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||
pub enum ParsedMetadataKind {
|
||||
|
|
@ -31,25 +34,6 @@ pub struct ParsedMetadata {
|
|||
pub confidence: u8,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||
pub enum UpdateChannelKind {
|
||||
GitHubReleases,
|
||||
ElectronBuilder,
|
||||
Zsync,
|
||||
DirectAsset,
|
||||
}
|
||||
|
||||
impl UpdateChannelKind {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::GitHubReleases => "github-releases",
|
||||
Self::ElectronBuilder => "electron-builder",
|
||||
Self::Zsync => "zsync",
|
||||
Self::DirectAsset => "direct-asset-lineage",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||
pub struct UpdateChannel {
|
||||
pub kind: UpdateChannelKind,
|
||||
|
|
@ -66,30 +50,6 @@ pub struct UpdateChannel {
|
|||
pub prerelease: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||
pub struct ChannelPreference {
|
||||
pub kind: UpdateChannelKind,
|
||||
pub locator: String,
|
||||
pub reason: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||
pub struct UpdateStrategy {
|
||||
pub preferred: ChannelPreference,
|
||||
#[serde(default)]
|
||||
pub alternates: Vec<ChannelPreference>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct ArtifactCandidate {
|
||||
pub url: String,
|
||||
pub version: String,
|
||||
pub arch: Option<String>,
|
||||
pub trusted_checksum: Option<String>,
|
||||
pub weak_checksum_md5: Option<String>,
|
||||
pub selection_reason: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct UpdatePlan {
|
||||
pub items: Vec<PlannedUpdate>,
|
||||
|
|
|
|||
|
|
@ -9,3 +9,4 @@ pub mod source;
|
|||
pub mod update;
|
||||
|
||||
pub use app::providers::{ExternalAddProvider, ExternalAddResolution, ProviderRegistry};
|
||||
pub use app::{UpmApp, UpmAppBuilder};
|
||||
|
|
|
|||
124
crates/upm-core/tests/application_facade.rs
Normal file
124
crates/upm-core/tests/application_facade.rs
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
use upm_core::adapters::traits::AdapterResolution;
|
||||
use upm_core::app::UpmApp;
|
||||
use upm_core::app::add::AddSecurityPolicy;
|
||||
use upm_core::app::providers::{ExternalAddProvider, ExternalAddResolution, ProviderRegistry};
|
||||
use upm_core::app::search::{SearchProvider, SearchProviderError};
|
||||
use upm_core::domain::search::{SearchInstallStatus, SearchQuery, SearchResult};
|
||||
use upm_core::domain::source::{
|
||||
NormalizedSourceKind, ResolvedRelease, SourceInputKind, SourceKind, SourceRef,
|
||||
};
|
||||
use upm_core::domain::update::{
|
||||
ArtifactCandidate, ChannelPreference, UpdateChannelKind, UpdateStrategy,
|
||||
};
|
||||
use upm_core::source::github::FixtureGitHubTransport;
|
||||
|
||||
struct StubSearchProvider;
|
||||
|
||||
impl SearchProvider for StubSearchProvider {
|
||||
fn search(&self, _query: &SearchQuery) -> Result<Vec<SearchResult>, SearchProviderError> {
|
||||
Ok(vec![SearchResult {
|
||||
provider_id: "external-search".to_owned(),
|
||||
display_name: "Firefox Nightly".to_owned(),
|
||||
description: Some("Provided by facade-owned providers".to_owned()),
|
||||
source_locator: "https://example.invalid/firefox-nightly".to_owned(),
|
||||
install_query: "external/firefox-nightly".to_owned(),
|
||||
canonical_locator: "external/firefox-nightly".to_owned(),
|
||||
version: Some("2026.03.21".to_owned()),
|
||||
install_status: SearchInstallStatus::Available,
|
||||
}])
|
||||
}
|
||||
}
|
||||
|
||||
struct StubExternalAddProvider;
|
||||
|
||||
impl ExternalAddProvider for StubExternalAddProvider {
|
||||
fn id(&self) -> &'static str {
|
||||
"stub-appimage"
|
||||
}
|
||||
|
||||
fn resolve(
|
||||
&self,
|
||||
source: &SourceRef,
|
||||
) -> Result<Option<ExternalAddResolution>, upm_core::adapters::traits::AdapterError> {
|
||||
Ok(
|
||||
(source.kind == SourceKind::AppImageHub).then(|| ExternalAddResolution {
|
||||
resolution: AdapterResolution {
|
||||
source: SourceRef {
|
||||
kind: SourceKind::AppImageHub,
|
||||
locator: source.locator.clone(),
|
||||
input_kind: SourceInputKind::AppImageHubShorthand,
|
||||
normalized_kind: NormalizedSourceKind::AppImageHub,
|
||||
canonical_locator: Some("2338455".to_owned()),
|
||||
requested_tag: None,
|
||||
requested_asset_name: None,
|
||||
tracks_latest: true,
|
||||
},
|
||||
release: ResolvedRelease {
|
||||
version: "stable".to_owned(),
|
||||
prerelease: false,
|
||||
},
|
||||
},
|
||||
selected_artifact: ArtifactCandidate {
|
||||
url: "https://downloads.example.invalid/firefox.AppImage".to_owned(),
|
||||
version: "stable".to_owned(),
|
||||
arch: Some("x86_64".to_owned()),
|
||||
trusted_checksum: None,
|
||||
weak_checksum_md5: Some("deadbeef".to_owned()),
|
||||
selection_reason: "provider-release".to_owned(),
|
||||
},
|
||||
update_strategy: UpdateStrategy {
|
||||
preferred: ChannelPreference {
|
||||
kind: UpdateChannelKind::DirectAsset,
|
||||
locator: "https://downloads.example.invalid/firefox.AppImage".to_owned(),
|
||||
reason: "provider-release".to_owned(),
|
||||
},
|
||||
alternates: Vec::new(),
|
||||
},
|
||||
display_name_hint: Some(
|
||||
"Firefox by Mozilla - Official AppImage Edition".to_owned(),
|
||||
),
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn upm_app_can_be_constructed_without_cli_owned_module_composition() {
|
||||
let _app = UpmApp::new();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn upm_app_search_delegates_through_the_application_facade() {
|
||||
let app = UpmApp::builder()
|
||||
.with_github_transport(Box::new(FixtureGitHubTransport))
|
||||
.with_provider_registry(
|
||||
ProviderRegistry::default().with_search_provider(StubSearchProvider),
|
||||
)
|
||||
.build();
|
||||
|
||||
let results = app.search(&SearchQuery::new("firefox"), &[]).unwrap();
|
||||
|
||||
assert!(results.remote_hits.iter().any(|hit| {
|
||||
hit.provider_id == "external-search" && hit.install_query == "external/firefox-nightly"
|
||||
}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn upm_app_add_planning_delegates_through_the_application_facade() {
|
||||
let app = UpmApp::builder()
|
||||
.with_github_transport(Box::new(FixtureGitHubTransport))
|
||||
.with_provider_registry(
|
||||
ProviderRegistry::default().with_external_add_provider(StubExternalAddProvider),
|
||||
)
|
||||
.build();
|
||||
|
||||
let plan = app
|
||||
.build_add_plan("appimagehub/2338455", AddSecurityPolicy::default())
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(plan.resolution.source.kind, SourceKind::AppImageHub);
|
||||
assert_eq!(
|
||||
plan.selected_artifact.url,
|
||||
"https://downloads.example.invalid/firefox.AppImage"
|
||||
);
|
||||
}
|
||||
|
|
@ -86,11 +86,7 @@ impl ExternalAddProvider for StubExternalAddProvider {
|
|||
#[test]
|
||||
fn build_search_results_with_registered_providers_uses_external_hits() {
|
||||
let query = SearchQuery::new("firefox");
|
||||
let search_provider = StubSearchProvider;
|
||||
let providers = ProviderRegistry {
|
||||
search_providers: vec![&search_provider],
|
||||
external_add_providers: Vec::new(),
|
||||
};
|
||||
let providers = ProviderRegistry::default().with_search_provider(StubSearchProvider);
|
||||
|
||||
let results = build_search_results_with_registered_providers(&query, &[], &providers).unwrap();
|
||||
|
||||
|
|
@ -129,11 +125,7 @@ fn build_add_plan_with_registered_providers_requires_external_provider_for_appim
|
|||
|
||||
#[test]
|
||||
fn build_add_plan_with_registered_providers_delegates_appimagehub_like_sources() {
|
||||
let provider = StubExternalAddProvider;
|
||||
let registry = ProviderRegistry {
|
||||
search_providers: Vec::new(),
|
||||
external_add_providers: vec![&provider],
|
||||
};
|
||||
let registry = ProviderRegistry::default().with_external_add_provider(StubExternalAddProvider);
|
||||
|
||||
let plan = build_add_plan_with_registered_providers(
|
||||
"appimagehub/2338455",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue