use std::collections::HashMap;
use std::fmt;
use std::fmt::{Debug, Display, Formatter};
use super::SchemeId;
use crate::chain::{ChainId, ChainProvider};
use crate::facilitator::{BoxFuture, Facilitator, FacilitatorError};
use crate::proto;
pub trait SchemeBuilder<P> {
fn build(
&self,
provider: P,
config: Option<serde_json::Value>,
) -> Result<Box<dyn Facilitator>, Box<dyn std::error::Error>>;
}
pub trait SchemeBlueprint<P>: SchemeId + for<'a> SchemeBuilder<&'a P> {}
impl<T, P> SchemeBlueprint<P> for T where T: SchemeId + for<'a> SchemeBuilder<&'a P> {}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct SchemeSlug {
pub chain_id: ChainId,
pub name: String,
}
impl SchemeSlug {
#[must_use]
pub const fn new(chain_id: ChainId, name: String) -> Self {
Self { chain_id, name }
}
#[must_use]
pub fn as_wildcard(&self) -> Self {
Self {
chain_id: ChainId::new(self.chain_id.namespace(), "*"),
name: self.name.clone(),
}
}
#[must_use]
pub fn is_wildcard(&self) -> bool {
self.chain_id.reference() == "*"
}
}
impl Display for SchemeSlug {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(
f,
"{}:{}:{}",
self.chain_id.namespace(),
self.chain_id.reference(),
self.name
)
}
}
#[derive(Default)]
pub struct SchemeRegistry(HashMap<SchemeSlug, Box<dyn Facilitator>>);
impl Debug for SchemeRegistry {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let slugs: Vec<String> = self.0.keys().map(ToString::to_string).collect();
f.debug_tuple("SchemeRegistry").field(&slugs).finish()
}
}
impl SchemeRegistry {
#[must_use]
pub fn new() -> Self {
Self(HashMap::new())
}
pub fn register<P: ChainProvider>(
&mut self,
blueprint: &dyn SchemeBlueprint<P>,
provider: &P,
config: Option<serde_json::Value>,
) -> Result<(), Box<dyn std::error::Error>> {
let chain_id = provider.chain_id();
let handler = blueprint.build(provider, config)?;
let slug = SchemeSlug::new(chain_id, blueprint.scheme().to_string());
self.0.insert(slug, handler);
Ok(())
}
#[must_use]
pub fn by_slug(&self, slug: &SchemeSlug) -> Option<&dyn Facilitator> {
self.0
.get(slug)
.or_else(|| {
let wildcard = slug.as_wildcard();
self.0.get(&wildcard)
})
.map(|h| &**h)
}
pub fn register_for_namespace<P: ChainProvider>(
&mut self,
blueprint: &dyn SchemeBlueprint<P>,
provider: &P,
config: Option<serde_json::Value>,
) -> Result<(), Box<dyn std::error::Error>> {
let handler = blueprint.build(provider, config)?;
let namespace = provider.chain_id().namespace().to_owned();
let slug = SchemeSlug::new(ChainId::new(namespace, "*"), blueprint.scheme().to_string());
self.0.insert(slug, handler);
Ok(())
}
pub fn values(&self) -> impl Iterator<Item = &dyn Facilitator> {
self.0.values().map(|v| &**v)
}
fn require_handler(
&self,
slug: Option<SchemeSlug>,
) -> Result<&dyn Facilitator, FacilitatorError> {
slug.and_then(|s| self.by_slug(&s))
.ok_or_else(|| FacilitatorError::Aborted {
reason: "no_facilitator_for_network".into(),
message: "no handler registered for this payment scheme".into(),
})
}
}
impl Facilitator for SchemeRegistry {
fn verify(
&self,
request: proto::VerifyRequest,
) -> BoxFuture<'_, Result<proto::VerifyResponse, FacilitatorError>> {
Box::pin(async move {
let handler = self.require_handler(request.scheme_slug())?;
handler.verify(request).await
})
}
fn settle(
&self,
request: proto::SettleRequest,
) -> BoxFuture<'_, Result<proto::SettleResponse, FacilitatorError>> {
Box::pin(async move {
let handler = self.require_handler(request.scheme_slug())?;
handler.settle(request).await
})
}
fn supported(&self) -> BoxFuture<'_, Result<proto::SupportedResponse, FacilitatorError>> {
Box::pin(async move {
let mut kinds = Vec::new();
let mut signers: HashMap<String, Vec<String>> = HashMap::new();
for handler in self.values() {
if let Ok(mut resp) = handler.supported().await {
kinds.append(&mut resp.kinds);
for (family, addrs) in resp.signers {
signers.entry(family).or_default().extend(addrs);
}
}
}
for addrs in signers.values_mut() {
addrs.sort_unstable();
addrs.dedup();
}
Ok(proto::SupportedResponse {
kinds,
extensions: Vec::new(),
signers,
})
})
}
}