bsv-payment-actix-middleware 0.1.0

BSV payment middleware for Actix-web, wire-compatible with the TypeScript payment-express-middleware
Documentation
//! Payment middleware configuration with builder pattern.

use std::sync::Arc;

use bsv::wallet::interfaces::WalletInterface;

use crate::error::PaymentError;
use crate::types::CalculateRequestPrice;

/// Configuration for the payment middleware.
///
/// Generic over `W` so that any wallet implementing `WalletInterface` can be used.
pub struct PaymentMiddlewareConfig<W: WalletInterface> {
    /// The wallet instance used for payment verification and key derivation.
    pub wallet: W,
    /// Optional callback to calculate the price for a given request.
    /// When `None`, the middleware falls back to `DEFAULT_SATOSHIS`.
    pub calculate_request_price: Option<Arc<CalculateRequestPrice>>,
}

/// Builder for [`PaymentMiddlewareConfig`].
///
/// The `wallet` field is required; calling [`build`](Self::build) without setting it
/// returns [`PaymentError::ServerMisconfigured`].
pub struct PaymentMiddlewareConfigBuilder<W: WalletInterface> {
    wallet: Option<W>,
    calculate_request_price: Option<Arc<CalculateRequestPrice>>,
}

impl<W: WalletInterface> Default for PaymentMiddlewareConfigBuilder<W> {
    fn default() -> Self {
        Self::new()
    }
}

impl<W: WalletInterface> PaymentMiddlewareConfigBuilder<W> {
    /// Create a new builder with all fields set to `None`.
    pub fn new() -> Self {
        Self {
            wallet: None,
            calculate_request_price: None,
        }
    }

    /// Set the wallet instance.
    pub fn wallet(mut self, wallet: W) -> Self {
        self.wallet = Some(wallet);
        self
    }

    /// Set the price calculation callback.
    pub fn calculate_request_price(mut self, cb: CalculateRequestPrice) -> Self {
        self.calculate_request_price = Some(Arc::new(cb));
        self
    }

