use std::fmt;
use adk_auth::{ScopeDenied, check_scopes};
use serde::{Deserialize, Serialize};
use super::PaymentsAuthError;
pub const PAYMENT_CHECKOUT_CREATE_SCOPE: &str = "payments:checkout:create";
pub const PAYMENT_CHECKOUT_UPDATE_SCOPE: &str = "payments:checkout:update";
pub const PAYMENT_CHECKOUT_COMPLETE_SCOPE: &str = "payments:checkout:complete";
pub const PAYMENT_CHECKOUT_CANCEL_SCOPE: &str = "payments:checkout:cancel";
pub const PAYMENT_CREDENTIAL_DELEGATE_SCOPE: &str = "payments:credential:delegate";
pub const PAYMENT_INTERVENTION_CONTINUE_SCOPE: &str = "payments:intervention:continue";
pub const PAYMENT_ORDER_UPDATE_SCOPE: &str = "payments:order:update";
pub const PAYMENT_ADMIN_SCOPE: &str = "payments:admin";
pub const PAYMENT_SETTLEMENT_SCOPE: &str = "payments:settlement:write";
pub const CHECKOUT_CREATE_SCOPES: &[&str] = &[PAYMENT_CHECKOUT_CREATE_SCOPE];
pub const CHECKOUT_UPDATE_SCOPES: &[&str] = &[PAYMENT_CHECKOUT_UPDATE_SCOPE];
pub const CHECKOUT_COMPLETE_SCOPES: &[&str] = &[PAYMENT_CHECKOUT_COMPLETE_SCOPE];
pub const CHECKOUT_CANCEL_SCOPES: &[&str] = &[PAYMENT_CHECKOUT_CANCEL_SCOPE];
pub const CREDENTIAL_DELEGATE_SCOPES: &[&str] = &[PAYMENT_CREDENTIAL_DELEGATE_SCOPE];
pub const INTERVENTION_CONTINUE_SCOPES: &[&str] = &[PAYMENT_INTERVENTION_CONTINUE_SCOPE];
pub const ORDER_UPDATE_SCOPES: &[&str] = &[PAYMENT_ORDER_UPDATE_SCOPE];
pub const ADMIN_SCOPES: &[&str] = &[PAYMENT_ADMIN_SCOPE];
pub const SETTLEMENT_SCOPES: &[&str] = &[PAYMENT_SETTLEMENT_SCOPE];
pub const ALL_PAYMENT_SCOPES: &[&str] = &[
PAYMENT_CHECKOUT_CREATE_SCOPE,
PAYMENT_CHECKOUT_UPDATE_SCOPE,
PAYMENT_CHECKOUT_COMPLETE_SCOPE,
PAYMENT_CHECKOUT_CANCEL_SCOPE,
PAYMENT_CREDENTIAL_DELEGATE_SCOPE,
PAYMENT_INTERVENTION_CONTINUE_SCOPE,
PAYMENT_ORDER_UPDATE_SCOPE,
PAYMENT_ADMIN_SCOPE,
PAYMENT_SETTLEMENT_SCOPE,
];
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PaymentOperation {
CreateCheckout,
UpdateCheckout,
CompleteCheckout,
CancelCheckout,
DelegateCredential,
ContinueIntervention,
UpdateOrder,
Settlement,
Admin,
}
impl PaymentOperation {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::CreateCheckout => "checkout_create",
Self::UpdateCheckout => "checkout_update",
Self::CompleteCheckout => "checkout_complete",
Self::CancelCheckout => "checkout_cancel",
Self::DelegateCredential => "credential_delegate",
Self::ContinueIntervention => "intervention_continue",
Self::UpdateOrder => "order_update",
Self::Settlement => "settlement",
Self::Admin => "admin",
}
}
#[must_use]
pub const fn audit_resource(self) -> &'static str {
match self {
Self::CreateCheckout => "payments.checkout.create",
Self::UpdateCheckout => "payments.checkout.update",
Self::CompleteCheckout => "payments.checkout.complete",
Self::CancelCheckout => "payments.checkout.cancel",
Self::DelegateCredential => "payments.credential.delegate",
Self::ContinueIntervention => "payments.intervention.continue",
Self::UpdateOrder => "payments.order.update",
Self::Settlement => "payments.settlement.write",
Self::Admin => "payments.admin",
}
}
#[must_use]
pub const fn required_scopes(self) -> &'static [&'static str] {
match self {
Self::CreateCheckout => CHECKOUT_CREATE_SCOPES,
Self::UpdateCheckout => CHECKOUT_UPDATE_SCOPES,
Self::CompleteCheckout => CHECKOUT_COMPLETE_SCOPES,
Self::CancelCheckout => CHECKOUT_CANCEL_SCOPES,
Self::DelegateCredential => CREDENTIAL_DELEGATE_SCOPES,
Self::ContinueIntervention => INTERVENTION_CONTINUE_SCOPES,
Self::UpdateOrder => ORDER_UPDATE_SCOPES,
Self::Settlement => SETTLEMENT_SCOPES,
Self::Admin => ADMIN_SCOPES,
}
}
}
impl fmt::Display for PaymentOperation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
pub fn check_payment_operation_scopes(
operation: PaymentOperation,
granted: &[String],
) -> Result<(), PaymentsAuthError> {
check_scopes(operation.required_scopes(), granted)
.map_err(|denied| scope_denied(operation, denied))
}
fn scope_denied(operation: PaymentOperation, denied: ScopeDenied) -> PaymentsAuthError {
PaymentsAuthError::MissingScopes {
operation,
required: denied.required,
missing: denied.missing,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn scope_check_rejects_missing_scope() {
let granted = vec![PAYMENT_CHECKOUT_CREATE_SCOPE.to_string()];
let err = check_payment_operation_scopes(PaymentOperation::CompleteCheckout, &granted)
.unwrap_err();
match err {
PaymentsAuthError::MissingScopes { operation, required, missing } => {
assert_eq!(operation, PaymentOperation::CompleteCheckout);
assert_eq!(required, vec![PAYMENT_CHECKOUT_COMPLETE_SCOPE.to_string()]);
assert_eq!(missing, vec![PAYMENT_CHECKOUT_COMPLETE_SCOPE.to_string()]);
}
other => panic!("unexpected auth error: {other}"),
}
}
}