use std::{error::Error, ops::Deref}; use axum::{Json, Router, extract::FromRequestParts, http::StatusCode, response::IntoResponse}; use jacquard::{ IntoStatic, identity::resolver::{self, IdentityError, IdentityResolver}, types::value::Data, }; use jacquard_api::com_atproto::identity::resolve_did::{ ResolveDid, ResolveDidError, ResolveDidOutput, }; use jacquard_common::types::xrpc::{XrpcMethod, XrpcRequest}; use miette::{IntoDiagnostic, Result}; use serde::{Deserialize, Serialize}; use tracing_subscriber::EnvFilter; trait IntoRouter { fn into_router(handler: U) -> Router where T: 'static, S: Clone + Send + Sync + 'static, U: axum::handler::Handler; } impl IntoRouter for X { /// Creates an axum router that will invoke `handler` in response to xrpc /// request `X`. fn into_router(handler: U) -> Router where T: 'static, S: Clone + Send + Sync + 'static, U: axum::handler::Handler, { Router::new().route( // this should really be a compile time operation, but I cannot // figure out how to make that happen format!("/xrpc/{}", X::NSID).as_str(), (match X::METHOD { XrpcMethod::Query => axum::routing::get, XrpcMethod::Procedure(_) => axum::routing::put, })(handler), ) } } struct XrpcArgs(T); impl Deref for XrpcArgs { type Target = T; fn deref(&self) -> &Self::Target { &self.0 } } #[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone, thiserror::Error)] #[serde(tag = "error", content = "message")] pub enum AtProtoGenericError { #[error("Invalid Request")] InvalidRequest(Option), #[error("Expired Token")] ExpiredToken(Option), #[error("Invalid Token")] InvalidToken(Option), } #[derive(thiserror::Error, Debug)] #[error(transparent)] struct XrpcError(pub T); impl IntoResponse for XrpcError { fn into_response(self) -> axum::response::Response { (StatusCode::BAD_REQUEST, Json(self.0)).into_response() } } impl From for XrpcError { fn from(value: AtProtoGenericError) -> Self { Self(value) } } impl<'a> From> for XrpcError> { fn from(value: ResolveDidError) -> Self { Self(value.into_static()) } } impl From for XrpcError> { fn from(value: IdentityError) -> Self { Self(ResolveDidError::DidNotFound(Some(format!("{}", value)))) } } struct XrpcResponse(T); impl IntoResponse for XrpcResponse { fn into_response(self) -> axum::response::Response { Json(self.0).into_response() } } impl<'a> From> for XrpcResponse> { fn from(value: ResolveDidOutput<'a>) -> Self { Self(value.into_static()) } } impl From for XrpcError { fn from(_: serde_html_form::de::Error) -> Self { AtProtoGenericError::InvalidRequest(None).into() } } impl FromRequestParts for XrpcArgs> { type Rejection = XrpcError; async fn from_request_parts( parts: &mut axum::http::request::Parts, _state: &S, ) -> std::result::Result { let q = parts.uri.query().unwrap_or_default(); let de = serde_html_form::Deserializer::new(url::form_urlencoded::parse(q.as_bytes())); let value = ResolveDid::deserialize(de)?; Ok(Self(value.into_static())) } } #[tokio::main] async fn main() -> Result<()> { tracing_subscriber::fmt() .with_timer(tracing_subscriber::fmt::time::UtcTime::rfc_3339()) .with_env_filter(EnvFilter::from_env("QDPDS_LOG")) .init(); let app = Router::new() .route("/", axum::routing::get(|| async { "hello world!" })) .merge(ResolveDid::into_router( async move |XrpcArgs(args): XrpcArgs>| -> Result< XrpcResponse, XrpcError, > { let res = resolver::slingshot_resolver_default(); let doc = res.resolve_did_doc(&args.did).await?; let valid_doc = doc.parse()?; let doc_value = serde_json::to_value(valid_doc).unwrap(); Ok(ResolveDidOutput { did_doc: Data::from_json(&doc_value).unwrap().into_static(), extra_data: Default::default(), } .into()) }, )) .layer(tower_http::trace::TraceLayer::new_for_http()); let listener = tokio::net::TcpListener::bind("0.0.0.0:3000") .await .into_diagnostic()?; axum::serve(listener, app).await.unwrap(); Ok(()) }