CLI app for developers prototyping atproto functionality
1
fork

Configure Feed

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

refactor: type FetchRecordError and populate NamedSource on body failures

- Replace (Option<u16>, String, bool) tuple return with typed FetchRecordError enum
- Add variants: Network, NotFound, HttpStatus, MissingValue, ParseEnvelope, ParseRecord
- Populate NamedSource for body-related failures (missing_value, parse_envelope, parse_record)
- Update call site to match on error variants and construct diagnostics with source context
- This addresses I-RE-1 and I-RE-2 deferred issues across two previous cycles

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

+268 -137
+3
Cargo.toml
··· 30 30 [dev-dependencies] 31 31 insta = "1.47" 32 32 tokio = { version = "1.51", features = ["rt", "macros", "test-util", "time"] } 33 + 34 + [lints.clippy] 35 + uninlined_format_args = "deny"
+186 -82
src/commands/test/labeler/identity.rs
··· 60 60 named_source: NamedSource<Arc<[u8]>>, 61 61 /// The span highlighting the "service" key. 62 62 #[label("service array")] 63 - span: SourceSpan, 63 + span: Option<SourceSpan>, 64 64 } 65 65 66 66 /// Represents a labeler endpoint that is not a valid URL. ··· 75 75 named_source: NamedSource<Arc<[u8]>>, 76 76 /// The span highlighting the endpoint value. 77 77 #[label("endpoint value")] 78 - span: SourceSpan, 78 + span: Option<SourceSpan>, 79 79 } 80 80 81 81 /// Represents a labeler endpoint that does not use HTTPS. ··· 90 90 named_source: NamedSource<Arc<[u8]>>, 91 91 /// The span highlighting the endpoint value. 92 92 #[label("endpoint value")] 93 - span: SourceSpan, 93 + span: Option<SourceSpan>, 94 94 } 95 95 96 96 /// Represents an endpoint mismatch between resolved and provided. 97 97 #[derive(Debug, Error, Diagnostic)] 98 98 #[error("{message}")] 99 - #[diagnostic(code = "labeler::identity::labeler_endpoint_matches_flag")] 99 + #[diagnostic(code = "labeler::identity::resolved_did_matches_flag")] 100 100 struct EndpointMismatchError { 101 101 /// The error message. 102 102 message: String, ··· 105 105 named_source: NamedSource<Arc<[u8]>>, 106 106 /// The span highlighting the endpoint value. 107 107 #[label("endpoint value")] 108 - span: SourceSpan, 108 + span: Option<SourceSpan>, 109 109 } 110 110 111 111 /// Represents a missing verification method error. ··· 120 120 named_source: NamedSource<Arc<[u8]>>, 121 121 /// The span highlighting the "verificationMethod" key. 122 122 #[label("verificationMethod array")] 123 - span: SourceSpan, 123 + span: Option<SourceSpan>, 124 124 } 125 125 126 126 /// Represents an unparseable signing key error. ··· 135 135 named_source: NamedSource<Arc<[u8]>>, 136 136 /// The span highlighting the multikey. 137 137 #[label("multikey")] 138 - span: SourceSpan, 138 + span: Option<SourceSpan>, 139 139 } 140 140 141 141 /// Represents a missing PDS service error. ··· 150 150 named_source: NamedSource<Arc<[u8]>>, 151 151 /// The span highlighting the "service" key. 152 152 #[label("service array")] 153 - span: SourceSpan, 153 + span: Option<SourceSpan>, 154 + } 155 + 156 + /// Typed error for labeler record fetch failures with rich diagnostics. 157 + #[derive(Debug, Error)] 158 + enum FetchRecordError { 159 + /// Network failure fetching labeler record. 160 + #[error("Network failure fetching labeler record")] 161 + Network(#[from] IdentityError), 162 + 163 + /// PDS returned 404: labeler record not found. 164 + #[error("PDS returned 404: labeler record not found")] 165 + NotFound, 166 + 167 + /// PDS returned unexpected HTTP status. 168 + #[error("PDS returned HTTP {status}")] 169 + HttpStatus { status: u16, body: Arc<[u8]> }, 170 + 171 + /// PDS response missing 'value' field. 172 + #[error("PDS response envelope missing 'value' field")] 173 + MissingValue { body: Arc<[u8]> }, 174 + 175 + /// Failed to parse PDS getRecord envelope. 176 + #[error("Failed to parse PDS getRecord envelope")] 177 + ParseEnvelope { 178 + body: Arc<[u8]>, 179 + #[source] 180 + source: serde_json::Error, 181 + }, 182 + 183 + /// Failed to parse labeler record body. 184 + #[error("Failed to parse labeler record body")] 185 + ParseRecord { 186 + body: Arc<[u8]>, 187 + #[source] 188 + source: serde_json::Error, 189 + }, 154 190 } 155 191 156 - /// Represents a labeler record fetch error with rich diagnostics. 192 + /// Diagnostic wrapper for labeler record fetch errors with rich context. 157 193 #[derive(Debug, Error, Diagnostic)] 158 194 #[error("{message}")] 159 195 #[diagnostic(code = "labeler::identity::labeler_record_fetched")] ··· 178 214 /// The raw labeler record bytes. 179 215 #[source_code] 180 216 named_source: NamedSource<Arc<[u8]>>, 181 - /// The span highlighting the "policies" key. 182 - #[label("policies field")] 183 - span: SourceSpan, 217 + /// The span highlighting the "labelValues" key. 218 + #[label("labelValues is empty")] 219 + span: Option<SourceSpan>, 220 + } 221 + 222 + /// Mapping from check ID suffix to human-readable summary. 223 + const IDENTITY_CHECK_SUMMARIES: &[(&str, &str)] = &[ 224 + ("identity::target_resolved", "target resolved"), 225 + ("identity::did_document_fetched", "DID document fetched"), 226 + ( 227 + "identity::labeler_service_present", 228 + "labeler service present", 229 + ), 230 + ( 231 + "identity::labeler_endpoint_is_https", 232 + "labeler endpoint is HTTPS", 233 + ), 234 + ( 235 + "identity::resolved_did_matches_flag", 236 + "resolved DID matches flag", 237 + ), 238 + ("identity::signing_key_present", "signing key present"), 239 + ("identity::pds_endpoint_present", "PDS endpoint present"), 240 + ("identity::labeler_record_fetched", "labeler record fetched"), 241 + ( 242 + "identity::labeler_record_policies_nonempty", 243 + "labeler record policies nonempty", 244 + ), 245 + ]; 246 + 247 + /// Look up a human-readable summary for a check ID. 248 + fn summary_for_check_id(check_id: &str) -> Cow<'static, str> { 249 + IDENTITY_CHECK_SUMMARIES 250 + .iter() 251 + .find(|(id, _)| *id == check_id) 252 + .map(|(_, summary)| Cow::Borrowed(*summary)) 253 + .unwrap_or(Cow::Borrowed("unknown")) 184 254 } 185 255 186 256 /// Run the identity stage of the labeler conformance suite. ··· 210 280 id: check_id, 211 281 stage: Stage::Identity, 212 282 status: CheckStatus::Skipped, 213 - summary: Cow::Borrowed(check_id.split("::").nth(1).unwrap_or("unknown")), 283 + summary: summary_for_check_id(check_id), 214 284 diagnostic: None, 215 285 skipped_reason: Some(Cow::Borrowed(skip_msg)), 216 286 }); ··· 225 295 let resolved_did: Option<Did> = match target { 226 296 LabelerTarget::Identified { 227 297 identifier, 228 - explicit_did, 229 - } => resolve_identifier(identifier, explicit_did, http, dns, &mut results).await, 298 + explicit_did: _, 299 + } => resolve_identifier(identifier, http, dns, &mut results).await, 230 300 LabelerTarget::Endpoint { did, .. } => { 231 301 // DID was explicitly provided via flag; treat as resolved. 232 302 results.push(CheckResult { ··· 247 317 id: "identity::did_document_fetched", 248 318 stage: Stage::Identity, 249 319 status: CheckStatus::Skipped, 250 - summary: Cow::Borrowed("DID document fetched"), 320 + summary: summary_for_check_id("identity::did_document_fetched"), 251 321 diagnostic: None, 252 322 skipped_reason: Some(Cow::Borrowed("blocked by identity::target_resolved")), 253 323 }); ··· 264 334 id: check_id, 265 335 stage: Stage::Identity, 266 336 status: CheckStatus::Skipped, 267 - summary: Cow::Borrowed(check_id.split("::").nth(1).unwrap_or("unknown")), 337 + summary: summary_for_check_id(check_id), 268 338 diagnostic: None, 269 339 skipped_reason: Some(Cow::Borrowed("blocked by identity::target_resolved")), 270 340 }); ··· 297 367 } => { 298 368 let diag: Box<dyn Diagnostic + Send + Sync> = 299 369 Box::new(DidDocumentDecodeError { 300 - message: format!("DID document JSON decode failed: {}", e), 370 + message: format!("DID document JSON decode failed: {e}"), 301 371 named_source: NamedSource::new( 302 372 source_name.clone(), 303 373 source_bytes.clone(), ··· 490 560 Some(resolved_endpoint), 491 561 ) => { 492 562 if !endpoints_match(flag_url, resolved_endpoint) { 493 - let span = json_span_for_quoted_literal( 494 - raw_did_doc.source_bytes.as_ref(), 495 - resolved_endpoint.as_ref(), 496 - ); 563 + // Search for the raw endpoint string from the DID doc's service entry. 564 + let service = 565 + find_service(&raw_did_doc.parsed, "atproto_labeler", "AtprotoLabeler"); 566 + let span = service.and_then(|svc| { 567 + json_span_for_quoted_literal( 568 + raw_did_doc.source_bytes.as_ref(), 569 + &svc.service_endpoint, 570 + ) 571 + }); 497 572 let diag = Box::new(EndpointMismatchError { 498 573 message: format!( 499 - "DID document endpoint ({}) does not match provided endpoint ({})", 500 - resolved_endpoint, flag_url 574 + "DID document endpoint ({resolved_endpoint}) does not match provided endpoint ({flag_url})" 501 575 ), 502 576 named_source: NamedSource::new( 503 577 raw_did_doc.source_name.clone(), ··· 584 658 let span = 585 659 json_span_for_quoted_literal(raw_did_doc.source_bytes.as_ref(), &multikey_str); 586 660 let diag = Box::new(SigningKeyUnparseableError { 587 - message: format!("Failed to parse signing key multikey: {}", e), 661 + message: format!("Failed to parse signing key multikey: {e}"), 588 662 named_source: NamedSource::new( 589 663 raw_did_doc.source_name.clone(), 590 664 raw_did_doc.source_bytes.clone(), ··· 739 813 }); 740 814 (Some(bytes), Some(policies)) 741 815 } 742 - Err((_status, msg, is_network)) => { 743 - let status = if is_network { 744 - CheckStatus::NetworkError 745 - } else { 746 - CheckStatus::SpecViolation 816 + Err(e) => { 817 + let (check_status, message, named_source, span) = match &e { 818 + FetchRecordError::Network(_) => { 819 + (CheckStatus::NetworkError, e.to_string(), None, None) 820 + } 821 + FetchRecordError::NotFound => { 822 + (CheckStatus::SpecViolation, e.to_string(), None, None) 823 + } 824 + FetchRecordError::HttpStatus { .. } => { 825 + (CheckStatus::SpecViolation, e.to_string(), None, None) 826 + } 827 + FetchRecordError::MissingValue { body } => { 828 + let src = NamedSource::new("PDS response", body.clone()); 829 + let span = SourceSpan::new(0.into(), body.len()); 830 + ( 831 + CheckStatus::SpecViolation, 832 + "PDS response envelope missing 'value' field".to_string(), 833 + Some(src), 834 + Some(span), 835 + ) 836 + } 837 + FetchRecordError::ParseEnvelope { body, .. } => { 838 + let src = NamedSource::new("PDS response", body.clone()); 839 + let span = SourceSpan::new(0.into(), body.len()); 840 + ( 841 + CheckStatus::SpecViolation, 842 + "Failed to parse PDS getRecord envelope".to_string(), 843 + Some(src), 844 + Some(span), 845 + ) 846 + } 847 + FetchRecordError::ParseRecord { body, .. } => { 848 + let src = NamedSource::new("labeler record", body.clone()); 849 + let span = SourceSpan::new(0.into(), body.len()); 850 + ( 851 + CheckStatus::SpecViolation, 852 + "Failed to parse labeler record body".to_string(), 853 + Some(src), 854 + Some(span), 855 + ) 856 + } 747 857 }; 748 858 let diag = Box::new(LabelerRecordFetchError { 749 - message: msg, 750 - named_source: None, 751 - span: None, 859 + message, 860 + named_source, 861 + span, 752 862 }); 753 863 block_facts = true; 754 864 results.push(CheckResult { 755 865 id: "identity::labeler_record_fetched", 756 866 stage: Stage::Identity, 757 - status, 867 + status: check_status, 758 868 summary: Cow::Borrowed("labeler record fetched"), 759 869 diagnostic: Some(diag), 760 870 skipped_reason: None, ··· 778 888 } 779 889 (Some(bytes), Some(policies)) => { 780 890 if policies.label_values.is_empty() { 781 - let span = json_span_for_quoted_literal(bytes.as_ref(), "policies"); 891 + let span = json_span_for_quoted_literal(bytes.as_ref(), "labelValues"); 782 892 let diag = Box::new(EmptyPoliciesError { 783 893 message: "Labeler record policies.labelValues is empty".to_string(), 784 894 named_source: NamedSource::new("labeler record", bytes.clone()), ··· 850 960 /// Helper to resolve an identifier (handle or DID) to a DID. 851 961 async fn resolve_identifier( 852 962 identifier: &AtIdentifier, 853 - _explicit_did: &Option<Did>, 854 963 http: &dyn HttpClient, 855 964 dns: &dyn DnsResolver, 856 965 results: &mut Vec<CheckResult>, 857 966 ) -> Option<Did> { 858 967 // Note: explicit_did vs resolved DID mismatch is checked separately as 859 - // resolved_did_matches_flag in the main run() function, not here. 968 + // resolved_did_matches_flag in the main run() function. 860 969 match identifier { 861 970 AtIdentifier::Handle(handle) => match resolve_handle(handle, http, dns).await { 862 971 Ok(did) => { ··· 929 1038 } 930 1039 931 1040 /// Helper to find the span of a JSON quoted literal (key or string value) in a byte slice. 1041 + /// 932 1042 /// Scans for the literal `"<literal>"` pattern and returns the span covering the entire 933 1043 /// quoted string including the quotes. This works for both JSON keys and string values since 934 1044 /// both are quoted identically in JSON. 1045 + /// 935 1046 /// For Phase 3 a simple substring search is acceptable since documents are small. 936 - fn json_span_for_quoted_literal(bytes: &[u8], literal: &str) -> SourceSpan { 937 - let search = format!("\"{}\"", literal); 938 - if let Some(pos) = bytes 1047 + fn json_span_for_quoted_literal(bytes: &[u8], literal: &str) -> Option<SourceSpan> { 1048 + let search = format!("\"{literal}\""); 1049 + bytes 939 1050 .windows(search.len()) 940 1051 .position(|w| w == search.as_bytes()) 941 - { 942 - SourceSpan::new((pos).into(), search.len()) 943 - } else { 944 - SourceSpan::new(0.into(), 0) 945 - } 1052 + .map(|pos| SourceSpan::new((pos).into(), search.len())) 946 1053 } 947 1054 948 1055 /// Fetch the labeler record from the PDS using the HTTP client seam. 949 - /// Returns (bytes, policies) on success, or (status, message, is_network_error) on failure. 1056 + /// Returns (bytes, policies) on success. 950 1057 async fn fetch_labeler_record( 951 1058 did: &Did, 952 1059 pds_endpoint: &Url, 953 1060 http: &dyn HttpClient, 954 - ) -> Result<(Arc<[u8]>, LabelerPolicies), (Option<u16>, String, bool)> { 1061 + ) -> Result<(Arc<[u8]>, LabelerPolicies), FetchRecordError> { 955 1062 // Build the XRPC request URL. 956 1063 let mut url = pds_endpoint.clone(); 957 1064 url.set_path("/xrpc/com.atproto.repo.getRecord"); ··· 964 1071 // Perform the HTTP request using the seam. 965 1072 let (status, body) = match http.get_bytes(&url).await { 966 1073 Ok((status, body)) => (status, body), 967 - Err(_) => return Err((None, "PDS request failed (network error)".to_string(), true)), 1074 + Err(e) => return Err(FetchRecordError::Network(e)), 968 1075 }; 969 1076 1077 + let body_arc: Arc<[u8]> = Arc::from(body); 1078 + 970 1079 // Handle HTTP status codes. 971 1080 match status { 972 - 404 => Err(( 973 - Some(404), 974 - "PDS returned 404: labeler record not found".to_string(), 975 - false, 976 - )), 1081 + 404 => Err(FetchRecordError::NotFound), 977 1082 200 => { 978 1083 // Try to deserialize the response body into LabelerPolicies. 979 1084 // The response is wrapped in {value: LabelerPolicies, uri, cid}, so extract the value field. 980 - match serde_json::from_slice::<serde_json::Value>(&body) { 1085 + match serde_json::from_slice::<serde_json::Value>(body_arc.as_ref()) { 981 1086 Ok(response_obj) => { 982 1087 if let Some(value) = response_obj.get("value") { 983 1088 match serde_json::from_value::<LabelerPolicies>(value.clone()) { 984 - Ok(policies) => Ok((Arc::from(body), policies)), 985 - Err(e) => Err(( 986 - Some(200), 987 - format!("Failed to parse labeler record: {}", e), 988 - false, 989 - )), 1089 + Ok(policies) => Ok((body_arc, policies)), 1090 + Err(e) => Err(FetchRecordError::ParseRecord { 1091 + body: body_arc, 1092 + source: e, 1093 + }), 990 1094 } 991 1095 } else { 992 - Err(( 993 - Some(200), 994 - "Response missing 'value' field".to_string(), 995 - false, 996 - )) 1096 + Err(FetchRecordError::MissingValue { body: body_arc }) 997 1097 } 998 1098 } 999 - Err(e) => Err(( 1000 - Some(200), 1001 - format!("Failed to parse PDS response: {}", e), 1002 - false, 1003 - )), 1099 + Err(e) => Err(FetchRecordError::ParseEnvelope { 1100 + body: body_arc, 1101 + source: e, 1102 + }), 1004 1103 } 1005 1104 } 1006 - _ => Err(( 1007 - Some(status), 1008 - format!("PDS returned unexpected status {}", status), 1009 - false, 1010 - )), 1105 + _ => Err(FetchRecordError::HttpStatus { 1106 + status, 1107 + body: body_arc, 1108 + }), 1011 1109 } 1012 1110 } 1013 1111 ··· 1031 1129 fn json_span_for_quoted_literal_key() { 1032 1130 let json = br#"{"service": [], "other": 123}"#; 1033 1131 let span = json_span_for_quoted_literal(json, "service"); 1034 - // If it found the key, the length should be non-zero. 1035 - assert!(!span.is_empty(), "should find service key in JSON"); 1132 + // If it found the key, Some with non-zero length. 1133 + assert!( 1134 + span.is_some_and(|s| !s.is_empty()), 1135 + "should find service key in JSON" 1136 + ); 1036 1137 } 1037 1138 1038 1139 #[test] 1039 1140 fn json_span_for_quoted_literal_not_found() { 1040 1141 let json = br#"{"other": 123}"#; 1041 1142 let span = json_span_for_quoted_literal(json, "service"); 1042 - // If key not found, both offset and length should be zero. 1043 - assert_eq!(span.len(), 0, "should not find service key in JSON"); 1143 + // If key not found, None. 1144 + assert!(span.is_none(), "should not find service key in JSON"); 1044 1145 } 1045 1146 1046 1147 #[test] ··· 1048 1149 let json = br#"{"serviceEndpoint": "https://example.com"}"#; 1049 1150 let span = json_span_for_quoted_literal(json, "https://example.com"); 1050 1151 // Should find the quoted value. 1051 - assert!(!span.is_empty(), "should find value in JSON"); 1152 + assert!( 1153 + span.is_some_and(|s| !s.is_empty()), 1154 + "should find value in JSON" 1155 + ); 1052 1156 } 1053 1157 1054 1158 #[test] 1055 1159 fn json_span_for_quoted_literal_value_not_found() { 1056 1160 let json = br#"{"serviceEndpoint": "https://example.com"}"#; 1057 1161 let span = json_span_for_quoted_literal(json, "https://wrong.example"); 1058 - // If value not found, both offset and length should be zero. 1059 - assert_eq!(span.len(), 0, "should not find missing value in JSON"); 1162 + // If value not found, None. 1163 + assert!(span.is_none(), "should not find missing value in JSON"); 1060 1164 } 1061 1165 }
+6 -9
src/commands/test/labeler/pipeline.rs
··· 67 67 68 68 fn unrecognized_target(raw: &str) -> Self { 69 69 Self::new(format!( 70 - "Unrecognized target '{}'. Expected one of:\n - ATProto handle (e.g., alice.bsky.social)\n - DID (e.g., did:plc:abc123 or did:web:example.com)\n - HTTPS endpoint URL (e.g., https://labeler.example.com)", 71 - raw 70 + "Unrecognized target '{raw}'. Expected one of:\n - ATProto handle (e.g., alice.bsky.social)\n - DID (e.g., did:plc:abc123 or did:web:example.com)\n - HTTPS endpoint URL (e.g., https://labeler.example.com)" 72 71 )) 73 72 } 74 73 75 74 fn http_not_supported(raw: &str) -> Self { 76 75 Self::new(format!( 77 - "HTTP endpoint '{}' is not supported. Please use an HTTPS endpoint instead.", 78 - raw 76 + "HTTP endpoint '{raw}' is not supported. Please use an HTTPS endpoint instead." 79 77 )) 80 78 } 81 79 82 80 fn ambiguous_did(raw: &str, explicit: &str) -> Self { 83 81 Self::new(format!( 84 - "Ambiguous target specification: target '{}' is already a DID, but --did {} was also provided. Please use only one.", 85 - raw, explicit 82 + "Ambiguous target specification: target '{raw}' is already a DID, but --did {explicit} was also provided. Please use only one." 86 83 )) 87 84 } 88 85 } ··· 152 149 // Check for HTTPS URL. 153 150 if raw.starts_with("https://") { 154 151 let url = Url::parse(raw) 155 - .map_err(|e| TargetParseError::new(format!("Invalid URL '{}': {}", raw, e)))?; 152 + .map_err(|e| TargetParseError::new(format!("Invalid URL '{raw}': {e}")))?; 156 153 return Ok(LabelerTarget::Endpoint { 157 154 url, 158 155 did: explicit_did.map(|d| Did(d.to_string())), ··· 278 275 AtIdentifier::Did(d) => d.0.clone(), 279 276 }; 280 277 if explicit_did.is_some() { 281 - format!("{} (with explicit DID)", id_str) 278 + format!("{id_str} (with explicit DID)") 282 279 } else { 283 280 id_str 284 281 } 285 282 } 286 283 LabelerTarget::Endpoint { url, did } => { 287 284 if did.is_some() { 288 - format!("{} (with explicit DID)", url) 285 + format!("{url} (with explicit DID)") 289 286 } else { 290 287 url.to_string() 291 288 }
+5 -8
src/commands/test/labeler/report.rs
··· 213 213 // Write the check result line. 214 214 write!(out, "{} {} ", result.status, result.summary)?; 215 215 if let Some(reason) = &result.skipped_reason { 216 - write!(out, "— {}", reason)?; 216 + write!(out, "— {reason}")?; 217 217 } 218 218 writeln!(out)?; 219 219 ··· 233 233 writeln!(out, " (diagnostic rendering failed)")?; 234 234 } else { 235 235 for line in buf.lines() { 236 - writeln!(out, " {}", line)?; 236 + writeln!(out, " {line}")?; 237 237 } 238 238 } 239 239 } ··· 434 434 // Check for the expected glyphs. 435 435 assert!( 436 436 output.contains("[OK]"), 437 - "output should contain [OK] glyph:\n{}", 438 - output 437 + "output should contain [OK] glyph:\n{output}" 439 438 ); 440 439 assert!( 441 440 output.contains("[FAIL]"), 442 - "output should contain [FAIL] glyph:\n{}", 443 - output 441 + "output should contain [FAIL] glyph:\n{output}" 444 442 ); 445 443 assert!( 446 444 output.contains("[SKIP]"), 447 - "output should contain [SKIP] glyph:\n{}", 448 - output 445 + "output should contain [SKIP] glyph:\n{output}" 449 446 ); 450 447 } 451 448 }
+11 -10
src/common/identity.rs
··· 402 402 ); 403 403 404 404 // DNS path: look up _atproto.<handle> for did= records. 405 - let dns_name = format!("_atproto.{}", handle); 405 + let dns_name = format!("_atproto.{handle}"); 406 406 407 407 let dns_error_opt = match dns.txt_lookup(&dns_name).await { 408 408 Ok(records) => { ··· 428 428 }; 429 429 430 430 // HTTPS fallback: GET https://<handle>/.well-known/atproto-did. 431 - let url = format!("https://{}/.well-known/atproto-did", handle); 431 + let url = format!("https://{handle}/.well-known/atproto-did"); 432 432 let url = url 433 433 .parse::<Url>() 434 434 .map_err(|_| IdentityError::InvalidHandle)?; ··· 485 485 DidMethod::Plc => { 486 486 // did:plc identifiers are base32-like and contain only URL-safe characters. 487 487 // No additional percent-encoding is needed. 488 - let url_str = format!("https://plc.directory/{}", did.0); 488 + let did_str = &did.0; 489 + let url_str = format!("https://plc.directory/{did_str}"); 489 490 let url = url_str 490 491 .parse::<Url>() 491 492 .map_err(|_| IdentityError::DidResolutionFailed { ··· 510 511 let path_parts = &parts[1..]; 511 512 512 513 let url_str = if path_parts.is_empty() { 513 - format!("https://{}/.well-known/did.json", host) 514 + format!("https://{host}/.well-known/did.json") 514 515 } else { 515 516 let path = path_parts 516 517 .iter() 517 518 .map(|p| percent_decode_str(p).unwrap_or_default()) 518 519 .collect::<Vec<_>>() 519 520 .join("/"); 520 - format!("https://{}/{}/did.json", host, path) 521 + format!("https://{host}/{path}/did.json") 521 522 }; 522 523 523 524 let url = url_str ··· 932 933 // [0xe7, 0x01] (k256 prefix) + 33-byte SEC1 point. 933 934 let expected_hex = 934 935 "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"; 935 - let actual_hex: String = sec1_bytes.iter().map(|b| format!("{:02x}", b)).collect(); 936 + let actual_hex: String = sec1_bytes.iter().map(|b| format!("{b:02x}")).collect(); 936 937 assert_eq!(actual_hex, expected_hex); 937 938 } 938 939 _ => panic!("Expected K256 verifying key"), ··· 960 961 // Expected bytes derived from the multikey fixture. 961 962 let expected_hex = 962 963 "026b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296"; 963 - let actual_hex: String = sec1_bytes.iter().map(|b| format!("{:02x}", b)).collect(); 964 + let actual_hex: String = sec1_bytes.iter().map(|b| format!("{b:02x}")).collect(); 964 965 assert_eq!(actual_hex, expected_hex); 965 966 } 966 967 _ => panic!("Expected P256 verifying key"), ··· 990 991 let mut key_bytes = vec![0xe7, 0x01]; 991 992 key_bytes.extend_from_slice(&[0; 33]); 992 993 // Manually create base16 multibase string (f prefix). 993 - let hex_str: String = key_bytes.iter().map(|b| format!("{:02x}", b)).collect(); 994 - let multikey = format!("f{}", hex_str); 994 + let hex_str: String = key_bytes.iter().map(|b| format!("{b:02x}")).collect(); 995 + let multikey = format!("f{hex_str}"); 995 996 996 997 let result = parse_multikey(&multikey); 997 998 assert!(result.is_err()); ··· 1019 1020 IdentityError::MultikeyLengthInvalid => { 1020 1021 // Correct error variant. 1021 1022 } 1022 - e => panic!("Expected MultikeyLengthInvalid, got {:?}", e), 1023 + e => panic!("Expected MultikeyLengthInvalid, got {e:?}"), 1023 1024 } 1024 1025 } 1025 1026
+27
tests/fixtures/labeler/identity/healthy_web/did.json
··· 1 + { 2 + "@context": [ 3 + "https://www.w3.org/ns/did/v1", 4 + "https://w3id.org/security/suites/ecdsa-2019/v1" 5 + ], 6 + "id": "did:web:web-labeler.example", 7 + "verificationMethod": [ 8 + { 9 + "id": "#atproto_label", 10 + "type": "EcdsaSecp256k1VerificationKey2019", 11 + "controller": "did:web:web-labeler.example", 12 + "publicKeyMultibase": "zQ3shVc2UkAfJCdc1TR8E66J85h48P43r93q8jGPkPpjF9Ef9" 13 + } 14 + ], 15 + "service": [ 16 + { 17 + "id": "#atproto_labeler", 18 + "type": "AtprotoLabeler", 19 + "serviceEndpoint": "https://web-labeler.example" 20 + }, 21 + { 22 + "id": "#atproto_pds", 23 + "type": "AtprotoPersonalDataServer", 24 + "serviceEndpoint": "https://pds.example.com" 25 + } 26 + ] 27 + }
+6 -5
tests/snapshots/labeler_identity__empty_policies_renders_spec_violation_with_span.snap
··· 18 18 labeler::identity::labeler_record_policies_nonempty 19 19 20 20 × Labeler record policies.labelValues is empty 21 - ╭─[labeler record:1:1] 22 - 1 │ { 23 - · ▲ 24 - · ╰── policies field 25 - 2 │ "uri": "at://did:plc:empty_policies_test_123456789ab/app.bsky.labeler.service/self", 21 + ╭─[labeler record:5:5] 22 + 4 │ "value": { 23 + 5 │ "labelValues": [] 24 + · ──────┬────── 25 + · ╰── labelValues is empty 26 + 6 │ } 26 27 ╰──── 27 28 == HTTP == 28 29 [SKIP] HTTP stage (stub) — blocked by identity stage failures
+8 -7
tests/snapshots/labeler_identity__endpoint_mismatch_spec_violation.snap
··· 11 11 [OK] labeler service present 12 12 [OK] labeler endpoint is HTTPS 13 13 [FAIL] resolved DID matches flag 14 - labeler::identity::labeler_endpoint_matches_flag 14 + labeler::identity::resolved_did_matches_flag 15 15 16 16 × DID document endpoint (https://original-endpoint.example.com/) does not match provided endpoint (https://other-labeler.example/) 17 - ╭─[https://plc.directory/did:plc:endpoint_mismatch_test_123456789:1:1] 18 - 1 │ { 19 - · ▲ 20 - · ╰── endpoint value 21 - 2 │ "@context": [ 22 - ╰──── 17 + ╭─[https://plc.directory/did:plc:endpoint_mismatch_test_123456789:22:26] 18 + 21 │ "type": "AtprotoLabeler", 19 + 22 │ "serviceEndpoint": "https://original-endpoint.example.com" 20 + · ───────────────────┬─────────────────── 21 + · ╰── endpoint value 22 + 23 │ }, 23 + ╰──── 23 24 [OK] signing key present 24 25 [OK] PDS endpoint present 25 26 [OK] labeler record fetched
+9 -9
tests/snapshots/labeler_identity__endpoint_only_no_did_skips_identity.snap
··· 6 6 elapsed: 0ms 7 7 8 8 == Identity == 9 - [SKIP] target_resolved — no DID supplied; run with a handle, a DID, or --did <did> 10 - [SKIP] did_document_fetched — no DID supplied; run with a handle, a DID, or --did <did> 11 - [SKIP] labeler_service_present — no DID supplied; run with a handle, a DID, or --did <did> 12 - [SKIP] labeler_endpoint_is_https — no DID supplied; run with a handle, a DID, or --did <did> 13 - [SKIP] resolved_did_matches_flag — no DID supplied; run with a handle, a DID, or --did <did> 14 - [SKIP] signing_key_present — no DID supplied; run with a handle, a DID, or --did <did> 15 - [SKIP] pds_endpoint_present — no DID supplied; run with a handle, a DID, or --did <did> 16 - [SKIP] labeler_record_fetched — no DID supplied; run with a handle, a DID, or --did <did> 17 - [SKIP] labeler_record_policies_nonempty — no DID supplied; run with a handle, a DID, or --did <did> 9 + [SKIP] target resolved — no DID supplied; run with a handle, a DID, or --did <did> 10 + [SKIP] DID document fetched — no DID supplied; run with a handle, a DID, or --did <did> 11 + [SKIP] labeler service present — no DID supplied; run with a handle, a DID, or --did <did> 12 + [SKIP] labeler endpoint is HTTPS — no DID supplied; run with a handle, a DID, or --did <did> 13 + [SKIP] resolved DID matches flag — no DID supplied; run with a handle, a DID, or --did <did> 14 + [SKIP] signing key present — no DID supplied; run with a handle, a DID, or --did <did> 15 + [SKIP] PDS endpoint present — no DID supplied; run with a handle, a DID, or --did <did> 16 + [SKIP] labeler record fetched — no DID supplied; run with a handle, a DID, or --did <did> 17 + [SKIP] labeler record policies nonempty — no DID supplied; run with a handle, a DID, or --did <did> 18 18 == HTTP == 19 19 [SKIP] HTTP stage (stub) — not yet implemented (phase 4) 20 20 == Subscription ==
+7 -7
tests/snapshots/labeler_identity__plc_directory_unreachable_renders_network_error.snap
··· 8 8 == Identity == 9 9 [NET] target resolved 10 10 [SKIP] DID document fetched — blocked by identity::target_resolved 11 - [SKIP] labeler_service_present — blocked by identity::target_resolved 12 - [SKIP] labeler_endpoint_is_https — blocked by identity::target_resolved 13 - [SKIP] resolved_did_matches_flag — blocked by identity::target_resolved 14 - [SKIP] signing_key_present — blocked by identity::target_resolved 15 - [SKIP] pds_endpoint_present — blocked by identity::target_resolved 16 - [SKIP] labeler_record_fetched — blocked by identity::target_resolved 17 - [SKIP] labeler_record_policies_nonempty — blocked by identity::target_resolved 11 + [SKIP] labeler service present — blocked by identity::target_resolved 12 + [SKIP] labeler endpoint is HTTPS — blocked by identity::target_resolved 13 + [SKIP] resolved DID matches flag — blocked by identity::target_resolved 14 + [SKIP] signing key present — blocked by identity::target_resolved 15 + [SKIP] PDS endpoint present — blocked by identity::target_resolved 16 + [SKIP] labeler record fetched — blocked by identity::target_resolved 17 + [SKIP] labeler record policies nonempty — blocked by identity::target_resolved 18 18 == HTTP == 19 19 [SKIP] HTTP stage (stub) — blocked by identity stage failures 20 20 == Subscription ==