bluejay_validator/value/input_coercion/
error.rs1use 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}