1use std::sync::Arc;
2
3use axum::{
4 Json, Router,
5 extract::State,
6 http::{StatusCode, header},
7 response::IntoResponse,
8};
9use jacquard::{
10 api::com_atproto::identity::resolve_did::{ResolveDidOutput, ResolveDidRequest},
11 identity::{JacquardResolver, resolver::IdentityResolver},
12 types::value::to_data,
13};
14use jacquard_axum::{ExtractXrpc, IntoRouter};
15use miette::{IntoDiagnostic, Result};
16use tracing_subscriber::EnvFilter;
17
18#[axum_macros::debug_handler]
19async fn resolve_did(
20 State(state): State<Arc<AppState>>,
21 ExtractXrpc(args): ExtractXrpc<ResolveDidRequest>,
22) -> Result<Json<ResolveDidOutput<'static>>, XrpcErrorResponse> {
23 let doc = state
24 .resolver
25 .resolve_did_doc_owned(&args.did)
26 .await
27 .map_err(|_| XrpcErrorResponse::internal_server_error())?;
28 Ok(ResolveDidOutput {
29 did_doc: to_data(&doc).map_err(|_| XrpcErrorResponse::internal_server_error())?,
30 extra_data: Default::default(),
31 }
32 .into())
33}
34
35#[tokio::main]
36async fn main() -> Result<()> {
37 tracing_subscriber::fmt()
38 .with_timer(tracing_subscriber::fmt::time::UtcTime::rfc_3339())
39 .with_env_filter(EnvFilter::from_env("QDPDS_LOG"))
40 .init();
41 let app = Router::new()
42 .route("/", axum::routing::get(|| async { "hello world!" }))
43 .merge(ResolveDidRequest::into_router(resolve_did))
44 .with_state(Arc::new(AppState::new()))
45 .layer(tower_http::trace::TraceLayer::new_for_http());
46 let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
47 .await
48 .into_diagnostic()?;
49 axum::serve(listener, app).await.into_diagnostic()?;
50 Ok(())
51}
52
53pub struct XrpcErrorResponse {
54 error: XrpcError,
55 pub status: StatusCode,
56}
57
58impl XrpcErrorResponse {
59 pub fn internal_server_error() -> Self {
60 Self {
61 error: XrpcError {
62 error: "InternalServerError".to_string(),
63 message: None,
64 },
65 status: StatusCode::INTERNAL_SERVER_ERROR,
66 }
67 }
68}
69
70#[derive(serde::Deserialize, serde::Serialize)]
71pub struct XrpcError {
72 pub error: String,
73 #[serde(skip_serializing_if = "std::option::Option::is_none")]
74 pub message: Option<String>,
75}
76
77impl IntoResponse for XrpcErrorResponse {
78 fn into_response(self) -> axum::response::Response {
79 Json(self.error).into_response()
80 }
81}
82
83pub struct AppState {
84 pub resolver: JacquardResolver,
85}
86
87impl AppState {
88 pub fn new() -> Self {
89 Self {
90 resolver: jacquard::identity::slingshot_resolver_default(),
91 }
92 }
93}