CLI app for developers prototyping atproto functionality
1
fork

Configure Feed

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

Refactor to clean up code

+487 -498
+30 -54
src/commands/test/oauth/client.rs
··· 8 8 9 9 pub mod fake_as; 10 10 pub mod pipeline; 11 + pub mod target; 11 12 12 13 use std::process::ExitCode; 13 14 ··· 72 73 use std::sync::Arc; 73 74 74 75 // Parse the target. 75 - let target = pipeline::parse_target(&self.target).map_err(miette::Report::new)?; 76 + let target = target::parse(&self.target).map_err(miette::Report::new)?; 76 77 77 78 // Build a single shared HTTP client. 78 79 let reqwest_client = reqwest::Client::builder() ··· 95 96 let combined_no_color = no_color || self.no_color; 96 97 97 98 // Handle mode selection. 98 - match self.mode { 99 - None => { 100 - // Static mode. 101 - let opts = pipeline::OauthClientOptions { 102 - http: &http, 103 - jwks: &jwks_fetcher, 104 - verbose: self.verbose, 105 - interactive: None, 106 - clock, 107 - }; 108 - 109 - let report = pipeline::run_pipeline(target, opts).await; 110 - 111 - // Render the report to stdout. 112 - let mut stdout = io::stdout().lock(); 113 - report 114 - .render( 115 - &mut stdout, 116 - &RenderConfig { 117 - no_color: combined_no_color, 118 - }, 119 - ) 120 - .map_err(|e| miette::miette!("Failed to render report: {e}"))?; 121 - 122 - // Return appropriate exit code. 123 - let exit_code = report.exit_code(); 124 - Ok(ExitCode::from(exit_code as u8)) 125 - } 99 + let interactive = match self.mode { 100 + None => None, 126 101 Some(ClientMode::Interactive(args)) => { 127 102 // Interactive mode. 128 - let interactive_opts = pipeline::InteractiveOptions { 103 + Some(pipeline::InteractiveOptions { 129 104 bind_port: args.port, 130 105 public_base_url: args.public_base_url, 131 106 drive_mode: pipeline::InteractiveDriveMode::WaitForExternalClient, 132 - }; 107 + }) 108 + } 109 + }; 133 110 134 - let opts = pipeline::OauthClientOptions { 135 - http: &http, 136 - jwks: &jwks_fetcher, 137 - verbose: self.verbose, 138 - interactive: Some(&interactive_opts), 139 - clock, 140 - }; 111 + // Static mode. 112 + let opts = pipeline::OauthClientOptions { 113 + http: &http, 114 + jwks: &jwks_fetcher, 115 + verbose: self.verbose, 116 + interactive: interactive.as_ref(), 117 + clock, 118 + }; 141 119 142 - let report = pipeline::run_pipeline(target, opts).await; 120 + let report = pipeline::run_pipeline(target, opts).await; 143 121 144 - // Render the report to stdout. 145 - let mut stdout = io::stdout().lock(); 146 - report 147 - .render( 148 - &mut stdout, 149 - &RenderConfig { 150 - no_color: combined_no_color, 151 - }, 152 - ) 153 - .map_err(|e| miette::miette!("Failed to render report: {e}"))?; 122 + // Render the report to stdout. 123 + let mut stdout = io::stdout().lock(); 124 + report 125 + .render( 126 + &mut stdout, 127 + &RenderConfig { 128 + no_color: combined_no_color, 129 + }, 130 + ) 131 + .map_err(|e| miette::miette!("Failed to render report: {e}"))?; 154 132 155 - // Return appropriate exit code. 156 - let exit_code = report.exit_code(); 157 - Ok(ExitCode::from(exit_code as u8)) 158 - } 159 - } 133 + // Return appropriate exit code. 134 + let exit_code = report.exit_code(); 135 + Ok(ExitCode::from(exit_code as u8)) 160 136 } 161 137 }
+3 -400
src/commands/test/oauth/client/pipeline.rs
··· 1 1 //! OAuth client conformance test pipeline and target parsing. 2 2 3 + use super::target::OauthClientTarget; 4 + 3 5 pub mod discovery; 4 6 pub mod interactive; 5 7 pub mod jwks; 6 8 pub mod metadata; 7 9 8 - use miette::{Diagnostic, NamedSource, SourceSpan}; 9 - use thiserror::Error; 10 10 use url::Url; 11 11 12 - /// An OAuth client target that has been parsed and validated. 13 - #[derive(Debug, Clone)] 14 - pub enum OauthClientTarget { 15 - /// An HTTPS URL pointing at a client metadata document. 16 - HttpsUrl(Url), 17 - /// A loopback development client using implicit metadata. 18 - Loopback(LoopbackTarget), 19 - } 20 - 21 - /// A loopback development client target. 22 - #[derive(Debug, Clone)] 23 - pub struct LoopbackTarget { 24 - /// The loopback host (localhost or 127.0.0.1). 25 - pub host: LoopbackHost, 26 - /// Optional port number. 27 - pub port: Option<u16>, 28 - /// The path component, including leading slash. 29 - pub path: String, 30 - } 31 - 32 - /// A recognized loopback hostname. 33 - #[derive(Debug, Clone, Copy, PartialEq, Eq)] 34 - pub enum LoopbackHost { 35 - /// `localhost` 36 - Localhost, 37 - /// `127.0.0.1` 38 - Loopback127, 39 - } 40 - 41 - /// Error parsing a client_id target. 42 - #[derive(Debug, Error)] 43 - pub enum TargetParseError { 44 - /// The input could not be parsed as a URL. 45 - #[error("target must be a valid URL")] 46 - NotAUrl { 47 - /// The invalid input. 48 - input: String, 49 - /// Help text explaining the accepted formats. 50 - help: &'static str, 51 - /// Source code for diagnostic display. 52 - src: NamedSource<String>, 53 - /// Span highlighting the input. 54 - span: SourceSpan, 55 - }, 56 - 57 - /// An HTTPS URL is missing the host component. 58 - #[error("HTTPS client_id must include a hostname")] 59 - HttpsMissingHost { 60 - /// The invalid input. 61 - input: String, 62 - /// Help text. 63 - help: &'static str, 64 - /// Source code for diagnostic display. 65 - src: NamedSource<String>, 66 - /// Span highlighting the input. 67 - span: SourceSpan, 68 - }, 69 - 70 - /// An HTTPS URL contains query string or fragment. 71 - #[error("HTTPS client_id must not include query string or fragment")] 72 - HttpsHasQueryOrFragment { 73 - /// The invalid input. 74 - input: String, 75 - /// Help text. 76 - help: &'static str, 77 - /// Source code for diagnostic display. 78 - src: NamedSource<String>, 79 - /// Span highlighting the input. 80 - span: SourceSpan, 81 - }, 82 - 83 - /// The URL uses an unsupported scheme (neither https nor http). 84 - #[error("client_id scheme must be 'https' or 'http'")] 85 - UnsupportedScheme { 86 - /// The invalid input. 87 - input: String, 88 - /// The unsupported scheme. 89 - scheme: String, 90 - /// Help text. 91 - help: &'static str, 92 - /// Source code for diagnostic display. 93 - src: NamedSource<String>, 94 - /// Span highlighting the input. 95 - span: SourceSpan, 96 - }, 97 - 98 - /// An HTTP URL with a hostname that is not a recognized loopback address. 99 - #[error("HTTP client_id must be localhost or 127.0.0.1")] 100 - HttpNonLoopback { 101 - /// The invalid input. 102 - input: String, 103 - /// The non-loopback hostname. 104 - host: String, 105 - /// Help text. 106 - help: &'static str, 107 - /// Source code for diagnostic display. 108 - src: NamedSource<String>, 109 - /// Span highlighting the input. 110 - span: SourceSpan, 111 - }, 112 - } 113 - 114 - impl Diagnostic for TargetParseError { 115 - fn code<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> { 116 - match self { 117 - TargetParseError::NotAUrl { .. } => Some(Box::new("oauth_client::target::not_a_url")), 118 - TargetParseError::HttpsMissingHost { .. } => { 119 - Some(Box::new("oauth_client::target::https_missing_host")) 120 - } 121 - TargetParseError::HttpsHasQueryOrFragment { .. } => Some(Box::new( 122 - "oauth_client::target::https_has_query_or_fragment", 123 - )), 124 - TargetParseError::UnsupportedScheme { .. } => { 125 - Some(Box::new("oauth_client::target::unsupported_scheme")) 126 - } 127 - TargetParseError::HttpNonLoopback { .. } => { 128 - Some(Box::new("oauth_client::target::http_non_loopback")) 129 - } 130 - } 131 - } 132 - 133 - fn source_code(&self) -> Option<&(dyn miette::SourceCode + 'static)> { 134 - match self { 135 - TargetParseError::NotAUrl { src, .. } 136 - | TargetParseError::HttpsMissingHost { src, .. } 137 - | TargetParseError::HttpsHasQueryOrFragment { src, .. } 138 - | TargetParseError::UnsupportedScheme { src, .. } 139 - | TargetParseError::HttpNonLoopback { src, .. } => Some(src), 140 - } 141 - } 142 - 143 - fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> { 144 - let span = match self { 145 - TargetParseError::NotAUrl { span, .. } 146 - | TargetParseError::HttpsMissingHost { span, .. } 147 - | TargetParseError::HttpsHasQueryOrFragment { span, .. } 148 - | TargetParseError::UnsupportedScheme { span, .. } 149 - | TargetParseError::HttpNonLoopback { span, .. } => span, 150 - }; 151 - 152 - Some(Box::new(std::iter::once(miette::LabeledSpan::new( 153 - Some("here".to_string()), 154 - span.offset(), 155 - span.len(), 156 - )))) 157 - } 158 - 159 - fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> { 160 - match self { 161 - TargetParseError::NotAUrl { help, .. } 162 - | TargetParseError::HttpsMissingHost { help, .. } 163 - | TargetParseError::HttpsHasQueryOrFragment { help, .. } 164 - | TargetParseError::UnsupportedScheme { help, .. } 165 - | TargetParseError::HttpNonLoopback { help, .. } => Some(Box::new(*help)), 166 - } 167 - } 168 - } 169 - 170 - impl TargetParseError { 171 - fn help_text() -> &'static str { 172 - "Accepted forms: https://example.com/path (for production), http://localhost[:port][/path] or http://127.0.0.1[:port][/path] (for development)" 173 - } 174 - } 175 - 176 - /// Parse a client_id target string into a validated `OauthClientTarget`. 177 - /// 178 - /// The atproto OAuth spec allows two forms: 179 - /// - Production: `https://...` with a hostname and no query/fragment. 180 - /// - Development loopback: `http://localhost[:port][/path]` or `http://127.0.0.1[:port][/path]`. 181 - #[expect(clippy::result_large_err)] 182 - pub fn parse_target(raw: &str) -> Result<OauthClientTarget, TargetParseError> { 183 - let span = SourceSpan::new(0.into(), raw.len()); 184 - let src = NamedSource::new("<client_id>", raw.to_string()); 185 - 186 - let url = Url::parse(raw).map_err(|_| TargetParseError::NotAUrl { 187 - input: raw.to_string(), 188 - help: TargetParseError::help_text(), 189 - src: src.clone(), 190 - span, 191 - })?; 192 - 193 - match url.scheme() { 194 - "https" => { 195 - // HTTPS must have a host. 196 - if url.host().is_none() { 197 - return Err(TargetParseError::HttpsMissingHost { 198 - input: raw.to_string(), 199 - help: TargetParseError::help_text(), 200 - src: src.clone(), 201 - span, 202 - }); 203 - } 204 - 205 - // HTTPS must not have query or fragment. 206 - if url.query().is_some() || url.fragment().is_some() { 207 - return Err(TargetParseError::HttpsHasQueryOrFragment { 208 - input: raw.to_string(), 209 - help: TargetParseError::help_text(), 210 - src: src.clone(), 211 - span, 212 - }); 213 - } 214 - 215 - Ok(OauthClientTarget::HttpsUrl(url)) 216 - } 217 - "http" => { 218 - // HTTP is only allowed for loopback addresses. 219 - match url.host_str() { 220 - Some("localhost") => { 221 - let port = url.port(); 222 - let path = url.path().to_string(); 223 - Ok(OauthClientTarget::Loopback(LoopbackTarget { 224 - host: LoopbackHost::Localhost, 225 - port, 226 - path, 227 - })) 228 - } 229 - Some("127.0.0.1") => { 230 - let port = url.port(); 231 - let path = url.path().to_string(); 232 - Ok(OauthClientTarget::Loopback(LoopbackTarget { 233 - host: LoopbackHost::Loopback127, 234 - port, 235 - path, 236 - })) 237 - } 238 - Some(host) => Err(TargetParseError::HttpNonLoopback { 239 - input: raw.to_string(), 240 - host: host.to_string(), 241 - help: TargetParseError::help_text(), 242 - src: src.clone(), 243 - span, 244 - }), 245 - None => Err(TargetParseError::HttpNonLoopback { 246 - input: raw.to_string(), 247 - host: "<no host>".to_string(), 248 - help: TargetParseError::help_text(), 249 - src: src.clone(), 250 - span, 251 - }), 252 - } 253 - } 254 - scheme => Err(TargetParseError::UnsupportedScheme { 255 - input: raw.to_string(), 256 - scheme: scheme.to_string(), 257 - help: TargetParseError::help_text(), 258 - src: src.clone(), 259 - span, 260 - }), 261 - } 262 - } 263 - 264 12 // Pipeline runner and report types for Task 3. 265 13 266 14 use crate::common::identity::HttpClient; ··· 362 110 opts: OauthClientOptions<'_>, 363 111 ) -> OauthClientReport { 364 112 // Build report header. 365 - let target_str = match &target { 366 - OauthClientTarget::HttpsUrl(url) => url.to_string(), 367 - OauthClientTarget::Loopback(lt) => { 368 - let host_str = match lt.host { 369 - LoopbackHost::Localhost => "localhost", 370 - LoopbackHost::Loopback127 => "127.0.0.1", 371 - }; 372 - if let Some(port) = lt.port { 373 - format!("http://{}:{}{}", host_str, port, lt.path) 374 - } else { 375 - format!("http://{}{}", host_str, lt.path) 376 - } 377 - } 378 - }; 379 - 380 113 let header = ReportHeader { 381 - target: target_str, 114 + target: target.to_string(), 382 115 resolved_did: None, 383 116 pds_endpoint: None, 384 117 labeler_endpoint: None, ··· 466 199 467 200 report 468 201 } 469 - 470 - #[cfg(test)] 471 - mod tests { 472 - use super::*; 473 - 474 - #[test] 475 - fn https_happy() { 476 - let url = "https://client.example.com/metadata.json"; 477 - let result = parse_target(url).expect("should parse"); 478 - match result { 479 - OauthClientTarget::HttpsUrl(u) => { 480 - assert_eq!(u.scheme(), "https"); 481 - assert_eq!(u.host_str(), Some("client.example.com")); 482 - } 483 - _ => panic!("expected HttpsUrl"), 484 - } 485 - } 486 - 487 - #[test] 488 - fn https_with_query_rejected() { 489 - let url = "https://client.example.com/metadata.json?key=value"; 490 - let result = parse_target(url); 491 - match result { 492 - Err(TargetParseError::HttpsHasQueryOrFragment { .. }) => { 493 - // Variant matched successfully; diagnostic code is set on the impl. 494 - } 495 - _ => panic!("expected HttpsHasQueryOrFragment, got {result:?}"), 496 - } 497 - } 498 - 499 - #[test] 500 - fn https_with_fragment_rejected() { 501 - let url = "https://client.example.com/metadata.json#section"; 502 - let result = parse_target(url); 503 - match result { 504 - Err(TargetParseError::HttpsHasQueryOrFragment { .. }) => {} 505 - _ => panic!("expected HttpsHasQueryOrFragment, got {result:?}"), 506 - } 507 - } 508 - 509 - #[test] 510 - fn localhost_no_port() { 511 - let url = "http://localhost"; 512 - let result = parse_target(url).expect("should parse"); 513 - match result { 514 - OauthClientTarget::Loopback(l) => { 515 - assert_eq!(l.host, LoopbackHost::Localhost); 516 - assert_eq!(l.port, None); 517 - assert_eq!(l.path, "/"); 518 - } 519 - _ => panic!("expected Loopback"), 520 - } 521 - } 522 - 523 - #[test] 524 - fn localhost_with_port() { 525 - let url = "http://localhost:8080"; 526 - let result = parse_target(url).expect("should parse"); 527 - match result { 528 - OauthClientTarget::Loopback(l) => { 529 - assert_eq!(l.host, LoopbackHost::Localhost); 530 - assert_eq!(l.port, Some(8080)); 531 - assert_eq!(l.path, "/"); 532 - } 533 - _ => panic!("expected Loopback"), 534 - } 535 - } 536 - 537 - #[test] 538 - fn localhost_with_path() { 539 - let url = "http://localhost/client.json"; 540 - let result = parse_target(url).expect("should parse"); 541 - match result { 542 - OauthClientTarget::Loopback(l) => { 543 - assert_eq!(l.host, LoopbackHost::Localhost); 544 - assert_eq!(l.port, None); 545 - assert_eq!(l.path, "/client.json"); 546 - } 547 - _ => panic!("expected Loopback"), 548 - } 549 - } 550 - 551 - #[test] 552 - fn ipv4_loopback() { 553 - let url = "http://127.0.0.1:3000/"; 554 - let result = parse_target(url).expect("should parse"); 555 - match result { 556 - OauthClientTarget::Loopback(l) => { 557 - assert_eq!(l.host, LoopbackHost::Loopback127); 558 - assert_eq!(l.port, Some(3000)); 559 - assert_eq!(l.path, "/"); 560 - } 561 - _ => panic!("expected Loopback"), 562 - } 563 - } 564 - 565 - #[test] 566 - fn http_non_loopback_host() { 567 - let url = "http://example.com/metadata.json"; 568 - let result = parse_target(url); 569 - match result { 570 - Err(TargetParseError::HttpNonLoopback { host, .. }) => { 571 - assert_eq!(host, "example.com"); 572 - } 573 - _ => panic!("expected HttpNonLoopback, got {result:?}"), 574 - } 575 - } 576 - 577 - #[test] 578 - fn ftp_scheme_rejected() { 579 - let url = "ftp://example.com/file"; 580 - let result = parse_target(url); 581 - match result { 582 - Err(TargetParseError::UnsupportedScheme { scheme, .. }) => { 583 - assert_eq!(scheme, "ftp"); 584 - } 585 - _ => panic!("expected UnsupportedScheme, got {result:?}"), 586 - } 587 - } 588 - 589 - #[test] 590 - fn garbage_input_not_a_url() { 591 - let url = "this is not a url at all!!!"; 592 - let result = parse_target(url); 593 - match result { 594 - Err(TargetParseError::NotAUrl { .. }) => {} 595 - _ => panic!("expected NotAUrl, got {result:?}"), 596 - } 597 - } 598 - }
+1 -1
src/commands/test/oauth/client/pipeline/discovery.rs
··· 13 13 use crate::common::identity::HttpClient; 14 14 use crate::common::report::{CheckResult, Stage, blocked_by, skipped_with_reason}; 15 15 16 - use super::{LoopbackHost, LoopbackTarget, OauthClientTarget}; 16 + use super::super::target::{LoopbackHost, LoopbackTarget, OauthClientTarget}; 17 17 18 18 /// Facts extracted from the discovery stage, populated when metadata is available. 19 19 #[derive(Debug, Clone)]
+406
src/commands/test/oauth/client/target.rs
··· 1 + use std::fmt; 2 + 3 + use miette::{Diagnostic, NamedSource, SourceSpan}; 4 + use thiserror::Error; 5 + use url::Url; 6 + 7 + /// An OAuth client target that has been parsed and validated. 8 + #[derive(Debug, Clone)] 9 + pub enum OauthClientTarget { 10 + /// An HTTPS URL pointing at a client metadata document. 11 + HttpsUrl(Url), 12 + /// A loopback development client using implicit metadata. 13 + Loopback(LoopbackTarget), 14 + } 15 + 16 + /// A loopback development client target. 17 + #[derive(Debug, Clone)] 18 + pub struct LoopbackTarget { 19 + /// The loopback host (localhost or 127.0.0.1). 20 + pub host: LoopbackHost, 21 + /// Optional port number. 22 + pub port: Option<u16>, 23 + /// The path component, including leading slash. 24 + pub path: String, 25 + } 26 + 27 + /// A recognized loopback hostname. 28 + #[derive(Debug, Clone, Copy, PartialEq, Eq)] 29 + pub enum LoopbackHost { 30 + /// `localhost` 31 + Localhost, 32 + /// `127.0.0.1` 33 + Loopback127, 34 + } 35 + 36 + /// Parse a client_id target string into a validated `OauthClientTarget`. 37 + /// 38 + /// The atproto OAuth spec allows two forms: 39 + /// - Production: `https://...` with a hostname and no query/fragment. 40 + /// - Development loopback: `http://localhost[:port][/path]` or `http://127.0.0.1[:port][/path]`. 41 + #[expect(clippy::result_large_err)] 42 + pub fn parse(raw: &str) -> Result<OauthClientTarget, TargetParseError> { 43 + let span = SourceSpan::new(0.into(), raw.len()); 44 + let src = NamedSource::new("<client_id>", raw.to_string()); 45 + 46 + let url = Url::parse(raw).map_err(|_| TargetParseError::NotAUrl { 47 + input: raw.to_string(), 48 + help: TargetParseError::help_text(), 49 + src: src.clone(), 50 + span, 51 + })?; 52 + 53 + match url.scheme() { 54 + "https" => { 55 + // HTTPS must have a host. 56 + if url.host().is_none() { 57 + return Err(TargetParseError::HttpsMissingHost { 58 + input: raw.to_string(), 59 + help: TargetParseError::help_text(), 60 + src: src.clone(), 61 + span, 62 + }); 63 + } 64 + 65 + // HTTPS must not have query or fragment. 66 + if url.query().is_some() || url.fragment().is_some() { 67 + return Err(TargetParseError::HttpsHasQueryOrFragment { 68 + input: raw.to_string(), 69 + help: TargetParseError::help_text(), 70 + src: src.clone(), 71 + span, 72 + }); 73 + } 74 + 75 + Ok(OauthClientTarget::HttpsUrl(url)) 76 + } 77 + "http" => { 78 + // HTTP is only allowed for loopback addresses. 79 + match url.host_str() { 80 + Some("localhost") => { 81 + let port = url.port(); 82 + let path = url.path().to_string(); 83 + Ok(OauthClientTarget::Loopback(LoopbackTarget { 84 + host: LoopbackHost::Localhost, 85 + port, 86 + path, 87 + })) 88 + } 89 + Some("127.0.0.1") => { 90 + let port = url.port(); 91 + let path = url.path().to_string(); 92 + Ok(OauthClientTarget::Loopback(LoopbackTarget { 93 + host: LoopbackHost::Loopback127, 94 + port, 95 + path, 96 + })) 97 + } 98 + Some(host) => Err(TargetParseError::HttpNonLoopback { 99 + input: raw.to_string(), 100 + host: host.to_string(), 101 + help: TargetParseError::help_text(), 102 + src: src.clone(), 103 + span, 104 + }), 105 + None => Err(TargetParseError::HttpNonLoopback { 106 + input: raw.to_string(), 107 + host: "<no host>".to_string(), 108 + help: TargetParseError::help_text(), 109 + src: src.clone(), 110 + span, 111 + }), 112 + } 113 + } 114 + scheme => Err(TargetParseError::UnsupportedScheme { 115 + input: raw.to_string(), 116 + scheme: scheme.to_string(), 117 + help: TargetParseError::help_text(), 118 + src: src.clone(), 119 + span, 120 + }), 121 + } 122 + } 123 + 124 + impl fmt::Display for OauthClientTarget { 125 + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 126 + match self { 127 + OauthClientTarget::HttpsUrl(url) => url.fmt(f), 128 + OauthClientTarget::Loopback(lt) => { 129 + let host_str = match lt.host { 130 + LoopbackHost::Localhost => "localhost", 131 + LoopbackHost::Loopback127 => "127.0.0.1", 132 + }; 133 + if let Some(port) = lt.port { 134 + write!(f, "http://{}:{}{}", host_str, port, lt.path) 135 + } else { 136 + write!(f, "http://{}{}", host_str, lt.path) 137 + } 138 + } 139 + } 140 + } 141 + } 142 + 143 + /// Error parsing a client_id target. 144 + #[derive(Debug, Error)] 145 + pub enum TargetParseError { 146 + /// The input could not be parsed as a URL. 147 + #[error("target must be a valid URL")] 148 + NotAUrl { 149 + /// The invalid input. 150 + input: String, 151 + /// Help text explaining the accepted formats. 152 + help: &'static str, 153 + /// Source code for diagnostic display. 154 + src: NamedSource<String>, 155 + /// Span highlighting the input. 156 + span: SourceSpan, 157 + }, 158 + 159 + /// An HTTPS URL is missing the host component. 160 + #[error("HTTPS client_id must include a hostname")] 161 + HttpsMissingHost { 162 + /// The invalid input. 163 + input: String, 164 + /// Help text. 165 + help: &'static str, 166 + /// Source code for diagnostic display. 167 + src: NamedSource<String>, 168 + /// Span highlighting the input. 169 + span: SourceSpan, 170 + }, 171 + 172 + /// An HTTPS URL contains query string or fragment. 173 + #[error("HTTPS client_id must not include query string or fragment")] 174 + HttpsHasQueryOrFragment { 175 + /// The invalid input. 176 + input: String, 177 + /// Help text. 178 + help: &'static str, 179 + /// Source code for diagnostic display. 180 + src: NamedSource<String>, 181 + /// Span highlighting the input. 182 + span: SourceSpan, 183 + }, 184 + 185 + /// The URL uses an unsupported scheme (neither https nor http). 186 + #[error("client_id scheme must be 'https' or 'http'")] 187 + UnsupportedScheme { 188 + /// The invalid input. 189 + input: String, 190 + /// The unsupported scheme. 191 + scheme: String, 192 + /// Help text. 193 + help: &'static str, 194 + /// Source code for diagnostic display. 195 + src: NamedSource<String>, 196 + /// Span highlighting the input. 197 + span: SourceSpan, 198 + }, 199 + 200 + /// An HTTP URL with a hostname that is not a recognized loopback address. 201 + #[error("HTTP client_id must be localhost or 127.0.0.1")] 202 + HttpNonLoopback { 203 + /// The invalid input. 204 + input: String, 205 + /// The non-loopback hostname. 206 + host: String, 207 + /// Help text. 208 + help: &'static str, 209 + /// Source code for diagnostic display. 210 + src: NamedSource<String>, 211 + /// Span highlighting the input. 212 + span: SourceSpan, 213 + }, 214 + } 215 + 216 + impl Diagnostic for TargetParseError { 217 + fn code<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> { 218 + match self { 219 + TargetParseError::NotAUrl { .. } => Some(Box::new("oauth_client::target::not_a_url")), 220 + TargetParseError::HttpsMissingHost { .. } => { 221 + Some(Box::new("oauth_client::target::https_missing_host")) 222 + } 223 + TargetParseError::HttpsHasQueryOrFragment { .. } => Some(Box::new( 224 + "oauth_client::target::https_has_query_or_fragment", 225 + )), 226 + TargetParseError::UnsupportedScheme { .. } => { 227 + Some(Box::new("oauth_client::target::unsupported_scheme")) 228 + } 229 + TargetParseError::HttpNonLoopback { .. } => { 230 + Some(Box::new("oauth_client::target::http_non_loopback")) 231 + } 232 + } 233 + } 234 + 235 + fn source_code(&self) -> Option<&(dyn miette::SourceCode + 'static)> { 236 + match self { 237 + TargetParseError::NotAUrl { src, .. } 238 + | TargetParseError::HttpsMissingHost { src, .. } 239 + | TargetParseError::HttpsHasQueryOrFragment { src, .. } 240 + | TargetParseError::UnsupportedScheme { src, .. } 241 + | TargetParseError::HttpNonLoopback { src, .. } => Some(src), 242 + } 243 + } 244 + 245 + fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> { 246 + let span = match self { 247 + TargetParseError::NotAUrl { span, .. } 248 + | TargetParseError::HttpsMissingHost { span, .. } 249 + | TargetParseError::HttpsHasQueryOrFragment { span, .. } 250 + | TargetParseError::UnsupportedScheme { span, .. } 251 + | TargetParseError::HttpNonLoopback { span, .. } => span, 252 + }; 253 + 254 + Some(Box::new(std::iter::once(miette::LabeledSpan::new( 255 + Some("here".to_string()), 256 + span.offset(), 257 + span.len(), 258 + )))) 259 + } 260 + 261 + fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> { 262 + match self { 263 + TargetParseError::NotAUrl { help, .. } 264 + | TargetParseError::HttpsMissingHost { help, .. } 265 + | TargetParseError::HttpsHasQueryOrFragment { help, .. } 266 + | TargetParseError::UnsupportedScheme { help, .. } 267 + | TargetParseError::HttpNonLoopback { help, .. } => Some(Box::new(*help)), 268 + } 269 + } 270 + } 271 + 272 + impl TargetParseError { 273 + fn help_text() -> &'static str { 274 + "Accepted forms: https://example.com/path (for production), http://localhost[:port][/path] or http://127.0.0.1[:port][/path] (for development)" 275 + } 276 + } 277 + 278 + #[cfg(test)] 279 + mod tests { 280 + use super::*; 281 + 282 + #[test] 283 + fn https_happy() { 284 + let url = "https://client.example.com/metadata.json"; 285 + let result = parse(url).expect("should parse"); 286 + match result { 287 + OauthClientTarget::HttpsUrl(u) => { 288 + assert_eq!(u.scheme(), "https"); 289 + assert_eq!(u.host_str(), Some("client.example.com")); 290 + } 291 + _ => panic!("expected HttpsUrl"), 292 + } 293 + } 294 + 295 + #[test] 296 + fn https_with_query_rejected() { 297 + let url = "https://client.example.com/metadata.json?key=value"; 298 + let result = parse(url); 299 + match result { 300 + Err(TargetParseError::HttpsHasQueryOrFragment { .. }) => { 301 + // Variant matched successfully; diagnostic code is set on the impl. 302 + } 303 + _ => panic!("expected HttpsHasQueryOrFragment, got {result:?}"), 304 + } 305 + } 306 + 307 + #[test] 308 + fn https_with_fragment_rejected() { 309 + let url = "https://client.example.com/metadata.json#section"; 310 + let result = parse(url); 311 + match result { 312 + Err(TargetParseError::HttpsHasQueryOrFragment { .. }) => {} 313 + _ => panic!("expected HttpsHasQueryOrFragment, got {result:?}"), 314 + } 315 + } 316 + 317 + #[test] 318 + fn localhost_no_port() { 319 + let url = "http://localhost"; 320 + let result = parse(url).expect("should parse"); 321 + match result { 322 + OauthClientTarget::Loopback(l) => { 323 + assert_eq!(l.host, LoopbackHost::Localhost); 324 + assert_eq!(l.port, None); 325 + assert_eq!(l.path, "/"); 326 + } 327 + _ => panic!("expected Loopback"), 328 + } 329 + } 330 + 331 + #[test] 332 + fn localhost_with_port() { 333 + let url = "http://localhost:8080"; 334 + let result = parse(url).expect("should parse"); 335 + match result { 336 + OauthClientTarget::Loopback(l) => { 337 + assert_eq!(l.host, LoopbackHost::Localhost); 338 + assert_eq!(l.port, Some(8080)); 339 + assert_eq!(l.path, "/"); 340 + } 341 + _ => panic!("expected Loopback"), 342 + } 343 + } 344 + 345 + #[test] 346 + fn localhost_with_path() { 347 + let url = "http://localhost/client.json"; 348 + let result = parse(url).expect("should parse"); 349 + match result { 350 + OauthClientTarget::Loopback(l) => { 351 + assert_eq!(l.host, LoopbackHost::Localhost); 352 + assert_eq!(l.port, None); 353 + assert_eq!(l.path, "/client.json"); 354 + } 355 + _ => panic!("expected Loopback"), 356 + } 357 + } 358 + 359 + #[test] 360 + fn ipv4_loopback() { 361 + let url = "http://127.0.0.1:3000/"; 362 + let result = parse(url).expect("should parse"); 363 + match result { 364 + OauthClientTarget::Loopback(l) => { 365 + assert_eq!(l.host, LoopbackHost::Loopback127); 366 + assert_eq!(l.port, Some(3000)); 367 + assert_eq!(l.path, "/"); 368 + } 369 + _ => panic!("expected Loopback"), 370 + } 371 + } 372 + 373 + #[test] 374 + fn http_non_loopback_host() { 375 + let url = "http://example.com/metadata.json"; 376 + let result = parse(url); 377 + match result { 378 + Err(TargetParseError::HttpNonLoopback { host, .. }) => { 379 + assert_eq!(host, "example.com"); 380 + } 381 + _ => panic!("expected HttpNonLoopback, got {result:?}"), 382 + } 383 + } 384 + 385 + #[test] 386 + fn ftp_scheme_rejected() { 387 + let url = "ftp://example.com/file"; 388 + let result = parse(url); 389 + match result { 390 + Err(TargetParseError::UnsupportedScheme { scheme, .. }) => { 391 + assert_eq!(scheme, "ftp"); 392 + } 393 + _ => panic!("expected UnsupportedScheme, got {result:?}"), 394 + } 395 + } 396 + 397 + #[test] 398 + fn garbage_input_not_a_url() { 399 + let url = "this is not a url at all!!!"; 400 + let result = parse(url); 401 + match result { 402 + Err(TargetParseError::NotAUrl { .. }) => {} 403 + _ => panic!("expected NotAUrl, got {result:?}"), 404 + } 405 + } 406 + }
+12 -11
tests/oauth_client_discovery.rs
··· 4 4 use std::sync::Arc; 5 5 6 6 use atproto_devtool::commands::test::oauth::client::pipeline::discovery; 7 - use atproto_devtool::commands::test::oauth::client::pipeline::{ 8 - OauthClientOptions, OauthClientReport, parse_target, run_pipeline, 7 + use atproto_devtool::commands::test::oauth::client::{ 8 + pipeline::{OauthClientOptions, OauthClientReport, run_pipeline}, 9 + target, 9 10 }; 10 11 use atproto_devtool::common::oauth::clock::RealClock; 11 12 use atproto_devtool::common::report::RenderConfig; ··· 32 33 metadata.to_vec(), 33 34 ); 34 35 35 - let target = parse_target("https://client.example.com/metadata.json").expect("parse failed"); 36 + let target = target::parse("https://client.example.com/metadata.json").expect("parse failed"); 36 37 let jwks_fetcher = common::FakeJwksFetcher::new(); 37 38 let opts = OauthClientOptions { 38 39 interactive: None, ··· 58 59 b"".to_vec(), 59 60 ); 60 61 61 - let target = parse_target("https://client.example.com/missing.json").expect("parse failed"); 62 + let target = target::parse("https://client.example.com/missing.json").expect("parse failed"); 62 63 let jwks_fetcher = common::FakeJwksFetcher::new(); 63 64 let opts = OauthClientOptions { 64 65 interactive: None, ··· 81 82 let url = Url::parse("https://client.example.com/metadata.json").unwrap(); 82 83 http.add_transport_error(&url); 83 84 84 - let target = parse_target("https://client.example.com/metadata.json").expect("parse failed"); 85 + let target = target::parse("https://client.example.com/metadata.json").expect("parse failed"); 85 86 let jwks_fetcher = common::FakeJwksFetcher::new(); 86 87 let opts = OauthClientOptions { 87 88 interactive: None, ··· 108 109 not_json.to_vec(), 109 110 ); 110 111 111 - let target = parse_target("https://client.example.com/metadata.json").expect("parse failed"); 112 + let target = target::parse("https://client.example.com/metadata.json").expect("parse failed"); 112 113 let jwks_fetcher = common::FakeJwksFetcher::new(); 113 114 let opts = OauthClientOptions { 114 115 interactive: None, ··· 137 138 Some("text/html; charset=utf-8".to_string()), 138 139 ); 139 140 140 - let target = parse_target("https://client.example.com/metadata.json").expect("parse failed"); 141 + let target = target::parse("https://client.example.com/metadata.json").expect("parse failed"); 141 142 let jwks_fetcher = common::FakeJwksFetcher::new(); 142 143 let opts = OauthClientOptions { 143 144 interactive: None, ··· 158 159 async fn loopback_root_produces_skip_rows() { 159 160 let http = common::FakeHttpClient::new(); 160 161 161 - let target = parse_target("http://localhost").expect("parse failed"); 162 + let target = target::parse("http://localhost").expect("parse failed"); 162 163 let jwks_fetcher = common::FakeJwksFetcher::new(); 163 164 let opts = OauthClientOptions { 164 165 interactive: None, ··· 179 180 async fn loopback_with_port_produces_same_skip_rows() { 180 181 let http = common::FakeHttpClient::new(); 181 182 182 - let target = parse_target("http://localhost:8080/client.json").expect("parse failed"); 183 + let target = target::parse("http://localhost:8080/client.json").expect("parse failed"); 183 184 let jwks_fetcher = common::FakeJwksFetcher::new(); 184 185 let opts = OauthClientOptions { 185 186 interactive: None, ··· 200 201 async fn loopback_127_0_0_1() { 201 202 let http = common::FakeHttpClient::new(); 202 203 203 - let target = parse_target("http://127.0.0.1:3000/").expect("parse failed"); 204 + let target = target::parse("http://127.0.0.1:3000/").expect("parse failed"); 204 205 let jwks_fetcher = common::FakeJwksFetcher::new(); 205 206 let opts = OauthClientOptions { 206 207 interactive: None, ··· 225 226 let url = Url::parse("https://client.example.com/metadata.json").unwrap(); 226 227 http.add_response(&url, 200, metadata.to_vec()); 227 228 228 - let target = parse_target("https://client.example.com/metadata.json").expect("parse failed"); 229 + let target = target::parse("https://client.example.com/metadata.json").expect("parse failed"); 229 230 230 231 // Directly call the discovery stage to inspect facts. 231 232 let output = discovery::run(&target, &http).await;
+7 -6
tests/oauth_client_endtoend.rs
··· 3 3 mod common; 4 4 use std::sync::Arc; 5 5 6 - use atproto_devtool::commands::test::oauth::client::pipeline::{ 7 - OauthClientOptions, OauthClientReport, parse_target, run_pipeline, 6 + use atproto_devtool::commands::test::oauth::client::{ 7 + pipeline::{OauthClientOptions, OauthClientReport, run_pipeline}, 8 + target, 8 9 }; 9 10 use atproto_devtool::common::oauth::clock::RealClock; 10 11 use atproto_devtool::common::report::{ ··· 47 48 interactive: None, 48 49 }; 49 50 50 - let target = parse_target("https://client.example.com/metadata.json").expect("parse failed"); 51 + let target = target::parse("https://client.example.com/metadata.json").expect("parse failed"); 51 52 let report = run_pipeline(target, opts).await; 52 53 let rendered = render_report_to_string(&report); 53 54 ··· 84 85 interactive: None, 85 86 }; 86 87 87 - let target = parse_target("https://client.example.com/metadata.json").expect("parse failed"); 88 + let target = target::parse("https://client.example.com/metadata.json").expect("parse failed"); 88 89 let report = run_pipeline(target, opts).await; 89 90 assert_eq!( 90 91 report.exit_code(), ··· 118 119 interactive: None, 119 120 }; 120 121 121 - let target = parse_target("https://client.example.com/metadata.json").expect("parse failed"); 122 + let target = target::parse("https://client.example.com/metadata.json").expect("parse failed"); 122 123 let report = run_pipeline(target, opts).await; 123 124 assert_eq!( 124 125 report.exit_code(), ··· 158 159 interactive: None, 159 160 }; 160 161 161 - let target = parse_target("https://client.example.com/metadata.json").expect("parse failed"); 162 + let target = target::parse("https://client.example.com/metadata.json").expect("parse failed"); 162 163 let report = run_pipeline(target, opts).await; 163 164 assert_eq!( 164 165 report.exit_code(),
+15 -14
tests/oauth_client_jwks.rs
··· 3 3 mod common; 4 4 use std::sync::Arc; 5 5 6 - use atproto_devtool::commands::test::oauth::client::pipeline::{ 7 - OauthClientOptions, OauthClientReport, parse_target, run_pipeline, 6 + use atproto_devtool::commands::test::oauth::client::{ 7 + pipeline::{OauthClientOptions, OauthClientReport, run_pipeline}, 8 + target, 8 9 }; 9 10 use atproto_devtool::common::oauth::clock::RealClock; 10 11 use atproto_devtool::common::report::RenderConfig; ··· 34 35 metadata.to_vec(), 35 36 ); 36 37 37 - let target = parse_target("https://client.example.com/metadata.json").expect("parse failed"); 38 + let target = target::parse("https://client.example.com/metadata.json").expect("parse failed"); 38 39 let jwks_fetcher = common::FakeJwksFetcher::new(); 39 40 let opts = OauthClientOptions { 40 41 interactive: None, ··· 70 71 let jwks_bytes = include_bytes!("fixtures/oauth_client/jwks/uri_es256_happy/jwks.json"); 71 72 jwks_fetcher.add_response(&jwks_uri, 200, jwks_bytes.to_vec(), None); 72 73 73 - let target = parse_target("https://client.example.com/metadata.json").expect("parse failed"); 74 + let target = target::parse("https://client.example.com/metadata.json").expect("parse failed"); 74 75 let opts = OauthClientOptions { 75 76 interactive: None, 76 77 http: &http, ··· 104 105 let jwks_fetcher = common::FakeJwksFetcher::new(); 105 106 jwks_fetcher.add_transport_error(&jwks_uri, "connection refused"); 106 107 107 - let target = parse_target("https://client.example.com/metadata.json").expect("parse failed"); 108 + let target = target::parse("https://client.example.com/metadata.json").expect("parse failed"); 108 109 let opts = OauthClientOptions { 109 110 interactive: None, 110 111 http: &http, ··· 134 135 metadata.to_vec(), 135 136 ); 136 137 137 - let target = parse_target("https://client.example.com/metadata.json").expect("parse failed"); 138 + let target = target::parse("https://client.example.com/metadata.json").expect("parse failed"); 138 139 let jwks_fetcher = common::FakeJwksFetcher::new(); 139 140 let opts = OauthClientOptions { 140 141 interactive: None, ··· 165 166 metadata.to_vec(), 166 167 ); 167 168 168 - let target = parse_target("https://client.example.com/metadata.json").expect("parse failed"); 169 + let target = target::parse("https://client.example.com/metadata.json").expect("parse failed"); 169 170 let jwks_fetcher = common::FakeJwksFetcher::new(); 170 171 let opts = OauthClientOptions { 171 172 interactive: None, ··· 196 197 metadata.to_vec(), 197 198 ); 198 199 199 - let target = parse_target("https://client.example.com/metadata.json").expect("parse failed"); 200 + let target = target::parse("https://client.example.com/metadata.json").expect("parse failed"); 200 201 let jwks_fetcher = common::FakeJwksFetcher::new(); 201 202 let opts = OauthClientOptions { 202 203 interactive: None, ··· 227 228 metadata.to_vec(), 228 229 ); 229 230 230 - let target = parse_target("https://client.example.com/metadata.json").expect("parse failed"); 231 + let target = target::parse("https://client.example.com/metadata.json").expect("parse failed"); 231 232 let jwks_fetcher = common::FakeJwksFetcher::new(); 232 233 let opts = OauthClientOptions { 233 234 interactive: None, ··· 258 259 metadata.to_vec(), 259 260 ); 260 261 261 - let target = parse_target("https://client.example.com/metadata.json").expect("parse failed"); 262 + let target = target::parse("https://client.example.com/metadata.json").expect("parse failed"); 262 263 let jwks_fetcher = common::FakeJwksFetcher::new(); 263 264 let opts = OauthClientOptions { 264 265 interactive: None, ··· 289 290 b"not found".to_vec(), 290 291 ); 291 292 292 - let target = parse_target("https://client.example.com/metadata.json").expect("parse failed"); 293 + let target = target::parse("https://client.example.com/metadata.json").expect("parse failed"); 293 294 let jwks_fetcher = common::FakeJwksFetcher::new(); 294 295 let opts = OauthClientOptions { 295 296 interactive: None, ··· 325 326 // Return invalid JSON (not parseable). 326 327 jwks_fetcher.add_response(&jwks_uri, 200, b"not valid json {{{".to_vec(), None); 327 328 328 - let target = parse_target("https://client.example.com/metadata.json").expect("parse failed"); 329 + let target = target::parse("https://client.example.com/metadata.json").expect("parse failed"); 329 330 let opts = OauthClientOptions { 330 331 interactive: None, 331 332 http: &http, ··· 359 360 let jwks_fetcher = common::FakeJwksFetcher::new(); 360 361 jwks_fetcher.add_response(&jwks_uri, 404, b"Not found".to_vec(), None); 361 362 362 - let target = parse_target("https://client.example.com/metadata.json").expect("parse failed"); 363 + let target = target::parse("https://client.example.com/metadata.json").expect("parse failed"); 363 364 let opts = OauthClientOptions { 364 365 interactive: None, 365 366 http: &http, ··· 384 385 let http = common::FakeHttpClient::new(); 385 386 // No seeded responses needed for loopback. 386 387 387 - let target = parse_target("http://localhost/").expect("parse failed"); 388 + let target = target::parse("http://localhost/").expect("parse failed"); 388 389 let jwks_fetcher = common::FakeJwksFetcher::new(); 389 390 let opts = OauthClientOptions { 390 391 interactive: None,
+13 -12
tests/oauth_client_metadata.rs
··· 3 3 mod common; 4 4 use std::sync::Arc; 5 5 6 - use atproto_devtool::commands::test::oauth::client::pipeline::{ 7 - OauthClientOptions, OauthClientReport, parse_target, run_pipeline, 6 + use atproto_devtool::commands::test::oauth::client::{ 7 + pipeline::{OauthClientOptions, OauthClientReport, run_pipeline}, 8 + target, 8 9 }; 9 10 use atproto_devtool::common::oauth::clock::RealClock; 10 11 use atproto_devtool::common::report::RenderConfig; ··· 35 36 metadata.to_vec(), 36 37 ); 37 38 38 - let target = parse_target("https://client.example.com/metadata.json").expect("parse failed"); 39 + let target = target::parse("https://client.example.com/metadata.json").expect("parse failed"); 39 40 let jwks_fetcher = common::FakeJwksFetcher::new(); 40 41 let opts = OauthClientOptions { 41 42 interactive: None, ··· 66 67 metadata.to_vec(), 67 68 ); 68 69 69 - let target = parse_target("https://client.example.com/metadata.json").expect("parse failed"); 70 + let target = target::parse("https://client.example.com/metadata.json").expect("parse failed"); 70 71 let jwks_fetcher = common::FakeJwksFetcher::new(); 71 72 let opts = OauthClientOptions { 72 73 interactive: None, ··· 98 99 ); 99 100 100 101 let target = 101 - parse_target("https://app.example.com/oauth-client-metadata.json").expect("parse failed"); 102 + target::parse("https://app.example.com/oauth-client-metadata.json").expect("parse failed"); 102 103 let jwks_fetcher = common::FakeJwksFetcher::new(); 103 104 let opts = OauthClientOptions { 104 105 interactive: None, ··· 129 130 metadata.to_vec(), 130 131 ); 131 132 132 - let target = parse_target("https://client.example.com/metadata.json").expect("parse failed"); 133 + let target = target::parse("https://client.example.com/metadata.json").expect("parse failed"); 133 134 let jwks_fetcher = common::FakeJwksFetcher::new(); 134 135 let opts = OauthClientOptions { 135 136 interactive: None, ··· 161 162 metadata.to_vec(), 162 163 ); 163 164 164 - let target = parse_target("https://client.example.com/metadata.json").expect("parse failed"); 165 + let target = target::parse("https://client.example.com/metadata.json").expect("parse failed"); 165 166 let jwks_fetcher = common::FakeJwksFetcher::new(); 166 167 let opts = OauthClientOptions { 167 168 interactive: None, ··· 194 195 metadata.to_vec(), 195 196 ); 196 197 197 - let target = parse_target("https://client.example.com/metadata.json").expect("parse failed"); 198 + let target = target::parse("https://client.example.com/metadata.json").expect("parse failed"); 198 199 let jwks_fetcher = common::FakeJwksFetcher::new(); 199 200 let opts = OauthClientOptions { 200 201 interactive: None, ··· 228 229 ); 229 230 230 231 let target = 231 - parse_target("https://app.example.com/oauth-client-metadata.json").expect("parse failed"); 232 + target::parse("https://app.example.com/oauth-client-metadata.json").expect("parse failed"); 232 233 let jwks_fetcher = common::FakeJwksFetcher::new(); 233 234 let opts = OauthClientOptions { 234 235 interactive: None, ··· 260 261 metadata.to_vec(), 261 262 ); 262 263 263 - let target = parse_target("https://client.example.com/metadata.json").expect("parse failed"); 264 + let target = target::parse("https://client.example.com/metadata.json").expect("parse failed"); 264 265 let jwks_fetcher = common::FakeJwksFetcher::new(); 265 266 let opts = OauthClientOptions { 266 267 interactive: None, ··· 286 287 let http = common::FakeHttpClient::new(); 287 288 // No seeded responses needed for loopback 288 289 289 - let target = parse_target("http://localhost/").expect("parse failed"); 290 + let target = target::parse("http://localhost/").expect("parse failed"); 290 291 let jwks_fetcher = common::FakeJwksFetcher::new(); 291 292 let opts = OauthClientOptions { 292 293 interactive: None, ··· 317 318 b"not found".to_vec(), 318 319 ); 319 320 320 - let target = parse_target("https://client.example.com/metadata.json").expect("parse failed"); 321 + let target = target::parse("https://client.example.com/metadata.json").expect("parse failed"); 321 322 let jwks_fetcher = common::FakeJwksFetcher::new(); 322 323 let opts = OauthClientOptions { 323 324 interactive: None,