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    #[error("{0}")]
38    Other(String),
39}
40
41impl TranslationError {
42    /// Returns the stable variant name for FFI and language-binding errors.
43    pub const fn kind(&self) -> &'static str {
44        match self {
45            Self::InvalidJson(_) => "InvalidJson",
46            Self::InvalidType { .. } => "InvalidType",
47            Self::UnsupportedTranslation { .. } => "UnsupportedTranslation",
48            Self::LossyConversion(_) => "LossyConversion",
49            Self::UnknownField { .. } => "UnknownField",
50            Self::InvalidValue { .. } => "InvalidValue",
51            Self::Other(_) => "Other",
52        }
53    }
54
55    /// Builds an [`TranslationError::InvalidValue`] for an unsupported message
56    /// role. A transparent router must reject the same payloads the upstream
57    /// provider would: an unknown role string (e.g. `"api"`) should surface as
58    /// an `invalid_value`-style error rather than being silently coerced to
59    /// `user`. `path` is a JSON-path pointer to the offending field so the
60    /// error reads like a provider validation error.
61    pub fn unsupported_role(path: impl Into<String>, value: &str) -> Self {
62        Self::InvalidValue {
63            path: path.into(),
64            message: format!(
65                "Invalid value: {value:?}. Supported message roles are \
66                 system, developer, user, assistant, tool."
67            ),
68        }
69    }
70}