refactor: add upm application facade and module api

This commit is contained in:
stoorps 2026-03-21 23:43:14 +00:00
parent 005d6ebfdb
commit e2a01d3095
Signed by: stoorps
SSH key fingerprint: SHA256:AZlPfu9hTu042EGtZElmDQoy+KvMOeShLDan/fYLoNI
36 changed files with 1058 additions and 607 deletions

View file

@ -11,4 +11,7 @@ path = "src/lib.rs"
quick-xml.workspace = true
reqwest.workspace = true
serde.workspace = true
upm-module-api = { path = "../upm-module-api" }
[dev-dependencies]
upm-core = { path = "../upm-core" }

View file

@ -1,13 +1,14 @@
use crate::source::appimagehub::{
AppImageHubError, AppImageHubTransport, resolve_appimagehub_item, resolve_appimagehub_item_with,
};
use upm_core::adapters::traits::{
use upm_module_api::adapters::traits::{
AdapterCapabilities, AdapterError, AdapterResolution, AdapterResolveOutcome, SourceAdapter,
};
use upm_core::app::providers::{ExternalAddProvider, ExternalAddResolution};
use upm_core::app::query::resolve_query;
use upm_core::domain::source::{ResolvedRelease, SourceKind, SourceRef};
use upm_core::domain::update::{
use upm_module_api::app::providers::{ExternalAddProvider, ExternalAddResolution};
use upm_module_api::domain::source::{
NormalizedSourceKind, ResolvedRelease, SourceInputKind, SourceKind, SourceRef,
};
use upm_module_api::domain::update::{
ArtifactCandidate, ChannelPreference, UpdateChannelKind, UpdateStrategy,
};
@ -58,7 +59,7 @@ impl SourceAdapter for AppImageHubAdapter {
}
fn normalize(&self, query: &str) -> Result<SourceRef, AdapterError> {
let source = resolve_query(query).map_err(|_| AdapterError::UnsupportedQuery)?;
let source = resolve_appimagehub_query(query)?;
if source.kind != SourceKind::AppImageHub {
return Err(AdapterError::UnsupportedQuery);
}
@ -92,17 +93,17 @@ impl SourceAdapter for AppImageHubAdapter {
}
}
pub struct AppImageHubAddProvider<'a, T: AppImageHubTransport + ?Sized> {
transport: &'a T,
pub struct AppImageHubAddProvider {
transport: Box<dyn AppImageHubTransport>,
}
impl<'a, T: AppImageHubTransport + ?Sized> AppImageHubAddProvider<'a, T> {
pub fn new(transport: &'a T) -> Self {
impl AppImageHubAddProvider {
pub fn new(transport: Box<dyn AppImageHubTransport>) -> Self {
Self { transport }
}
}
impl<T: AppImageHubTransport + ?Sized> ExternalAddProvider for AppImageHubAddProvider<'_, T> {
impl ExternalAddProvider for AppImageHubAddProvider {
fn id(&self) -> &'static str {
"appimagehub"
}
@ -113,11 +114,11 @@ impl<T: AppImageHubTransport + ?Sized> ExternalAddProvider for AppImageHubAddPro
}
let adapter = AppImageHubAdapter;
let resolution = match adapter.resolve_source_with(source, self.transport)? {
let resolution = match adapter.resolve_source_with(source, self.transport.as_ref())? {
AdapterResolveOutcome::Resolved(resolution) => resolution,
AdapterResolveOutcome::NoInstallableArtifact { .. } => return Ok(None),
};
let Some(resolved_item) = resolve_appimagehub_item_with(source, self.transport)
let Some(resolved_item) = resolve_appimagehub_item_with(source, self.transport.as_ref())
.map_err(|error| AdapterError::ResolutionFailed(format!("{error:?}")))?
else {
return Ok(None);
@ -161,3 +162,35 @@ fn render_appimagehub_error(error: &AppImageHubError) -> String {
}
}
}
fn resolve_appimagehub_query(query: &str) -> Result<SourceRef, AdapterError> {
let trimmed = query.trim();
let id = if let Some(id) = trimmed.strip_prefix("appimagehub/") {
id
} else if let Some(id) = trimmed.strip_prefix("https://www.appimagehub.com/p/") {
id
} else if let Some(id) = trimmed.strip_prefix("http://www.appimagehub.com/p/") {
id
} else {
return Err(AdapterError::UnsupportedQuery);
};
if !id.chars().all(|ch| ch.is_ascii_digit()) {
return Err(AdapterError::UnsupportedQuery);
}
Ok(SourceRef {
kind: SourceKind::AppImageHub,
locator: format!("https://www.appimagehub.com/p/{id}"),
input_kind: if trimmed.starts_with("appimagehub/") {
SourceInputKind::AppImageHubShorthand
} else {
SourceInputKind::AppImageHubUrl
},
normalized_kind: NormalizedSourceKind::AppImageHub,
canonical_locator: Some(id.to_owned()),
requested_tag: None,
requested_asset_name: None,
tracks_latest: true,
})
}

View file

@ -1,25 +1,29 @@
use crate::source::appimagehub::{
AppImageHubSearchError, AppImageHubTransport, search_appimagehub_with,
};
use upm_core::app::search::{SearchProvider, SearchProviderError};
use upm_core::domain::search::{SearchInstallStatus, SearchQuery, SearchResult};
use upm_module_api::app::search::{SearchProvider, SearchProviderError};
use upm_module_api::domain::search::{SearchInstallStatus, SearchQuery, SearchResult};
pub struct AppImageHubSearchProvider<'a, T: AppImageHubTransport + ?Sized> {
transport: &'a T,
pub struct AppImageHubSearchProvider {
transport: Box<dyn AppImageHubTransport>,
}
impl<'a, T: AppImageHubTransport + ?Sized> AppImageHubSearchProvider<'a, T> {
pub fn new(transport: &'a T) -> Self {
impl AppImageHubSearchProvider {
pub fn new(transport: Box<dyn AppImageHubTransport>) -> Self {
Self { transport }
}
}
impl<T: AppImageHubTransport + ?Sized> SearchProvider for AppImageHubSearchProvider<'_, T> {
impl SearchProvider for AppImageHubSearchProvider {
fn search(&self, query: &SearchQuery) -> Result<Vec<SearchResult>, SearchProviderError> {
let hits = search_appimagehub_with(&query.text, query.remote_limit, self.transport)
.map_err(|error| {
SearchProviderError::new("appimagehub", &render_appimagehub_search_error(&error))
})?;
let hits =
search_appimagehub_with(&query.text, query.remote_limit, self.transport.as_ref())
.map_err(|error| {
SearchProviderError::new(
"appimagehub",
&render_appimagehub_search_error(&error),
)
})?;
let normalized_query = normalize_lookup(&query.text);
let mut ranked_hits = hits

View file

@ -1,7 +1,7 @@
use std::env;
use std::time::Duration;
use upm_core::domain::source::SourceRef;
use upm_module_api::domain::source::SourceRef;
const DEFAULT_APPIMAGEHUB_API_BASE: &str = "https://api.appimagehub.com/ocs/v1/content";
const GLOBAL_FIXTURE_MODE_ENV: &str = "UPM_FIXTURE_MODE";

View file

@ -24,7 +24,7 @@ impl SearchProvider for StubProvider {
#[test]
fn appimagehub_search_provider_maps_hits_to_install_ready_results() {
let provider = AppImageHubSearchProvider::new(&FixtureAppImageHubTransport);
let provider = AppImageHubSearchProvider::new(Box::new(FixtureAppImageHubTransport));
let results = provider.search(&SearchQuery::new("firefox")).unwrap();
@ -38,7 +38,7 @@ fn appimagehub_search_provider_maps_hits_to_install_ready_results() {
#[test]
fn appimagehub_hits_are_annotated_as_installed_by_canonical_id() {
let provider = AppImageHubSearchProvider::new(&FixtureAppImageHubTransport);
let provider = AppImageHubSearchProvider::new(Box::new(FixtureAppImageHubTransport));
let installed = vec![AppRecord {
stable_id: "firefox".to_owned(),
display_name: "Firefox by Mozilla - Official AppImage Edition".to_owned(),
@ -76,7 +76,7 @@ fn appimagehub_hits_are_annotated_as_installed_by_canonical_id() {
#[test]
fn search_can_merge_github_and_appimagehub_providers() {
let github = GitHubSearchProvider::new(&FixtureGitHubTransport);
let appimagehub = AppImageHubSearchProvider::new(&FixtureAppImageHubTransport);
let appimagehub = AppImageHubSearchProvider::new(Box::new(FixtureAppImageHubTransport));
let stub = StubProvider {
hit: SearchResult {
provider_id: "github".to_owned(),
@ -131,7 +131,7 @@ fn appimagehub_adapter_resolves_installable_items_through_fixture_transport() {
#[test]
fn appimagehub_add_provider_resolves_external_add_plan() {
let provider = AppImageHubAddProvider::new(&FixtureAppImageHubTransport);
let provider = AppImageHubAddProvider::new(Box::new(FixtureAppImageHubTransport));
let source = resolve_query("appimagehub/2338455").unwrap();
let resolution = provider.resolve(&source).unwrap().unwrap();