(
client: &reqwest::Client,
route: &ResolvedRoute,
)
| 456 | } |
| 457 | |
| 458 | pub async fn verify_backend_endpoint( |
| 459 | client: &reqwest::Client, |
| 460 | route: &ResolvedRoute, |
| 461 | ) -> Result<ValidatedEndpoint, ValidationFailure> { |
| 462 | let probes = validation_probes(route); |
| 463 | let Some(first) = probes.first() else { |
| 464 | return Err(no_writable_protocol_failure(route)); |
| 465 | }; |
| 466 | |
| 467 | if mock::is_mock_route(route) { |
| 468 | return Ok(ValidatedEndpoint { |
| 469 | url: build_provider_url(route, &route.model, first.path, false), |
| 470 | protocol: first.protocol.to_string(), |
| 471 | }); |
| 472 | } |
| 473 | |
| 474 | let headers = vec![("content-type".to_string(), "application/json".to_string())]; |
| 475 | let mut last_shape_failure = None; |
| 476 | |
| 477 | for probe in &probes { |
| 478 | match try_validation_probe(client, route, probe, &headers).await { |
| 479 | Ok(endpoint) => return Ok(endpoint), |
| 480 | // A request-shape rejection means this protocol is wrong for the |
| 481 | // model (e.g. a chat probe against an embeddings model), so fall |
| 482 | // through to the next advertised protocol. Any other failure |
| 483 | // describes the backend itself (credentials, rate limit, |
| 484 | // connectivity, health) and is terminal across all protocols. |
| 485 | // |
| 486 | // Keep the first shape failure: it is the most-preferred protocol's |
| 487 | // rejection and the most actionable error to report. |
| 488 | Err(err) if err.kind == ValidationFailureKind::RequestShape => { |
| 489 | last_shape_failure.get_or_insert(err); |
| 490 | } |
| 491 | Err(err) => return Err(err), |
| 492 | } |
| 493 | } |
| 494 | |
| 495 | Err(last_shape_failure.unwrap_or_else(|| no_writable_protocol_failure(route))) |
| 496 | } |
| 497 | |
| 498 | /// Run one validation probe, retrying with its fallback body only when the |
| 499 | /// upstream specifically rejected `max_completion_tokens`. |
no test coverage detected