A better Rust ATProto crate
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

added some basic extractor tests, verified deserialization changes removes need for extractor url encoding workaround.

Orual d478b2fb 4b657b11

+146 -17
+2 -17
crates/jacquard-axum/src/lib.rs
··· 106 106 } 107 107 XrpcMethod::Query => { 108 108 if let Some(path_query) = req.uri().path_and_query() { 109 - // TODO: see if we can eliminate this now that we've fixed the deserialize impls for string types 110 - let query = 111 - urlencoding::decode(path_query.query().unwrap_or("")).map_err(|e| { 112 - ( 113 - StatusCode::BAD_REQUEST, 114 - [( 115 - header::CONTENT_TYPE, 116 - HeaderValue::from_static("application/json"), 117 - )], 118 - Json(json!({ 119 - "error": "InvalidRequest", 120 - "message": format!("failed to decode request: {}", e) 121 - })), 122 - ) 123 - .into_response() 124 - })?; 109 + let query = path_query.query().unwrap_or(""); 125 110 let value: R::Request<'_> = serde_html_form::from_str::<R::Request<'_>>( 126 - query.as_ref(), 111 + query, 127 112 ) 128 113 .map_err(|e| { 129 114 (
+144
crates/jacquard-axum/tests/extractor_tests.rs
··· 1 + use axum::{Json, Router, response::IntoResponse}; 2 + use axum_test::TestServer; 3 + use jacquard_axum::{ExtractXrpc, IntoRouter}; 4 + use jacquard_common::types::string::Did; 5 + use serde::{Deserialize, Serialize}; 6 + use std::collections::BTreeMap; 7 + 8 + // Mock XRPC endpoint for testing 9 + #[derive(Debug, Clone, Serialize, Deserialize)] 10 + struct TestQueryRequest<'a> { 11 + #[serde(borrow)] 12 + did: Did<'a>, 13 + #[serde(default)] 14 + limit: Option<u32>, 15 + } 16 + 17 + impl jacquard::IntoStatic for TestQueryRequest<'_> { 18 + type Output = TestQueryRequest<'static>; 19 + 20 + fn into_static(self) -> Self::Output { 21 + TestQueryRequest { 22 + did: self.did.into_static(), 23 + limit: self.limit, 24 + } 25 + } 26 + } 27 + 28 + #[derive(Debug, Clone, Serialize, Deserialize)] 29 + struct TestQueryResponse<'a> { 30 + #[serde(borrow)] 31 + did: Did<'a>, 32 + #[serde(skip_serializing_if = "BTreeMap::is_empty", default)] 33 + extra_data: BTreeMap<String, serde_json::Value>, 34 + } 35 + 36 + impl jacquard::IntoStatic for TestQueryResponse<'_> { 37 + type Output = TestQueryResponse<'static>; 38 + 39 + fn into_static(self) -> Self::Output { 40 + TestQueryResponse { 41 + did: self.did.into_static(), 42 + extra_data: self.extra_data, 43 + } 44 + } 45 + } 46 + 47 + #[derive(Debug, Clone, Serialize, Deserialize, thiserror::Error)] 48 + #[error("test error")] 49 + struct TestError; 50 + 51 + impl jacquard::IntoStatic for TestError { 52 + type Output = TestError; 53 + 54 + fn into_static(self) -> Self::Output { 55 + self 56 + } 57 + } 58 + 59 + impl jacquard::xrpc::XrpcResp for TestQueryResponse<'_> { 60 + const NSID: &'static str = "com.example.test.query"; 61 + const ENCODING: &'static str = "application/json"; 62 + type Output<'a> = TestQueryResponse<'a>; 63 + type Err<'a> = TestError; 64 + } 65 + 66 + impl jacquard::xrpc::XrpcRequest for TestQueryRequest<'_> { 67 + const NSID: &'static str = "com.example.test.query"; 68 + const METHOD: jacquard::xrpc::XrpcMethod = jacquard::xrpc::XrpcMethod::Query; 69 + type Response = TestQueryResponse<'static>; 70 + } 71 + 72 + impl jacquard::xrpc::XrpcEndpoint for TestQueryRequest<'_> { 73 + const PATH: &'static str = "/xrpc/com.example.test.query"; 74 + const METHOD: jacquard::xrpc::XrpcMethod = jacquard::xrpc::XrpcMethod::Query; 75 + type Request<'a> = TestQueryRequest<'a>; 76 + type Response = TestQueryResponse<'static>; 77 + } 78 + 79 + async fn test_handler(ExtractXrpc(req): ExtractXrpc<TestQueryRequest<'_>>) -> impl IntoResponse { 80 + Json(TestQueryResponse { 81 + did: req.did, 82 + extra_data: BTreeMap::new(), 83 + }) 84 + } 85 + 86 + #[tokio::test] 87 + async fn test_url_encoded_did_in_query_params() { 88 + let app = Router::new().merge(TestQueryRequest::into_router(test_handler)); 89 + 90 + let server = TestServer::new(app).unwrap(); 91 + 92 + // Test with URL-encoded DID (colons should be encoded as %3A) 93 + let response = server 94 + .get("/xrpc/com.example.test.query?did=did%3Aplc%3A123abc") 95 + .await; 96 + 97 + response.assert_status_ok(); 98 + 99 + let body_text = response.text(); 100 + println!("URL-encoded test response: {}", body_text); 101 + let body: TestQueryResponse = serde_json::from_str(&body_text).unwrap(); 102 + println!("Parsed DID: {}", body.did.as_str()); 103 + assert_eq!(body.did.as_str(), "did:plc:123abc"); 104 + } 105 + 106 + #[tokio::test] 107 + async fn test_unencoded_did_in_query_params() { 108 + let app = Router::new().merge(TestQueryRequest::into_router(test_handler)); 109 + 110 + let server = TestServer::new(app).unwrap(); 111 + 112 + // Test with unencoded DID (some clients might send it unencoded) 113 + let response = server 114 + .get("/xrpc/com.example.test.query?did=did:plc:123abc") 115 + .await; 116 + 117 + response.assert_status_ok(); 118 + 119 + let body_text = response.text(); 120 + println!("Unencoded test response: {}", body_text); 121 + let body: TestQueryResponse = serde_json::from_str(&body_text).unwrap(); 122 + println!("Parsed DID: {}", body.did.as_str()); 123 + assert_eq!(body.did.as_str(), "did:plc:123abc"); 124 + } 125 + 126 + #[tokio::test] 127 + async fn test_multiple_params_with_encoded_did() { 128 + let app = Router::new().merge(TestQueryRequest::into_router(test_handler)); 129 + 130 + let server = TestServer::new(app).unwrap(); 131 + 132 + // Test with multiple params including URL-encoded DID 133 + let response = server 134 + .get("/xrpc/com.example.test.query?did=did%3Aweb%3Aexample.com&limit=50") 135 + .await; 136 + 137 + response.assert_status_ok(); 138 + 139 + let body_text = response.text(); 140 + println!("Multiple params test response: {}", body_text); 141 + let body: TestQueryResponse = serde_json::from_str(&body_text).unwrap(); 142 + println!("Parsed DID: {}", body.did.as_str()); 143 + assert_eq!(body.did.as_str(), "did:web:example.com"); 144 + }