Monorepo for Tangled tangled.org
2

Configure Feed

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

bobbin/xrpc: don't need to encode at-uris

Lewis: May this revision serve well! <lewis@tangled.org>

author
Lewis
date (Jun 18, 2026, 2:50 PM +0300) commit 091d599e parent 1f3d6fdc change-id srklprrq
+159 -1
+1 -1
bobbin/crates/xrpc/tests/aggregation.rs
··· 155 155 } 156 156 157 157 fn list_request(endpoint: &str, subject: &str, extras: &[(&str, &str)]) -> Request<Body> { 158 - let mut qs = format!("subject={}", encode(subject)); 158 + let mut qs = format!("subject={subject}"); 159 159 extras.iter().for_each(|(k, v)| { 160 160 qs.push('&'); 161 161 qs.push_str(k);
+37
bobbin/crates/xrpc/tests/bulk.rs
··· 127 127 fn bulk_request(endpoint: &str, key: &str, values: &[&str]) -> Request<Body> { 128 128 let qs = values 129 129 .iter() 130 + .map(|v| format!("{key}={v}")) 131 + .collect::<Vec<_>>() 132 + .join("&"); 133 + Request::builder() 134 + .uri(format!("/xrpc/{endpoint}?{qs}")) 135 + .body(Body::empty()) 136 + .unwrap() 137 + } 138 + 139 + fn bulk_request_escaped(endpoint: &str, key: &str, values: &[&str]) -> Request<Body> { 140 + let qs = values 141 + .iter() 130 142 .map(|v| format!("{key}={}", enc(v))) 131 143 .collect::<Vec<_>>() 132 144 .join("&"); ··· 258 270 assert_eq!(status, StatusCode::OK); 259 271 let items = body["items"].as_array().unwrap(); 260 272 assert_eq!(items.len(), 2); 273 + } 274 + 275 + #[tokio::test] 276 + async fn get_profiles_accepts_percent_escaped_at_uris() { 277 + let h = Harness::new().await; 278 + h.mount( 279 + &did("did:plc:nel"), 280 + &nsid("sh.tangled.actor.profile"), 281 + &rkey("self"), 282 + profile_body(&handle("witchcraft.systems")), 283 + ) 284 + .await; 285 + let app = router(h.state.clone()); 286 + let (status, body) = json_response( 287 + app.oneshot(bulk_request_escaped( 288 + "sh.tangled.actor.getProfiles", 289 + "actors", 290 + &["at://did:plc:nel/sh.tangled.actor.profile/self"], 291 + )) 292 + .await 293 + .unwrap(), 294 + ) 295 + .await; 296 + assert_eq!(status, StatusCode::OK); 297 + assert_eq!(body["items"].as_array().unwrap().len(), 1); 261 298 } 262 299 263 300 #[tokio::test]
+60
bobbin/crates/xrpc/tests/cold_start.rs
··· 84 84 } 85 85 86 86 fn xrpc_request(endpoint: &str, param: &str, value: &str) -> Request<Body> { 87 + Request::builder() 88 + .uri(format!("/xrpc/{endpoint}?{param}={value}")) 89 + .body(Body::empty()) 90 + .unwrap() 91 + } 92 + 93 + fn xrpc_request_escaped(endpoint: &str, param: &str, value: &str) -> Request<Body> { 87 94 let encoded: String = byte_serialize(value.as_bytes()).collect(); 88 95 Request::builder() 89 96 .uri(format!("/xrpc/{endpoint}?{param}={encoded}")) ··· 212 219 } 213 220 }) 214 221 .await; 222 + } 223 + 224 + #[tokio::test] 225 + async fn percent_escaped_at_uri_resolves_identically_to_raw() { 226 + let server = MockServer::start().await; 227 + let clam = did("did:plc:clam"); 228 + mount_record( 229 + &server, 230 + &clam, 231 + &nsid("sh.tangled.actor.profile"), 232 + &rkey("self"), 233 + json!({ 234 + "$type": "sh.tangled.actor.profile", 235 + "bluesky": false, 236 + "description": "clam shell" 237 + }), 238 + ) 239 + .await; 240 + 241 + let state = fresh_app(&Url::parse(&server.uri()).unwrap()).await; 242 + let app = router(state); 243 + 244 + let at_uri = format!("at://{}/sh.tangled.actor.profile/self", clam.as_ref()); 245 + 246 + let (raw_status, raw_body) = json_response( 247 + app.clone() 248 + .oneshot(xrpc_request( 249 + "sh.tangled.actor.getProfile", 250 + "actor", 251 + &at_uri, 252 + )) 253 + .await 254 + .unwrap(), 255 + ) 256 + .await; 257 + let (escaped_status, escaped_body) = json_response( 258 + app.oneshot(xrpc_request_escaped( 259 + "sh.tangled.actor.getProfile", 260 + "actor", 261 + &at_uri, 262 + )) 263 + .await 264 + .unwrap(), 265 + ) 266 + .await; 267 + 268 + assert_eq!(raw_status, StatusCode::OK, "raw at-uri status"); 269 + assert_eq!(escaped_status, StatusCode::OK, "escaped at-uri status"); 270 + assert_eq!( 271 + raw_body, escaped_body, 272 + "raw and escaped must resolve identically" 273 + ); 274 + assert_eq!(escaped_body["uri"], at_uri); 215 275 } 216 276 217 277 #[tokio::test]
+61
bobbin/crates/xrpc/tests/extended.rs
··· 133 133 } 134 134 135 135 fn list_request(endpoint: &str, subject: &str, extras: &[(&str, &str)]) -> Request<Body> { 136 + let mut qs = format!("subject={subject}"); 137 + extras.iter().for_each(|(k, v)| { 138 + qs.push('&'); 139 + qs.push_str(k); 140 + qs.push('='); 141 + qs.push_str(&encode(v)); 142 + }); 143 + Request::builder() 144 + .uri(format!("/xrpc/{endpoint}?{qs}")) 145 + .body(Body::empty()) 146 + .unwrap() 147 + } 148 + 149 + fn list_request_escaped_subject( 150 + endpoint: &str, 151 + subject: &str, 152 + extras: &[(&str, &str)], 153 + ) -> Request<Body> { 136 154 let mut qs = format!("subject={}", encode(subject)); 137 155 extras.iter().for_each(|(k, v)| { 138 156 qs.push('&'); ··· 383 401 assert_eq!(items.len(), 1); 384 402 assert_eq!(items[0]["value"]["subject"], json!(issue_uri.as_ref())); 385 403 assert_eq!(items[0]["value"]["add"][0]["key"], json!(def_uri.as_ref())); 404 + } 405 + 406 + #[tokio::test] 407 + async fn list_ops_accepts_percent_escaped_subject() { 408 + let h = Harness::new().await; 409 + let issue_uri = at("at://did:plc:clam/sh.tangled.repo.issue/i1"); 410 + let author = did("did:plc:nel"); 411 + let rk = rkey("op1"); 412 + let def_uri = at("at://did:plc:clam/sh.tangled.label.definition/bug"); 413 + h.add_edge( 414 + &nsid("sh.tangled.label.op"), 415 + &issue_uri, 416 + &at(&format!( 417 + "at://{}/sh.tangled.label.op/{}", 418 + author.as_ref(), 419 + rk.as_ref() 420 + )), 421 + ); 422 + h.mount( 423 + &author, 424 + &nsid("sh.tangled.label.op"), 425 + &rk, 426 + label_op_body(&issue_uri, &def_uri, "true"), 427 + ) 428 + .await; 429 + 430 + let app = router(h.state.clone()); 431 + let (status, body) = json_response( 432 + app.oneshot(list_request_escaped_subject( 433 + "sh.tangled.label.listOps", 434 + issue_uri.as_ref(), 435 + &[], 436 + )) 437 + .await 438 + .unwrap(), 439 + ) 440 + .await; 441 + assert_eq!( 442 + status, 443 + StatusCode::OK, 444 + "escaped subject must still be accepted" 445 + ); 446 + assert_eq!(body["items"].as_array().unwrap().len(), 1); 386 447 } 387 448 388 449 #[tokio::test]