1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
use crate::{
    policy::{content::ContentPolicy, record::RecordPolicy},
    services::CoreService,
};
use anyhow::Result;
use axum::{
    extract::{
        rejection::{JsonRejection, PathRejection},
        FromRequest, FromRequestParts,
    },
    http::StatusCode,
    response::IntoResponse,
    Router,
};
use serde::{Serialize, Serializer};
use std::{path::PathBuf, sync::Arc};
use url::Url;

pub mod content;
pub mod fetch;
pub mod package;
pub mod proof;

/// An extractor that wraps the JSON extractor of Axum.
///
/// This extractor returns an API error on rejection.
#[derive(FromRequest)]
#[from_request(via(axum::Json), rejection(Error))]
pub struct Json<T>(pub T);

impl<T> IntoResponse for Json<T>
where
    T: Serialize,
{
    fn into_response(self) -> axum::response::Response {
        axum::Json(self.0).into_response()
    }
}

fn serialize_status<S>(status: &StatusCode, serializer: S) -> Result<S::Ok, S::Error>
where
    S: Serializer,
{
    serializer.serialize_u16(status.as_u16())
}

/// Represents a generic error from the API.
#[derive(Serialize, Debug)]
pub struct Error {
    #[serde(serialize_with = "serialize_status")]
    status: StatusCode,
    message: String,
}

impl From<JsonRejection> for Error {
    fn from(rejection: JsonRejection) -> Self {
        Self {
            status: rejection.status(),
            message: rejection.body_text(),
        }
    }
}

impl IntoResponse for Error {
    fn into_response(self) -> axum::response::Response {
        (self.status, axum::Json(self)).into_response()
    }
}

/// An extractor that wraps the path extractor of Axum.
///
/// This extractor returns an API error on rejection.
#[derive(FromRequestParts)]
#[from_request(via(axum::extract::Path), rejection(Error))]
pub struct Path<T>(T);

impl From<PathRejection> for Error {
    fn from(rejection: PathRejection) -> Self {
        Self {
            status: rejection.status(),
            message: rejection.body_text(),
        }
    }
}

pub async fn not_found() -> impl IntoResponse {
    Error {
        status: StatusCode::NOT_FOUND,
        message: "the requested resource was not found".to_string(),
    }
}

pub fn create_router(
    content_base_url: Url,
    core: CoreService,
    temp_dir: PathBuf,
    files_dir: PathBuf,
    content_policy: Option<Arc<dyn ContentPolicy>>,
    record_policy: Option<Arc<dyn RecordPolicy>>,
) -> Router {
    let proof_config = proof::Config::new(core.clone());
    let package_config = package::Config::new(
        core.clone(),
        files_dir.clone(),
        temp_dir,
        content_policy,
        record_policy,
    );
    let fetch_config = fetch::Config::new(core);
    let content_config = content::Config::new(content_base_url, files_dir);

    Router::new()
        .nest("/package", package_config.into_router())
        .nest("/content", content_config.into_router())
        .nest("/fetch", fetch_config.into_router())
        .nest("/proof", proof_config.into_router())
        .fallback(not_found)
}