use std::fmt::{self, Debug, Formatter};
use std::future::Future;
use compact_str::CompactString;
use crate::chain::{ChainId, ChainIdPattern};
use crate::error::ClientError;
use crate::scheme::sealed::Sealed;
use crate::wire::PaymentRequired;
pub trait SchemeClient: super::SchemeId + Sealed + Send + Sync {
fn accept(&self, payment_required: &PaymentRequired) -> Vec<PaymentCandidate>;
}
pub struct PaymentCandidate {
pub chain_id: ChainId,
pub asset: CompactString,
pub amount: CompactString,
pub scheme: CompactString,
pub pay_to: CompactString,
pub signer: Box<dyn PaymentCandidateSigner + Send + Sync>,
}
impl Debug for PaymentCandidate {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("PaymentCandidate")
.field("chain_id", &self.chain_id)
.field("asset", &self.asset)
.field("amount", &self.amount)
.field("scheme", &self.scheme)
.field("pay_to", &self.pay_to)
.finish_non_exhaustive()
}
}
impl PaymentCandidate {
pub async fn sign(&self) -> Result<String, ClientError> {
self.signer.sign_payment().await
}
}
pub trait PaymentCandidateSigner: Send + Sync {
fn sign_payment<'a>(
&'a self,
) -> std::pin::Pin<Box<dyn Future<Output = Result<String, ClientError>> + Send + 'a>>;
}
pub trait PaymentSelector: Send + Sync {
fn select<'a>(&self, candidates: &[&'a PaymentCandidate]) -> Option<&'a PaymentCandidate>;
}
#[derive(Debug, Clone, Copy, Default)]
pub struct FirstMatch;
impl PaymentSelector for FirstMatch {
fn select<'a>(&self, candidates: &[&'a PaymentCandidate]) -> Option<&'a PaymentCandidate> {
candidates.first().copied()
}
}
#[derive(Debug, Default)]
pub struct PreferChain(Vec<ChainIdPattern>);
impl PreferChain {
pub fn new<P: Into<Vec<ChainIdPattern>>>(patterns: P) -> Self {
Self(patterns.into())
}
#[must_use]
pub fn or_chain<P: Into<Vec<ChainIdPattern>>>(mut self, patterns: P) -> Self {
self.0.extend(patterns.into());
self
}
}
impl PaymentSelector for PreferChain {
fn select<'a>(&self, candidates: &[&'a PaymentCandidate]) -> Option<&'a PaymentCandidate> {
for pattern in &self.0 {
if let Some(hit) = candidates.iter().find(|c| pattern.matches(&c.chain_id)) {
return Some(*hit);
}
}
candidates.first().copied()
}
}
#[derive(Debug, Clone, Copy)]
pub struct MaxAmount(pub u128);
impl PaymentSelector for MaxAmount {
fn select<'a>(&self, candidates: &[&'a PaymentCandidate]) -> Option<&'a PaymentCandidate> {
candidates
.iter()
.find(|c| c.amount.parse::<u128>().is_ok_and(|a| a <= self.0))
.copied()
}
}
pub trait PaymentPolicy: Send + Sync {
fn apply<'a>(&self, candidates: Vec<&'a PaymentCandidate>) -> Vec<&'a PaymentCandidate>;
}
#[derive(Debug, Default)]
pub struct NetworkPolicy(Vec<ChainIdPattern>);
impl NetworkPolicy {
pub fn new<P: Into<Vec<ChainIdPattern>>>(patterns: P) -> Self {
Self(patterns.into())
}
}
impl PaymentPolicy for NetworkPolicy {
fn apply<'a>(&self, candidates: Vec<&'a PaymentCandidate>) -> Vec<&'a PaymentCandidate> {
candidates
.into_iter()
.filter(|c| self.0.iter().any(|p| p.matches(&c.chain_id)))
.collect()
}
}
#[derive(Debug, Default)]
pub struct SchemePolicy(Vec<CompactString>);
impl SchemePolicy {
pub fn new<S: Into<CompactString>, I: IntoIterator<Item = S>>(schemes: I) -> Self {
Self(schemes.into_iter().map(Into::into).collect())
}
}
impl PaymentPolicy for SchemePolicy {
fn apply<'a>(&self, candidates: Vec<&'a PaymentCandidate>) -> Vec<&'a PaymentCandidate> {
candidates
.into_iter()
.filter(|c| self.0.iter().any(|s| s.as_str() == c.scheme.as_str()))
.collect()
}
}
#[derive(Debug, Clone, Copy)]
pub struct MaxAmountPolicy(pub u128);
impl PaymentPolicy for MaxAmountPolicy {
fn apply<'a>(&self, candidates: Vec<&'a PaymentCandidate>) -> Vec<&'a PaymentCandidate> {
candidates
.into_iter()
.filter(|c| c.amount.parse::<u128>().is_ok_and(|a| a <= self.0))
.collect()
}
}