+2
-17
crates/jacquard-axum/src/lib.rs
+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
+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
+
}