Skip to main content

switchyard_translation/
error.rs

1// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Error types for request, response, and streaming translation.
5
6use thiserror::Error;
7
8use crate::format::FormatId;
9
10/// Result alias for translation operations.
11pub type Result<T> = std::result::Result<T, TranslationError>;
12
13/// Failures that can occur while decoding, encoding, or enforcing policy.
14#[derive(Debug, Error)]
15pub enum TranslationError {
16    #[error("invalid JSON: {0}")]
17    InvalidJson(#[from] serde_json::Error),
18
19    #[error("expected {expected} at {path}")]
20    InvalidType {
21        path: String,
22        expected: &'static str,
23    },
24
25    #[error("translation from {from} to {to} is not supported")]
26    UnsupportedTranslation { from: FormatId, to: FormatId },
27
28    #[error("lossy conversion rejected: {0}")]
29    LossyConversion(String),
30
31    #[error("unknown field rejected at {path}")]
32    UnknownField { path: String },
33
34    #[error("invalid value at {path}: {message}")]
35    InvalidValue { path: String, message: String },
36
37    /// The response body reports a failed generation, such as OpenAI Responses
38    /// `status: "failed"`. `error` contains the provider's error object or a
39    /// message explaining that the provider supplied no error details.
40    #[error("upstream reported a failed response: {error}")]
41    UpstreamFailure { error: serde_json::Value },
42    #[error("{0}")]
43    Other(String),
44}
45
46impl TranslationError {
47    /// Returns the stable variant name for FFI and language-binding errors.
48    pub const fn kind(&self) -> &'static str {
49        match self {
50            Self::InvalidJson(_) => "InvalidJson",
51            Self::InvalidType { .. } => "InvalidType",
52            Self::UnsupportedTranslation { .. } => "UnsupportedTranslation",
53            Self::LossyConversion(_) => "LossyConversion",
54            Self::UnknownField { .. } => "UnknownField",
55            Self::InvalidValue { .. } => "InvalidValue",
56            Self::UpstreamFailure { .. } => "UpstreamFailure",
57            Self::Other(_) => "Other",
58        }
59    }
60
61    /// Builds an [`TranslationError::InvalidValue`] for an unsupported message
62    /// role. A transparent router must reject the same payloads the upstream
63    /// provider would: an unknown role string (e.g. `"api"`) should surface as
64    /// an `invalid_value`-style error rather than being silently coerced to
65    /// `user`. `path` is a JSON-path pointer to the offending field so the
66    /// error reads like a provider validation error.
67    pub fn unsupported_role(path: impl Into<String>, value: &str) -> Self {
68        Self::InvalidValue {
69            path: path.into(),
70            message: format!(
71                "Invalid value: {value:?}. Supported message roles are \
72                 system, developer, user, assistant, tool."
73            ),
74        }
75    }
76}