Skip to main content

pylon_core/
error.rs

1//
2// This source file is part of the Pylon open source project.
3//
4// Copyright (c) 2026 Jaldis B.V.
5//
6// Licensed under the MIT OR Apache-2.0 license (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10//     https://opensource.org/licenses/MIT
11//     https://www.apache.org/licenses/LICENSE-2.0
12//
13// Unless required by applicable law or agreed to in writing, software
14// distributed under the License is distributed on an "AS IS" BASIS,
15// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16// See the License for the specific language governing permissions and
17// limitations under the License.
18//
19
20use thiserror::Error;
21
22#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
23pub struct Position {
24    pub line: u32,
25    pub col: u32,
26}
27
28// ── Compilation errors ─────────────────────────────────────────────────────────
29
30#[derive(Debug, Error)]
31pub enum PyQLError {
32    #[error(transparent)]
33    Syntax(#[from] PyQLSyntaxError),
34    #[error(transparent)]
35    Type(#[from] PyQLTypeError),
36    #[error(transparent)]
37    Resolution(#[from] PyQLResolutionError),
38    #[error(transparent)]
39    Cardinality(#[from] PyQLCardinalityError),
40    #[error(transparent)]
41    Fragment(#[from] PyQLFragmentError),
42}
43
44impl PyQLError {
45    /// Maps this error to the `pylon.exceptions.*` class name it corresponds to,
46    /// plus its message and position — the single source of truth shared by the
47    /// Python binding (`pylon-py`'s `pyql_err`) and the language server's
48    /// diagnostics, so both surfaces stay in sync with exactly one match arm set.
49    pub fn class_name_message_position(&self) -> (&'static str, &str, &Position) {
50        use PyQLError as E;
51        use PyQLResolutionError as R;
52        match self {
53            E::Syntax(e) => ("InvalidQueryError", &e.message, &e.position),
54            E::Type(e) => ("InvalidQueryError", &e.message, &e.position),
55            E::Resolution(R::UnknownType(e)) => ("UnknownTypeError", &e.message, &e.position),
56            E::Resolution(R::UnknownField(e)) => ("UnknownLinkError", &e.message, &e.position),
57            E::Resolution(R::UnknownParameter(e)) => ("UnknownParameterError", &e.message, &e.position),
58            E::Cardinality(e) => ("InvalidQueryError", &e.message, &e.position),
59            E::Fragment(e) => ("SchemaError", &e.message, &e.position),
60        }
61    }
62}
63
64#[derive(Debug, Error, Clone)]
65#[error("{message}")]
66pub struct PyQLSyntaxError {
67    pub message: String,
68    pub position: Position,
69}
70
71#[derive(Debug, Error, Clone)]
72#[error("{message}")]
73pub struct PyQLTypeError {
74    pub message: String,
75    pub position: Position,
76}
77
78/// Base error for unknown-identifier failures; variants correspond to the Python subclass hierarchy.
79#[derive(Debug, Error)]
80pub enum PyQLResolutionError {
81    #[error(transparent)]
82    UnknownType(#[from] PyQLUnknownTypeError),
83    #[error(transparent)]
84    UnknownField(#[from] PyQLUnknownFieldError),
85    #[error(transparent)]
86    UnknownParameter(#[from] PyQLUnknownParameterError),
87}
88
89#[derive(Debug, Error, Clone)]
90#[error("{message}")]
91pub struct PyQLUnknownTypeError {
92    pub message: String,
93    pub position: Position,
94}
95
96#[derive(Debug, Error, Clone)]
97#[error("{message}")]
98pub struct PyQLUnknownFieldError {
99    pub message: String,
100    pub position: Position,
101}
102
103#[derive(Debug, Error, Clone)]
104#[error("{message}")]
105pub struct PyQLUnknownParameterError {
106    pub message: String,
107    pub position: Position,
108}
109
110#[derive(Debug, Error, Clone)]
111#[error("{message}")]
112pub struct PyQLCardinalityError {
113    pub message: String,
114    pub position: Position,
115}
116
117#[derive(Debug, Error, Clone)]
118#[error("{message}")]
119pub struct PyQLFragmentError {
120    pub message: String,
121    pub position: Position,
122    /// Identifies the failing schema element, e.g. 'Product.total_price (computed)'.
123    pub context: String,
124}
125
126// ── Execution errors ───────────────────────────────────────────────────────────
127
128#[derive(Debug, Error)]
129pub enum PylonExecutionError {
130    #[error(transparent)]
131    ConstraintViolation(#[from] PylonConstraintViolationError),
132    #[error(transparent)]
133    CardinalityViolation(#[from] PylonCardinalityViolationError),
134    #[error(transparent)]
135    MissingRequired(#[from] PylonMissingRequiredError),
136    #[error(transparent)]
137    InvalidValue(#[from] PylonInvalidValueError),
138}
139
140#[derive(Debug, Error, Clone)]
141#[error("{message}")]
142pub struct PylonConstraintViolationError {
143    pub message: String,
144}
145
146#[derive(Debug, Error, Clone)]
147#[error("{message}")]
148pub struct PylonCardinalityViolationError {
149    pub message: String,
150}
151
152#[derive(Debug, Error, Clone)]
153#[error("{message}")]
154pub struct PylonMissingRequiredError {
155    pub message: String,
156}
157
158#[derive(Debug, Error, Clone)]
159#[error("{message}")]
160pub struct PylonInvalidValueError {
161    pub message: String,
162}