A better Rust ATProto crate
at main 2.6 kB view raw
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}