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