@@ -3411,6 +3411,26 @@ fn google_error_message(response: MicrosoftHttpResponse) -> String { |
| 3411 | 3411 | } |
| 3412 | 3412 | } |
| 3413 | 3413 | |
| 3414 | +fn google_oauth_error_message(response: MicrosoftHttpResponse) -> String { |
| 3415 | + if let Ok(value) = serde_json::from_str::<Value>(&response.body) |
| 3416 | + && let Some(error) = graph_string(&value, "error") |
| 3417 | + { |
| 3418 | + let description = graph_string(&value, "error_description"); |
| 3419 | + return match description { |
| 3420 | + Some(description) if !description.is_empty() => { |
| 3421 | + format!("Google OAuth {}: {error}: {description}", response.status) |
| 3422 | + } |
| 3423 | + _ => format!("Google OAuth {}: {error}", response.status), |
| 3424 | + }; |
| 3425 | + } |
| 3426 | + let body = response.body.trim(); |
| 3427 | + if body.is_empty() { |
| 3428 | + format!("Google OAuth HTTP {}", response.status) |
| 3429 | + } else { |
| 3430 | + format!("Google OAuth HTTP {}: {body}", response.status) |
| 3431 | + } |
| 3432 | +} |
| 3433 | + |
| 3414 | 3434 | fn parse_oauth_json(response: MicrosoftHttpResponse) -> Result<Value, ProviderError> { |
| 3415 | 3435 | if response.status != 200 { |
| 3416 | 3436 | return Err(ProviderError::Auth(graph_error_message(response))); |
@@ -3436,7 +3456,7 @@ fn google_token_from_response( |
| 3436 | 3456 | existing_refresh_token: Option<String>, |
| 3437 | 3457 | ) -> Result<GoogleToken, ProviderError> { |
| 3438 | 3458 | if response.status != 200 { |
| 3439 | | - return Err(ProviderError::Auth(google_error_message(response))); |
| 3459 | + return Err(ProviderError::Auth(google_oauth_error_message(response))); |
| 3440 | 3460 | } |
| 3441 | 3461 | let value = serde_json::from_str::<Value>(&response.body) |
| 3442 | 3462 | .map_err(|err| ProviderError::Auth(format!("Google token response is invalid: {err}")))?; |
@@ -4488,32 +4508,32 @@ impl fmt::Display for ProviderError { |
| 4488 | 4508 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 4489 | 4509 | match self { |
| 4490 | 4510 | Self::Config(reason) => write!(f, "provider config error: {reason}"), |
| 4491 | | - Self::Auth(reason) => write!(f, "Microsoft auth error: {reason}"), |
| 4492 | | - Self::Keyring(reason) => write!(f, "Microsoft token keyring error: {reason}"), |
| 4493 | | - Self::Http(reason) => write!(f, "Microsoft HTTP error: {reason}"), |
| 4511 | + Self::Auth(reason) => write!(f, "provider auth error: {reason}"), |
| 4512 | + Self::Keyring(reason) => write!(f, "provider token keyring error: {reason}"), |
| 4513 | + Self::Http(reason) => write!(f, "provider HTTP error: {reason}"), |
| 4494 | 4514 | Self::Graph(reason) => write!(f, "Microsoft Graph error: {reason}"), |
| 4495 | 4515 | Self::Api(reason) => write!(f, "provider API error: {reason}"), |
| 4496 | | - Self::Mapping(reason) => write!(f, "Microsoft event mapping error: {reason}"), |
| 4516 | + Self::Mapping(reason) => write!(f, "provider event mapping error: {reason}"), |
| 4497 | 4517 | Self::Validation(reason) => write!(f, "{reason}"), |
| 4498 | | - Self::NotFound(id) => write!(f, "Microsoft event '{id}' was not found"), |
| 4518 | + Self::NotFound(id) => write!(f, "provider event '{id}' was not found"), |
| 4499 | 4519 | Self::CacheRead { path, reason } => { |
| 4500 | 4520 | write!( |
| 4501 | 4521 | f, |
| 4502 | | - "failed to read Microsoft cache {}: {reason}", |
| 4522 | + "failed to read provider cache {}: {reason}", |
| 4503 | 4523 | path.display() |
| 4504 | 4524 | ) |
| 4505 | 4525 | } |
| 4506 | 4526 | Self::CacheParse { path, reason } => { |
| 4507 | 4527 | write!( |
| 4508 | 4528 | f, |
| 4509 | | - "failed to parse Microsoft cache {}: {reason}", |
| 4529 | + "failed to parse provider cache {}: {reason}", |
| 4510 | 4530 | path.display() |
| 4511 | 4531 | ) |
| 4512 | 4532 | } |
| 4513 | 4533 | Self::CacheWrite { path, reason } => { |
| 4514 | 4534 | write!( |
| 4515 | 4535 | f, |
| 4516 | | - "failed to write Microsoft cache {}: {reason}", |
| 4536 | + "failed to write provider cache {}: {reason}", |
| 4517 | 4537 | path.display() |
| 4518 | 4538 | ) |
| 4519 | 4539 | } |
@@ -4974,6 +4994,24 @@ mod tests { |
| 4974 | 4994 | assert!(master.recurrence.is_some()); |
| 4975 | 4995 | } |
| 4976 | 4996 | |
| 4997 | + #[test] |
| 4998 | + fn google_oauth_errors_parse_top_level_error_response() { |
| 4999 | + let response = RecordingHttpClient::json( |
| 5000 | + 400, |
| 5001 | + json!({ |
| 5002 | + "error": "invalid_request", |
| 5003 | + "error_description": "client_secret is missing." |
| 5004 | + }), |
| 5005 | + ); |
| 5006 | + |
| 5007 | + let err = google_token_from_response(response, None).expect_err("400 fails"); |
| 5008 | + |
| 5009 | + assert_eq!( |
| 5010 | + err.to_string(), |
| 5011 | + "provider auth error: Google OAuth 400: invalid_request: client_secret is missing." |
| 5012 | + ); |
| 5013 | + } |
| 5014 | + |
| 4977 | 5015 | #[test] |
| 4978 | 5016 | fn provider_write_targets_use_configured_editable_calendars() { |
| 4979 | 5017 | let cache_file = temp_path("targets/microsoft-cache.json"); |