1use std::{net::SocketAddr, sync::Arc};
2
3use axum::{
4 body::Body,
5 extract::{Path, State},
6 http::StatusCode,
7 response::{IntoResponse, Response},
8 routing::get,
9 Json, Router,
10};
11use ethers::types::U256;
12use tokio::task::JoinHandle;
13use tracing::{info_span, Instrument, Span};
14
15use crate::MetadataGenerator;
16
17pub async fn nft_handler<T>(
20 Path(token_id): Path<String>,
21 State(generator): State<Arc<T>>,
22) -> Response
23where
24 T: MetadataGenerator,
25{
26 let e_500 = (
27 StatusCode::INTERNAL_SERVER_ERROR,
28 "service temporarily unavailable",
29 );
30 let token_id = match U256::from_dec_str(&token_id) {
31 Ok(id) => id,
32 Err(e) => {
33 tracing::error!(token_id = ?token_id, error = %e, "error in token_id parsing");
34 return e_500.into_response();
35 }
36 };
37
38 match generator.metadata_for(token_id).await {
39 Ok(Some(metadata)) => (
40 [("Cache-Control", "max-age=300, must-revalidate")],
41 Json(metadata),
42 )
43 .into_response(),
44 Ok(None) => (
45 StatusCode::NOT_FOUND,
46 [("Cache-Control", "max-age=300, must-revalidate")],
47 "unknown token id",
48 )
49 .into_response(),
50 Err(e) => {
51 tracing::error!(error = %e, "error in metadata lookup");
52 e_500.into_response()
53 }
54 }
55}
56
57pub async fn contract_handler<T>(State(generator): State<Arc<T>>) -> Response
60where
61 T: MetadataGenerator,
62{
63 match generator.contract_metadata().await {
64 Some(metadata) => (
65 [("Cache-Control", "max-age=300, must-revalidate")],
66 Json(metadata),
67 )
68 .into_response(),
69 None => (StatusCode::NOT_FOUND, "no contract metadata").into_response(),
70 }
71}
72
73pub async fn return_404() -> impl IntoResponse {
75 (StatusCode::NOT_FOUND, "unknown route")
76}
77
78pub async fn return_200() -> impl IntoResponse {
80 StatusCode::OK
81}
82
83pub fn serve_generator_with_span<T>(
92 t: T,
93 socket: impl Into<SocketAddr>,
94 span: Span,
95) -> JoinHandle<()>
96where
97 T: MetadataGenerator + Send + Sync + 'static,
98{
99 let app = Router::<_, Body>::with_state(Arc::new(t))
100 .route("/healthcheck", get(return_200))
101 .route(
102 "/favicon.ico",
103 get(|| async move { (StatusCode::NOT_FOUND, "") }),
104 )
105 .route("/:token_id", get(nft_handler))
106 .route("/", get(contract_handler))
107 .fallback(return_404);
108
109 serve_router_with_span(app, socket, span)
110}
111
112pub fn serve_generator<T>(t: T, socket: impl Into<SocketAddr>) -> JoinHandle<()>
120where
121 T: MetadataGenerator + Send + Sync + 'static,
122{
123 let span = info_span!("serve_generator");
124 serve_generator_with_span(t, socket, span)
125}
126
127pub fn serve_router_with_span<T>(
133 app: Router<Arc<T>>,
134 socket: impl Into<SocketAddr>,
135 span: Span,
136) -> JoinHandle<()>
137where
138 T: MetadataGenerator + Send + Sync + 'static,
139{
140 let addr = socket.into();
141 tokio::spawn(async move {
142 Instrument::instrument(
143 axum::Server::bind(&addr).serve(app.into_make_service()),
144 span,
145 )
146 .await
147 .unwrap();
148 })
149}
150
151pub fn serve_router<T>(app: Router<Arc<T>>, socket: impl Into<SocketAddr>) -> JoinHandle<()>
156where
157 T: MetadataGenerator + Send + Sync + 'static,
158{
159 let span = info_span!("serve_router");
160 serve_router_with_span(app, socket, span)
161}