Skip to main content

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("Expected non-null value", *value.span())),
127                Vec::new(),
128            ),
129            Error::NoImplicitConversion {
130                value,
131                input_type_name,
132                ..
133            } => Self::new(
134                error.message(),
135                Some(Annotation::new(
136                    format!("No implicit conversion to {input_type_name}"),
137                    *value.span(),
138                )),
139                Vec::new(),
140            ),
141            Error::NoEnumMemberWithName {
142                value,
143                enum_type_name,
144                ..
145            } => Self::new(
146                error.message(),
147                Some(Annotation::new(
148                    format!("No such member on enum {enum_type_name}"),
149                    *value.span(),
150                )),
151                Vec::new(),
152            ),
153            Error::NoValueForRequiredFields {
154                value, field_names, ..
155            } => {
156                let joined_field_names = field_names.iter().join(", ");
157                Self::new(
158                    error.message(),
159                    Some(Annotation::new(
160                        format!("No value for required fields: {joined_field_names}"),
161                        *value.span(),
162                    )),
163                    Vec::new(),
164                )
165            }
166            Error::NonUniqueFieldNames { keys, .. } => Self::new(
167                error.message(),
168                None,
169                Vec::from_iter(
170                    keys.iter()
171                        .map(|key| Annotation::new("Entry for field", *key.span())),
172                ),
173            ),
174            Error::NoInputFieldWithName {
175                field,
176                input_object_type_name,
177                ..
178            } => Self::new(
179                error.message(),
180                Some(Annotation::new(
181                    format!("No field with this name on input type {input_object_type_name}"),
182                    *field.span(),
183                )),
184                Vec::new(),
185            ),
186            Error::CustomScalarInvalidValue { value, message, .. } => Self::new(
187                message.clone(),
188                Some(Annotation::new(message.clone(), *value.span())),
189                Vec::new(),
190            ),
191            #[cfg(feature = "one-of-input-objects")]
192            Error::OneOfInputNullValues {
193                value,
194                null_entries,
195                ..
196            } => Self::new(
197                error.message(),
198                Some(Annotation::new(
199                    "oneOf input object must not contain any null values",
200                    *value.span(),
201                )),
202                null_entries
203                    .iter()
204                    .map(|(key, value)| {
205                        Annotation::new("Entry with null value", key.span().merge(value.span()))
206                    })
207                    .collect(),
208            ),
209            #[cfg(feature = "one-of-input-objects")]
210            Error::OneOfInputNotSingleNonNullValue {
211                value,
212                non_null_entries,
213                ..
214            } => Self::new(
215                error.message(),
216                Some(Annotation::new(
217                    "oneOf input object must contain single non-null",
218                    *value.span(),
219                )),
220                non_null_entries
221                    .iter()
222                    .map(|(key, value)| {
223                        Annotation::new("Entry with non-null value", key.span().merge(value.span()))
224                    })
225                    .collect(),
226            ),
227        }
228    }
229}