    /// Build the configuration.
    ///
    /// Returns `Err(PaymentError::ServerMisconfigured)` if the wallet has not been set.
    pub fn build(self) -> Result<PaymentMiddlewareConfig<W>, PaymentError> {
        let wallet = self.wallet.ok_or(PaymentError::ServerMisconfigured)?;
        Ok(PaymentMiddlewareConfig {
            wallet,
            calculate_request_price: self.calculate_request_price,
        })
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use async_trait::async_trait;
    use bsv::wallet::error::WalletError;
    use bsv::wallet::interfaces::*;

    /// Minimal mock wallet for testing. All methods return `unimplemented!()`.
    #[derive(Clone)]
    struct MockWallet;

    #[async_trait]
    impl WalletInterface for MockWallet {
        async fn create_action(
            &self,
            _args: CreateActionArgs,
            _originator: Option<&str>,
        ) -> Result<CreateActionResult, WalletError> {
            unimplemented!()
        }
        async fn sign_action(
            &self,
            _args: SignActionArgs,
            _originator: Option<&str>,
        ) -> Result<SignActionResult, WalletError> {
            unimplemented!()
        }
        async fn abort_action(
            &self,
            _args: AbortActionArgs,
            _originator: Option<&str>,
        ) -> Result<AbortActionResult, WalletError> {
            unimplemented!()
        }
        async fn list_actions(
            &self,
            _args: ListActionsArgs,
            _originator: Option<&str>,
        ) -> Result<ListActionsResult, WalletError> {
            unimplemented!()
        }
        async fn internalize_action(
            &self,
            _args: InternalizeActionArgs,
            _originator: Option<&str>,
        ) -> Result<InternalizeActionResult, WalletError> {
            unimplemented!()
        }
        async fn list_outputs(
            &self,
            _args: ListOutputsArgs,
            _originator: Option<&str>,
        ) -> Result<ListOutputsResult, WalletError> {
            unimplemented!()
        }
        async fn relinquish_output(
            &self,
            _args: RelinquishOutputArgs,
            _originator: Option<&str>,
        ) -> Result<RelinquishOutputResult, WalletError> {
            unimplemented!()
        }
        async fn get_public_key(
            &self,
            _args: GetPublicKeyArgs,
            _originator: Option<&str>,
        ) -> Result<GetPublicKeyResult, WalletError> {
            unimplemented!()
        }
        async fn reveal_counterparty_key_linkage(
            &self,
            _args: RevealCounterpartyKeyLinkageArgs,
            _originator: Option<&str>,
        ) -> Result<RevealCounterpartyKeyLinkageResult, WalletError> {
            unimplemented!()
        }
        async fn reveal_specific_key_linkage(
            &self,
            _args: RevealSpecificKeyLinkageArgs,
            _originator: Option<&str>,
        ) -> Result<RevealSpecificKeyLinkageResult, WalletError> {
            unimplemented!()
        }
        async fn encrypt(
            &self,
            _args: EncryptArgs,
            _originator: Option<&str>,
        ) -> Result<EncryptResult, WalletError> {
            unimplemented!()
        }
        async fn decrypt(
            &self,
            _args: DecryptArgs,
            _originator: Option<&str>,
        ) -> Result<DecryptResult, WalletError> {
            unimplemented!()
        }
        async fn create_hmac(
            &self,
            _args: CreateHmacArgs,
            _originator: Option<&str>,
        ) -> Result<CreateHmacResult, WalletError> {
            unimplemented!()
        }
        async fn verify_hmac(
            &self,
            _args: VerifyHmacArgs,
            _originator: Option<&str>,
        ) -> Result<VerifyHmacResult, WalletError> {
            unimplemented!()
        }
        async fn create_signature(
            &self,
            _args: CreateSignatureArgs,
            _originator: Option<&str>,
        ) -> Result<CreateSignatureResult, WalletError> {
            unimplemented!()
        }
        async fn verify_signature(
            &self,
            _args: VerifySignatureArgs,
            _originator: Option<&str>,
        ) -> Result<VerifySignatureResult, WalletError> {
            unimplemented!()
        }
        async fn acquire_certificate(
            &self,
            _args: AcquireCertificateArgs,
            _originator: Option<&str>,
        ) -> Result<Certificate, WalletError> {
            unimplemented!()
        }
        async fn list_certificates(
            &self,
            _args: ListCertificatesArgs,
            _originator: Option<&str>,
        ) -> Result<ListCertificatesResult, WalletError> {
            unimplemented!()
        }
        async fn prove_certificate(
            &self,
            _args: ProveCertificateArgs,
            _originator: Option<&str>,
        ) -> Result<ProveCertificateResult, WalletError> {
            unimplemented!()
        }
        async fn relinquish_certificate(
            &self,
            _args: RelinquishCertificateArgs,
            _originator: Option<&str>,
        ) -> Result<RelinquishCertificateResult, WalletError> {
            unimplemented!()
        }
        async fn discover_by_identity_key(
            &self,
            _args: DiscoverByIdentityKeyArgs,
            _originator: Option<&str>,
        ) -> Result<DiscoverCertificatesResult, WalletError> {
            unimplemented!()
        }
        async fn discover_by_attributes(
            &self,
            _args: DiscoverByAttributesArgs,
            _originator: Option<&str>,
        ) -> Result<DiscoverCertificatesResult, WalletError> {
            unimplemented!()
        }
        async fn is_authenticated(
            &self,
            _originator: Option<&str>,
        ) -> Result<AuthenticatedResult, WalletError> {
            unimplemented!()
        }
        async fn wait_for_authentication(
            &self,
            _originator: Option<&str>,
        ) -> Result<AuthenticatedResult, WalletError> {
            unimplemented!()
        }
        async fn get_height(
            &self,
            _originator: Option<&str>,
        ) -> Result<GetHeightResult, WalletError> {
            unimplemented!()
        }
        async fn get_header_for_height(
            &self,
            _args: GetHeaderArgs,
            _originator: Option<&str>,
        ) -> Result<GetHeaderResult, WalletError> {
            unimplemented!()
        }
        async fn get_network(
            &self,
            _originator: Option<&str>,
        ) -> Result<GetNetworkResult, WalletError> {
            unimplemented!()
        }
        async fn get_version(
            &self,
            _originator: Option<&str>,
        ) -> Result<GetVersionResult, WalletError> {
            unimplemented!()
        }
    }

    #[test]
    fn builder_with_wallet_succeeds() {
        let config = PaymentMiddlewareConfigBuilder::new()
            .wallet(MockWallet)
            .build();
        assert!(config.is_ok());
        let config = config.unwrap();
        assert!(config.calculate_request_price.is_none());
    }

    #[test]
    fn builder_without_wallet_fails() {
        let result = PaymentMiddlewareConfigBuilder::<MockWallet>::new().build();
        assert!(result.is_err());
    }

    #[test]
    fn builder_with_wallet_and_callback_succeeds() {
        let cb: CalculateRequestPrice = Box::new(|_req| Box::pin(async { Ok(500u64) }));
        let config = PaymentMiddlewareConfigBuilder::new()
            .wallet(MockWallet)
            .calculate_request_price(cb)
            .build();
        assert!(config.is_ok());
        assert!(config.unwrap().calculate_request_price.is_some());
    }
}