bluejay_validator/value/input_coercion/
error.rs

1use crate::Path;
2use bluejay_core::{ObjectValue, Value};
3#[cfg(feature = "parser-integration")]
4use bluejay_parser::{
5    ast::Value as ParserValue,
6    error::{Annotation, Error as ParserError},
7    HasSpan,
8};
9#[cfg(feature = "parser-integration")]
10use itertools::Itertools;
11use std::borrow::Cow;
12
13#[derive(PartialEq, Debug)]
14pub enum Error<'a, const CONST: bool, V: Value<CONST>> {
15    NullValueForRequiredType {
16        value: &'a V,
17        input_type_name: String,
18        path: Path<'a>,
19    },
20    NoImplicitConversion {
21        value: &'a V,
22        input_type_name: String,
23        path: Path<'a>,
24    },
25    NoEnumMemberWithName {
26        name: &'a str,
27        value: &'a V,
28        enum_type_name: &'a str,
29        path: Path<'a>,
30    },
31    NoValueForRequiredFields {
32        value: &'a V,
33        field_names: Vec<&'a str>,
34        input_object_type_name: &'a str,
35        path: Path<'a>,
36    },
37    NonUniqueFieldNames {
38        value: &'a V,
39        field_name: &'a str,
40        keys: Vec<&'a <V::Object as ObjectValue<CONST>>::Key>,
41        path: Path<'a>,
42    },
43    NoInputFieldWithName {
44        field: &'a <V::Object as ObjectValue<CONST>>::Key,
45        input_object_type_name: &'a str,
46        path: Path<'a>,
47    },
48    CustomScalarInvalidValue {
49        value: &'a V,
50        custom_scalar_type_name: &'a str,
51        message: Cow<'static, str>,
52        path: Path<'a>,
53    },
54    #[cfg(feature = "one-of-input-objects")]
55    OneOfInputNullValues {
56        value: &'a V,
57        input_object_type_name: &'a str,
58        null_entries: Vec<(&'a <V::Object as ObjectValue<CONST>>::Key, &'a V)>,
59        path: Path<'a>,
60    },
61    #[cfg(feature = "one-of-input-objects")]
62    OneOfInputNotSingleNonNullValue {
63        value: &'a V,
64        input_object_type_name: &'a str,
65        non_null_entries: Vec<(&'a <V::Object as ObjectValue<CONST>>::Key, &'a V)>,
66        path: Path<'a>,
67    },
68}
69
70impl<const CONST: bool, V: Value<CONST>> Error<'_, CONST, V> {
71    pub fn message(&self) -> Cow<'static, str> {
72        match self {
73            Self::NullValueForRequiredType { input_type_name, .. } => {
74                format!("Got null when non-null value of type {input_type_name} was expected")
75                    .into()
76            }
77            Self::NoImplicitConversion { input_type_name, value, .. } => {
78                format!("No implicit conversion of {} to {input_type_name}", value.as_ref().variant()).into()
79            }
80            Self::NoEnumMemberWithName { name, enum_type_name, .. } => {
81                format!("No member `{name}` on enum {enum_type_name}").into()
82            }
83            Self::NoValueForRequiredFields {
84                field_names, input_object_type_name, ..
85            } => {
86                let joined_field_names = field_names.iter().join(", ");
87                format!(
88                    "No value for required fields on input type {input_object_type_name}: {joined_field_names}"
89                )
90                .into()
91            }
92            Self::NonUniqueFieldNames { field_name, .. } => {
93                format!("Object with multiple entries for field {field_name}").into()
94            }
95            Self::NoInputFieldWithName { field, input_object_type_name, .. } => {
96                format!(
97                    "No field with name {} on input type {input_object_type_name}",
98                    field.as_ref()
99                )
100                .into()
101            }
102            Self::CustomScalarInvalidValue { message, .. } => message.clone(),
103            #[cfg(feature = "one-of-input-objects")]
104            Self::OneOfInputNullValues { input_object_type_name, .. } => {
105                format!("Multiple entries with null values for oneOf input object {input_object_type_name}")
106                    .into()
107            }
108            #[cfg(feature = "one-of-input-objects")]
109            Self::OneOfInputNotSingleNonNullValue { input_object_type_name, non_null_entries, .. } => {
110                format!(
111                    "Got {} entries with non-null values for oneOf input object {input_object_type_name}",
112                    non_null_entries.len()
113                )
114                .into()
115            }
116        }
117    }
118}
119
120#[cfg(feature = "parser-integration")]
121impl<'a, const CONST: bool> From<Error<'a, CONST, ParserValue<'a, CONST>>> for ParserError {
122    fn from(error: Error<'a, CONST, ParserValue<'a, CONST>>) -> Self {
123        match &error {
124            Error::NullValueForRequiredType { value, .. } => Self::new(
125                error.message(),
126                Some(Annotation::new(
127                    "Expected non-null value",
128                    value.span().clone(),
129                )),
130                Vec::new(),
131            ),
132            Error::NoImplicitConversion {
133                value,
134                input_type_name,
135                ..
136            } => Self::new(
137                error.message(),
138                Some(Annotation::new(
139                    format!("No implicit conversion to {input_type_name}"),
140                    value.span().clone(),
141                )),
142                Vec::new(),
143            ),
144            Error::NoEnumMemberWithName {
145                value,
146                enum_type_name,
147                ..
148            } => Self::new(
149                error.message(),
150                Some(Annotation::new(
151                    format!("No such member on enum {enum_type_name}"),
152                    value.span().clone(),
153                )),
154                Vec::new(),
155            ),
156            Error::NoValueForRequiredFields {
157                value, field_names, ..
158            } => {
159                let joined_field_names = field_names.iter().join(", ");
160                Self::new(
161                    error.message(),
162                    Some(Annotation::new(
163                        format!("No value for required fields: {joined_field_names}"),
164                        value.span().clone(),
165                    )),
166                    Vec::new(),
167                )
168            }
169            Error::NonUniqueFieldNames { keys, .. } => Self::new(
170                error.message(),
171                None,
172                Vec::from_iter(
173                    keys.iter()
174                        .map(|key| Annotation::new("Entry for field", key.span().clone())),
175                ),
176            ),
177            Error::NoInputFieldWithName {
178                field,
179                input_object_type_name,
180                ..
181            } => Self::new(
182                error.message(),
183                Some(Annotation::new(
184                    format!("No field with this name on input type {input_object_type_name}"),
185                    field.span().clone(),
186                )),
187                Vec::new(),
188            ),
189            Error::CustomScalarInvalidValue { value, message, .. } => Self::new(
190                message.clone(),
191                Some(Annotation::new(message.clone(), value.span().clone())),
192                Vec::new(),
193            ),
194            #[cfg(feature = "one-of-input-objects")]
195            Error::OneOfInputNullValues {
196                value,
197                null_entries,
198                ..
199            } => Self::new(
200                error.message(),
201                Some(Annotation::new(
202                    "oneOf input object must not contain any null values",
203                    value.span().clone(),
204                )),
205                null_entries
206                    .iter()
207                    .map(|(key, value)| {
208                        Annotation::new("Entry with null value", key.span().merge(value.span()))
209                    })
210                    .collect(),
211            ),
212            #[cfg(feature = "one-of-input-objects")]
213            Error::OneOfInputNotSingleNonNullValue {
214                value,
215                non_null_entries,
216                ..
217            } => Self::new(
218                error.message(),
219                Some(Annotation::new(
220                    "oneOf input object must contain single non-null",
221                    value.span().clone(),
222                )),
223                non_null_entries
224                    .iter()
225                    .map(|(key, value)| {
226                        Annotation::new("Entry with non-null value", key.span().merge(value.span()))
227                    })
228                    .collect(),
229            ),
230        }
231    }
232}