Send a single validation request and classify the response.
(
client: &reqwest::Client,
route: &ResolvedRoute,
path: &str,
protocol: &str,
headers: &[(String, String)],
body: bytes::Bytes,
)
| 540 | |
| 541 | /// Send a single validation request and classify the response. |
| 542 | async fn try_validation_request( |
| 543 | client: &reqwest::Client, |
| 544 | route: &ResolvedRoute, |
| 545 | path: &str, |
| 546 | protocol: &str, |
| 547 | headers: &[(String, String)], |
| 548 | body: bytes::Bytes, |
| 549 | ) -> Result<ValidatedEndpoint, ValidationFailure> { |
| 550 | let response = send_backend_request(client, route, "POST", path, headers, body) |
| 551 | .await |
| 552 | .map_err(|err| match err { |
| 553 | RouterError::UpstreamUnavailable(details) => ValidationFailure { |
| 554 | kind: ValidationFailureKind::Connectivity, |
| 555 | details, |
| 556 | }, |
| 557 | RouterError::Internal(details) | RouterError::UpstreamProtocol(details) => { |
| 558 | ValidationFailure { |
| 559 | kind: ValidationFailureKind::Unexpected, |
| 560 | details, |
| 561 | } |
| 562 | } |
| 563 | RouterError::RouteNotFound(details) |
| 564 | | RouterError::NoCompatibleRoute(details) |
| 565 | | RouterError::Unauthorized(details) => ValidationFailure { |
| 566 | kind: ValidationFailureKind::Unexpected, |
| 567 | details, |
| 568 | }, |
| 569 | })?; |
| 570 | let url = build_provider_url(route, &route.model, path, false); |
| 571 | |
| 572 | if response.status().is_success() { |
| 573 | return Ok(ValidatedEndpoint { |
| 574 | url, |
| 575 | protocol: protocol.to_string(), |
| 576 | }); |
| 577 | } |
| 578 | |
| 579 | let status = response.status(); |
| 580 | let body = response.text().await.map_err(|e| ValidationFailure { |
| 581 | kind: ValidationFailureKind::Unexpected, |
| 582 | details: format!("failed to read validation response body: {e}"), |
| 583 | })?; |
| 584 | let body = body.trim(); |
| 585 | let body_suffix = if body.is_empty() { |
| 586 | String::new() |
| 587 | } else { |
| 588 | format!( |
| 589 | " Response body: {}", |
| 590 | body.chars().take(200).collect::<String>() |
| 591 | ) |
| 592 | }; |
| 593 | |
| 594 | // Some OpenAI-compatible providers report an auth failure as 400/404/422 |
| 595 | // with an auth-shaped error body rather than 401/403. Classify those as a |
| 596 | // terminal credential failure so a bad key is not mistaken for a |
| 597 | // wrong-protocol probe and masked by a later probe that accepts it. |
| 598 | let kind = match status.as_u16() { |
| 599 | 401 | 403 => ValidationFailureKind::Credentials, |
no test coverage detected