use std::fmt::{Debug, Formatter};
use crate::chain::{ChainId, ChainIdPattern};
use crate::facilitator::BoxFuture;
use crate::proto;
pub trait SchemeClient: super::SchemeId + Send + Sync {
fn accept(&self, payment_required: &proto::PaymentRequired) -> Vec<PaymentCandidate>;
}
pub struct PaymentCandidate {
pub chain_id: ChainId,
pub asset: String,
pub amount: String,
pub scheme: String,
pub pay_to: String,
pub signer: Box<dyn PaymentCandidateSigner + Send + Sync>,
}
impl Debug for PaymentCandidate {
fn fmt(&self, f: &mut Formatter<'_>) -> std::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)
.field("signer", &"<dyn PaymentCandidateSigner>")
.finish()
}
}
impl PaymentCandidate {
pub async fn sign(&self) -> Result<String, ClientError> {
self.signer.sign_payment().await
}
}
pub trait PaymentCandidateSigner {
fn sign_payment(&self) -> BoxFuture<'_, Result<String, ClientError>>;
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum ClientError {
#[error("No matching payment option found")]
NoMatchingPaymentOption,
#[error("Request is not cloneable (streaming body?)")]
RequestNotCloneable,
#[error("Failed to parse 402 response: {0}")]
ParseError(String),
#[error("Failed to sign payment: {0}")]
SigningError(String),
#[error("JSON error: {0}")]
JsonError(#[from] serde_json::Error),
#[error("Payment pre-condition not met: {0}")]
PreConditionFailed(String),
}
pub trait PaymentSelector: Send + Sync {
fn select<'a>(&self, candidates: &[&'a PaymentCandidate]) -> Option<&'a PaymentCandidate>;
}
#[derive(Debug, Clone, Copy)]
pub struct FirstMatch;
impl PaymentSelector for FirstMatch {
fn select<'a>(&self, candidates: &[&'a PaymentCandidate]) -> Option<&'a PaymentCandidate> {
candidates.first().copied()
}
}
#[derive(Debug)]
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>>>(self, patterns: P) -> Self {
Self(self.0.into_iter().chain(patterns.into()).collect())
}
}
impl PaymentSelector for PreferChain {
fn select<'a>(&self, candidates: &[&'a PaymentCandidate]) -> Option<&'a PaymentCandidate> {
for pattern in &self.0 {
if let Some(candidate) = candidates.iter().find(|c| pattern.matches(&c.chain_id)) {
return Some(*candidate);
}
}
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)]
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)]
pub struct SchemePolicy(Vec<String>);
impl SchemePolicy {
pub fn new<S: Into<String>, 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 == &c.scheme))
.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()
}
}