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(
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}