Skip to main content

zlicenser_server/http/
mod.rs

1pub mod enroll;
2pub mod revoke;
3pub mod transfer;
4pub mod upgrade;
5pub mod webhook;
6
7use std::sync::Arc;
8
9use axum::{
10    Json, Router,
11    http::StatusCode,
12    response::{IntoResponse, Response},
13    routing::{delete, post},
14};
15use serde_json::json;
16
17use crate::issuance::handlers::HandlerContext;
18use crate::storage::Storage;
19
20pub fn build_router<S: Storage + Clone + Send + Sync + 'static>(
21    ctx: Arc<HandlerContext<S>>,
22) -> Router {
23    Router::new()
24        .route("/enroll/request", post(enroll::request_handler::<S>))
25        .route(
26            "/enroll/receipt/{session_id}",
27            post(enroll::receipt_handler::<S>),
28        )
29        .route("/webhook/stripe", post(webhook::stripe_handler::<S>))
30        .route("/licenses/{id}", delete(revoke::revoke_handler::<S>))
31        .route(
32            "/licenses/{id}/transfer/request",
33            post(transfer::request_handler::<S>),
34        )
35        .route(
36            "/transfers/{id}/approve",
37            post(transfer::approve_handler::<S>),
38        )
39        .route(
40            "/transfers/{id}/reject",
41            post(transfer::reject_handler::<S>),
42        )
43        .route(
44            "/licenses/{id}/upgrade",
45            post(upgrade::upgrade_handler::<S>),
46        )
47        .with_state(ctx)
48}
49
50impl IntoResponse for crate::Error {
51    fn into_response(self) -> Response {
52        let (status, code) = match &self {
53            crate::Error::NotFound
54            | crate::Error::SessionNotFound
55            | crate::Error::BindingNotFound => (StatusCode::NOT_FOUND, "not_found"),
56
57            crate::Error::OfferExpired => (StatusCode::GONE, "offer_expired"),
58
59            crate::Error::SeatLimitReached
60            | crate::Error::Conflict(_)
61            | crate::Error::TransferAlreadyPending => (StatusCode::CONFLICT, "conflict"),
62
63            crate::Error::InvalidTransition(_) => (StatusCode::CONFLICT, "invalid_transition"),
64
65            crate::Error::PaymentNotHeld => (StatusCode::PAYMENT_REQUIRED, "payment_not_held"),
66
67            crate::Error::TsaFailed(_) | crate::Error::PaymentCaptureFailed(_) => {
68                (StatusCode::BAD_GATEWAY, "upstream_failure")
69            }
70
71            crate::Error::ClientVersionRejected { .. }
72            | crate::Error::OfferNonceMismatch
73            | crate::Error::InvalidReceiptSignature
74            | crate::Error::ConsentInvalid(_) => (StatusCode::BAD_REQUEST, "bad_request"),
75
76            crate::Error::ProductInactive
77            | crate::Error::RevocationNotSupported
78            | crate::Error::ForeignKeyViolation(_) => {
79                (StatusCode::UNPROCESSABLE_ENTITY, "rejected")
80            }
81
82            _ => (StatusCode::INTERNAL_SERVER_ERROR, "internal_error"),
83        };
84
85        let body = Json(json!({ "error": code, "message": self.to_string() }));
86        (status, body).into_response()
87    }
88}