A better Rust ATProto crate
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}