use super::error::{BackendError, Result};
use super::types::*;
pub trait AcquisitionBackend: Send + Sync {
fn id(&self) -> BackendId;
fn capabilities(&self) -> Capabilities;
fn credentials(&self) -> CredentialState {
CredentialState::NotRequired
}
fn claim_url(&self, _url: &str) -> Option<ItemRef> {
None
}
fn search(&self, _query: &SearchQuery) -> Result<Vec<Offer>> {
Err(BackendError::unsupported(self.id(), "search"))
}
fn enrich(&self, _offers: &mut [Offer]) -> Result<()> {
Ok(())
}
fn purchase(&self, _item: &ItemRef) -> Result<PurchaseFlow> {
Err(BackendError::unsupported(self.id(), "purchase"))
}
fn fetch(&self, _item: &ItemRef, _opts: &FetchOpts) -> Result<Vec<AcquiredFile>> {
Err(BackendError::unsupported(self.id(), "fetch"))
}
}
#[cfg(test)]
mod tests {
use super::*;
struct Stub;
impl AcquisitionBackend for Stub {
fn id(&self) -> BackendId {
BackendId::Bandcamp
}
fn capabilities(&self) -> Capabilities {
Capabilities::default()
}
}
#[test]
fn the_trait_is_object_safe() {
let b: Box<dyn AcquisitionBackend> = Box::new(Stub);
assert_eq!(b.id(), BackendId::Bandcamp);
}
#[test]
fn unimplemented_operations_report_unsupported_rather_than_panicking() {
let b = Stub;
let q = SearchQuery::from_text("x", 5);
assert!(matches!(
b.search(&q),
Err(BackendError::Unsupported { op: "search", .. })
));
assert!(matches!(
b.purchase(&ItemRef::new(BackendId::Bandcamp, "t:1")),
Err(BackendError::Unsupported { op: "purchase", .. })
));
}
#[test]
fn enrich_defaults_to_a_no_op_not_an_error() {
assert!(Stub.enrich(&mut []).is_ok());
}
#[test]
fn claim_url_defaults_to_declining() {
assert!(Stub.claim_url("https://example.com/x").is_none());
}
#[test]
fn backends_are_shareable_across_threads() {
let b: Box<dyn AcquisitionBackend> = Box::new(Stub);
let r = &b;
std::thread::scope(|s| {
let h = s.spawn(move || r.id());
assert_eq!(h.join().unwrap(), BackendId::Bandcamp);
});
}
}