warg_server/api/v1/
mod.rs

1use crate::{
2    policy::{content::ContentPolicy, record::RecordPolicy},
3    services::CoreService,
4};
5use anyhow::Result;
6use axum::{
7    async_trait,
8    extract::{
9        rejection::{JsonRejection, PathRejection},
10        FromRequest, FromRequestParts,
11    },
12    http::{request::Parts, StatusCode},
13    response::IntoResponse,
14    Router,
15};
16use serde::{Serialize, Serializer};
17use std::{path::PathBuf, str::FromStr, sync::Arc};
18use url::Url;
19use warg_api::v1::REGISTRY_HEADER_NAME;
20
21pub mod content;
22pub mod fetch;
23pub mod ledger;
24pub mod monitor;
25pub mod package;
26pub mod proof;
27
28/// An extractor that wraps the JSON extractor of Axum.
29///
30/// This extractor returns an API error on rejection.
31#[derive(FromRequest)]
32#[from_request(via(axum::Json), rejection(Error))]
33pub struct Json<T>(pub T);
34
35impl<T> IntoResponse for Json<T>
36where
37    T: Serialize,
38{
39    fn into_response(self) -> axum::response::Response {
40        axum::Json(self.0).into_response()
41    }
42}
43
44fn serialize_status<S>(status: &StatusCode, serializer: S) -> Result<S::Ok, S::Error>
45where
46    S: Serializer,
47{
48    serializer.serialize_u16(status.as_u16())
49}
50
51/// Represents a generic error from the API.
52#[derive(Serialize, Debug)]
53pub struct Error {
54    #[serde(serialize_with = "serialize_status")]
55    status: StatusCode,
56    message: String,
57}
58
59impl From<JsonRejection> for Error {
60    fn from(rejection: JsonRejection) -> Self {
61        Self {
62            status: rejection.status(),
63            message: rejection.body_text(),
64        }
65    }
66}
67
68impl IntoResponse for Error {
69    fn into_response(self) -> axum::response::Response {
70        (self.status, axum::Json(self)).into_response()
71    }
72}
73
74/// An extractor that wraps the path extractor of Axum.
75///
76/// This extractor returns an API error on rejection.
77#[derive(FromRequestParts)]
78#[from_request(via(axum::extract::Path), rejection(Error))]
79pub struct Path<T>(T);
80
81impl From<PathRejection> for Error {
82    fn from(rejection: PathRejection) -> Self {
83        Self {
84            status: rejection.status(),
85            message: rejection.body_text(),
86        }
87    }
88}
89
90pub async fn not_found() -> impl IntoResponse {
91    Error {
92        status: StatusCode::NOT_FOUND,
93        message: "the requested resource was not found".to_string(),
94    }
95}
96
97/// An extractor for the `Warg-Registry` header. Currently, this server implementation
98/// does not support this header and returns a `501` error.
99pub struct RegistryHeader(Option<String>);
100
101#[async_trait]
102impl<S> FromRequestParts<S> for RegistryHeader
103where
104    S: Send + Sync,
105{
106    type Rejection = (StatusCode, &'static str);
107
108    async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
109        if parts.headers.contains_key(REGISTRY_HEADER_NAME) {
110            Err((
111                StatusCode::NOT_IMPLEMENTED,
112                "`Warg-Registry` header is not supported",
113            ))
114        } else {
115            Ok(RegistryHeader(None))
116        }
117    }
118}
119
120impl FromStr for RegistryHeader {
121    type Err = std::convert::Infallible;
122    fn from_str(src: &str) -> Result<Self, Self::Err> {
123        Ok(RegistryHeader(Some(src.to_string())))
124    }
125}
126
127pub fn create_router(
128    content_base_url: Url,
129    core: CoreService,
130    temp_dir: PathBuf,
131    files_dir: PathBuf,
132    content_policy: Option<Arc<dyn ContentPolicy>>,
133    record_policy: Option<Arc<dyn RecordPolicy>>,
134) -> Router {
135    let proof_config = proof::Config::new(core.clone());
136    let package_config = package::Config::new(
137        core.clone(),
138        files_dir.clone(),
139        temp_dir,
140        content_policy,
141        record_policy,
142    );
143    let fetch_config = fetch::Config::new(core.clone());
144    let content_config = content::Config::new(content_base_url, files_dir);
145    let monitor_config = monitor::Config::new(core.clone());
146    let ledger_config = ledger::Config::new(core);
147
148    Router::new()
149        .nest("/content", content_config.into_router())
150        .nest("/fetch", fetch_config.into_router())
151        .nest("/ledger", ledger_config.into_router())
152        .nest("/package", package_config.into_router())
153        .nest("/proof", proof_config.into_router())
154        .nest("/verify", monitor_config.into_router())
155        .fallback(not_found)
156}