Skip to main content

saola_user_facing_errors/query_engine/
validation.rs

1use crate::KnownError;
2use itertools::Itertools;
3use serde::Serialize;
4use serde_json::json;
5use std::{borrow::Cow, error, fmt};
6
7/// A validation error is a Serializable object that contains the path where the validation error
8/// of a certain `kind` ocurred, and an optional and arbitrary piece of `meta`-information.
9#[derive(Debug, Serialize)]
10#[serde(rename_all = "camelCase")]
11pub struct ValidationError {
12    kind: ValidationErrorKind,
13    #[serde(skip)]
14    message: String,
15    #[serde(flatten)]
16    meta: Option<serde_json::Value>,
17}
18
19impl ValidationError {
20    pub fn kind(&self) -> &ValidationErrorKind {
21        &self.kind
22    }
23
24    pub fn message(&self) -> &str {
25        &self.message
26    }
27}
28
29impl fmt::Display for ValidationError {
30    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31        f.write_str(&self.message)
32    }
33}
34
35#[derive(Debug, Serialize)]
36pub enum ValidationErrorKind {
37    /// See [`ValidationError::unexpected_runtime_error`].
38    UnexpectedRuntimeError,
39    /// See [`ValidationError::empty_selection`]
40    EmptySelection,
41    ///See [`ValidationError::invalid_argument_type`]
42    InvalidArgumentType,
43    ///See [`ValidationError::invalid_argument_value`]
44    InvalidArgumentValue,
45    /// See [`ValidationError::some_fields_missing`]
46    SomeFieldsMissing,
47    /// See [`ValidationError::too_many_fields_given`]
48    TooManyFieldsGiven,
49    /// See [`ValidationError::selection_set_on_scalar`]
50    SelectionSetOnScalar,
51    /// See [`ValidationError::required_argument_missing`] and
52    /// [`ValidationError::conditionally_required_argument_missing`]
53    RequiredArgumentMissing,
54    /// See [`ValidationError::union`]
55    Union,
56    /// See [`ValidationError::unknown_argument`]
57    UnknownArgument,
58    /// See [`ValidationError::unknown_input_field`]
59    UnknownInputField,
60    /// See [`ValidationError::unknown_selection_field`]
61    UnknownSelectionField,
62    /// See [`ValidationError::value_too_large`]
63    ValueTooLarge,
64}
65
66impl ValidationErrorKind {
67    /// Returns the appropriate code code for the different validation errors.
68    ///
69    /// TODO: Ideally each all validation errors should have the same error code (P2009), or distinct
70    /// type each of them should have an individual error code. For the time being, we keep the
71    /// semantics documented in the [error reference][r] as users might be relying on the error
72    /// codes when subscribing to error events. Otherwise, we could be introducing a breaking change.
73    ///
74    /// [r]: https://www.prisma.io/docs/reference/api-reference/error-reference
75    pub fn code(&self) -> &'static str {
76        match self {
77            ValidationErrorKind::RequiredArgumentMissing => "P2012",
78            _ => "P2009",
79        }
80    }
81}
82
83impl From<ValidationError> for crate::KnownError {
84    fn from(err: ValidationError) -> Self {
85        KnownError {
86            message: err.message.clone(),
87            meta: serde_json::to_value(&err).expect("Failed to render validation error to JSON"),
88            error_code: Cow::from(err.kind.code()),
89        }
90    }
91}
92
93impl From<ValidationError> for crate::Error {
94    fn from(err: ValidationError) -> Self {
95        KnownError::from(err).into()
96    }
97}
98
99impl ValidationError {
100    /// Creates an [`ValidationErrorKind::UnexpectedRuntimeError`] kind of error when something unexpected
101    /// happen at runtime after a query was properly validated by the parser against the schema.
102    pub fn unexpected_runtime_error(message: String) -> Self {
103        ValidationError {
104            kind: ValidationErrorKind::UnexpectedRuntimeError,
105            message,
106            meta: None,
107        }
108    }
109
110    /// Creates an [`ValidationErrorKind::EmptySelection`] kind of error, which happens when the
111    /// selection of fields is empty for a query.
112    ///
113    /// Example json query:
114    ///
115    /// {
116    ///     "action": "findMany",
117    ///     "modelName": "User",
118    ///     "query": {
119    ///         "selection": {}
120    ///     }
121    /// }
122    pub fn empty_selection(selection_path: Vec<&str>, output_type_description: OutputTypeDescription) -> Self {
123        let message = String::from("Expected a minimum of 1 field to be present, got 0");
124        ValidationError {
125            kind: ValidationErrorKind::EmptySelection,
126            message,
127            meta: Some(json!({ "outputType": output_type_description, "selectionPath": selection_path })),
128        }
129    }
130
131    /// Creates an [`ValidationErrorKind::InvalidArgumentType`] kind of error, which happens when the
132    /// argument is of a type that is incompatible with its definition.
133    ///
134    /// Say the schema type for user.id is `Int`
135    ///
136    /// The example json query will fail, as it's trying to pass a string instead.
137    ///
138    /// {
139    ///     "action": "findMany",
140    ///     "modelName": "User",
141    ///     "query": {
142    ///         "arguments": {
143    ///             "where": {
144    ///                 "id": "a22b8732-be32-4a30-9b38-78843aaa48f8"
145    ///             }
146    ///         },
147    ///         "selection": {
148    ///             "$scalars": true
149    ///         }
150    ///     }
151    /// }
152    pub fn invalid_argument_type(
153        selection_path: Vec<&str>,
154        argument_path: Vec<&str>,
155        argument_description: ArgumentDescription<'_>,
156        inferred_argument_type: String,
157    ) -> Self {
158        let message = format!(
159            "Invalid argument type. `{}` should be of any of the following types: `{}`",
160            argument_description.name,
161            argument_description.type_names.join(", ")
162        );
163        ValidationError {
164            kind: ValidationErrorKind::InvalidArgumentType,
165            message,
166            meta: Some(
167                json!({"argumentPath": argument_path, "argument": argument_description, "selectionPath": selection_path, "inferredType": inferred_argument_type }),
168            ),
169        }
170    }
171
172    /// Creates an [`ValidationErrorKind::InvalidArgumentValue`] kind of error, which happens when the
173    /// argument is of the correct type, but its value is invalid, said a negative number on a type
174    /// that is integer but which values should be non-negative. Or a uuid which type is correctly
175    /// a string, but its format is not the appropriate.
176    ///
177    /// Say the schema type for user.id is `Int`
178    ///
179    /// The example json query will fail, as it's trying to pass a string instead.
180    ///
181    /// {
182    ///     "action": "findMany",
183    ///     "modelName": "User",
184    ///     "query": {
185    ///         "arguments": {
186    ///             "where": {
187    ///                 "dob": "invalid date"
188    ///             }
189    ///         },
190    ///         "selection": {
191    ///             "$scalars": true
192    ///         }
193    ///     }
194    /// }
195    pub fn invalid_argument_value(
196        selection_path: Vec<&str>,
197        argument_path: Vec<&str>,
198        value: String,
199        expected_argument_type: &str,
200        underlying_err: Option<Box<dyn error::Error>>,
201    ) -> Self {
202        let argument_name = argument_path.last().expect("Argument path cannot not be empty");
203        let (message, meta) = if let Some(err) = underlying_err {
204            let err_msg = err.to_string();
205            let message = format!(
206                "Invalid argument agument value. `{}` is not a valid `{}`. Underlying error: {}",
207                value, expected_argument_type, &err_msg
208            );
209            let argument = ArgumentDescription::new(*argument_name, vec![Cow::Borrowed(expected_argument_type)]);
210            let meta = json!({"argumentPath": argument_path, "argument": argument, "selectionPath": selection_path, "underlyingError": &err_msg});
211            (message, Some(meta))
212        } else {
213            let message = format!(
214                "Invalid argument agument value. `{}` is not a valid `{}`",
215                value, &expected_argument_type
216            );
217            let argument = ArgumentDescription::new(*argument_name, vec![Cow::Borrowed(expected_argument_type)]);
218            let meta = json!({"argumentPath": argument_path, "argument": argument, "selectionPath": selection_path, "underlyingError": serde_json::Value::Null});
219            (message, Some(meta))
220        };
221        ValidationError {
222            kind: ValidationErrorKind::InvalidArgumentValue,
223            message,
224            meta,
225        }
226    }
227
228    /// Creates an [`ValidationErrorKind::SomeFieldsMissing`] kind of error, which happens when
229    /// there are some fields missing from a query
230    pub fn some_fields_missing(
231        selection_path: Vec<&str>,
232        argument_path: Vec<&str>,
233        min_field_count: Option<usize>,
234        max_field_count: Option<usize>,
235        required_fields: Option<Vec<Cow<'_, str>>>,
236        provided_field_count: usize,
237        input_type_description: &InputTypeDescription,
238    ) -> Self {
239        let constraints =
240            InputTypeConstraints::new(min_field_count, max_field_count, required_fields, provided_field_count);
241        let message = format!("Some fields are missing: {constraints}");
242        ValidationError {
243            kind: ValidationErrorKind::SomeFieldsMissing,
244            message,
245            meta: Some(
246                json!({ "inputType": input_type_description, "argumentPath": argument_path, "selectionPath": selection_path, "constraints": constraints }),
247            ),
248        }
249    }
250
251    /// Creates an [`ValidationErrorKind::SomeFieldsMissing`] kind of error, which happens when
252    /// there are more fields given than the ones a type accept
253    pub fn too_many_fields_given(
254        selection_path: Vec<&str>,
255        argument_path: Vec<&str>,
256        min_field_count: Option<usize>,
257        max_field_count: Option<usize>,
258        required_fields: Option<Vec<Cow<'_, str>>>,
259        provided_field_count: usize,
260        input_type_description: &InputTypeDescription,
261    ) -> Self {
262        let constraints =
263            InputTypeConstraints::new(min_field_count, max_field_count, required_fields, provided_field_count);
264        let message = format!("Too many fields given: {constraints}");
265        ValidationError {
266            kind: ValidationErrorKind::TooManyFieldsGiven,
267            message,
268            meta: Some(
269                json!({ "inputType": input_type_description, "argumentPath": argument_path,  "selectionPath": selection_path, "constraints": constraints }),
270            ),
271        }
272    }
273
274    /// Creates an [`ValidationErrorKind::RequiredArgumentMissing`] kind of error, which happens
275    /// when there is a missing argument for a field missing, like the `where` field below.
276    ///
277    /// Example json query:
278    ///
279    /// {
280    ///     "action": "findMany",
281    ///     "modelName": "User",
282    ///     "query": {
283    ///         "selection": {}
284    ///     }
285    /// }
286    ///
287    pub fn required_argument_missing(
288        selection_path: Vec<&str>,
289        argument_path: Vec<&str>,
290        input_type_descriptions: &[InputTypeDescription],
291    ) -> Self {
292        let message = format!("`{}`: A value is required but not set", argument_path.join("."));
293        ValidationError {
294            kind: ValidationErrorKind::RequiredArgumentMissing,
295            message,
296            meta: Some(
297                json!({ "inputTypes": input_type_descriptions, "argumentPath": argument_path,  "selectionPath": selection_path }),
298            ),
299        }
300    }
301
302    /// Creates a [`ValidationErrorKind::RequiredArgumentMissing`] kind of error
303    /// for a conditionally required argument which needs to be present in the
304    /// current query because another argument which depends on it was provided.
305    ///
306    /// Example json query (`take` requires `orderBy` in views):
307    ///
308    /// ```json
309    /// {
310    ///     "action": "findMany",
311    ///     "modelName": "UserView",
312    ///     "query": {
313    ///         "arguments": {
314    ///             "take": "1"
315    ///         },
316    ///         "selection": {
317    ///             "$scalars": true
318    ///         }
319    ///     }
320    /// }
321    /// ```
322    pub fn conditionally_required_argument_missing(
323        selection_path: &[&str],
324        argument_path: &[&str],
325        dependent_argument_path: &[&str],
326        input_type_descriptions: &[InputTypeDescription],
327    ) -> Self {
328        let message = format!(
329            "`{}`: A value is required but not set. It is required because `{}` was provided.",
330            argument_path.join("."),
331            dependent_argument_path.join(".")
332        );
333        ValidationError {
334            kind: ValidationErrorKind::RequiredArgumentMissing,
335            message,
336            meta: Some(json!({
337                "inputTypes": input_type_descriptions,
338                "argumentPath": argument_path,
339                "dependentArgumentPath": dependent_argument_path,
340                "selectionPath": selection_path,
341            })),
342        }
343    }
344
345    /// Creates an [`ValidationErrorKind::UnknownArgument`] kind of error, which happens when the
346    /// arguments for a query are not congruent with those expressed in the schema
347    ///
348    /// Example json query:
349    ///
350    /// {
351    ///     "action": "findMany",
352    ///     "modelName": "User",
353    ///     "query": {
354    ///         "arguments": {
355    ///             "foo": "123"
356    ///         },
357    ///         "selection": {
358    ///             "$scalars": true
359    ///         }
360    ///     }
361    /// }
362    /// Todo: add the `given` type to the meta
363    pub fn unknown_argument(
364        selection_path: Vec<&str>,
365        argument_path: Vec<&str>,
366        valid_argument_descriptions: Vec<ArgumentDescription<'_>>,
367    ) -> Self {
368        let message = String::from("Argument does not exist in enclosing type");
369        ValidationError {
370            kind: ValidationErrorKind::UnknownArgument,
371            message,
372            meta: Some(
373                json!({"argumentPath": argument_path, "arguments": valid_argument_descriptions, "selectionPath": selection_path}),
374            ),
375        }
376    }
377
378    /// Creates a [`ValidationErrorKind::UnknownInputField`] kind of error, which happens when the
379    /// argument value for a query contains a field that does not exist in the schema for the
380    /// input type.
381    ///
382    /// TODO:
383    ///   how is this conceptually different from an unknown argument? This used to be a
384    ///   FieldNotFoundError (see [old code][c]), but the same FieldNotFoundError was used to
385    ///   denote what's now an UnknownSelectionField.
386    ///
387    /// [c]: https://www.prisma.io/docs/reference/api-reference/error-reference
388    ///
389    /// Example json query:
390    ///
391    /// {
392    ///     "action": "findMany",
393    ///     "modelName": "User",
394    ///     "query": {
395    ///         "arguments": {
396    ///             "where": {
397    ///                 "foo": 2
398    ///             }
399    ///         },
400    ///         "selection": {
401    ///             "$scalars": true
402    ///         }
403    ///     }
404    /// }
405    ///
406    pub fn unknown_input_field(
407        selection_path: Vec<&str>,
408        argument_path: Vec<&str>,
409        input_type_description: InputTypeDescription,
410    ) -> Self {
411        let message = format!(
412            "`{}.{}`: Field does not exist in enclosing type.",
413            selection_path.join("."),
414            argument_path.join("."),
415        );
416        ValidationError {
417            kind: ValidationErrorKind::UnknownInputField,
418            message,
419            meta: Some(
420                json!({ "inputType": input_type_description, "argumentPath": argument_path, "selectionPath": selection_path }),
421            ),
422        }
423    }
424
425    /// Creates an [`ValidationErrorKind::UnknownSelectionField`] kind of error, which happens when
426    /// the selection of fields for a query contains a field that does not exist in the schema for the
427    /// enclosing type
428    ///
429    /// Example json query:
430    ///
431    /// {
432    ///     "action": "findMany",
433    ///     "modelName": "User",
434    ///     "query": {
435    ///         "selection": {
436    ///             "notAField": true
437    ///         }
438    ///     }
439    // }
440    pub fn unknown_selection_field(selection_path: Vec<&str>, output_type_description: OutputTypeDescription) -> Self {
441        let message = format!(
442            "Field '{}' not found in enclosing type '{}'",
443            selection_path.last().expect("Selection path must not be empty"),
444            output_type_description.name
445        );
446        ValidationError {
447            kind: ValidationErrorKind::UnknownSelectionField,
448            message,
449            meta: Some(json!({ "outputType": output_type_description, "selectionPath": selection_path })),
450        }
451    }
452
453    /// Creates an [`ValidationErrorKind::SelectionSetOnScalar`] kind of error, which happens when there
454    /// is a nested selection block on a scalar field
455    ///
456    /// Example json query:
457    ///
458    /// {
459    ///     "action": "findMany",
460    ///     "modelName": "User",
461    ///     "query": {
462    ///         "selection": {
463    ///             "email": {
464    ///                 "selection": {
465    ///                     "id": true
466    ///                 }
467    ///             }
468    ///         }
469    ///     }
470    /// }
471    pub fn selection_set_on_scalar(field_name: String, selection_path: Vec<&str>) -> Self {
472        let message = format!("Cannot select over scalar field '{}'", &field_name);
473        ValidationError {
474            kind: ValidationErrorKind::SelectionSetOnScalar,
475            message,
476            meta: Some(json!({ "fieldName": field_name, "selectionPath": selection_path })),
477        }
478    }
479
480    /// Creates an error that is the union of different validation errors
481    pub fn union(errors: Vec<ValidationError>) -> Self {
482        let message = format!(
483            "Unable to match input value to any allowed input type for the field. Parse errors: [{}]",
484            errors.iter().map(|err| format!("{err}")).collect::<Vec<_>>().join(", ")
485        );
486        ValidationError {
487            message,
488            kind: ValidationErrorKind::Union,
489            meta: Some(json!({ "errors": errors })),
490        }
491    }
492
493    /// Creates an [`ValidationErrorKind::ValueTooLarge`] kind of error, which happens when the value
494    /// for a float or integer coming from the JS client is larger than what can fit in an i64
495    /// (2^64 - 1 = 18446744073709550000)
496    ///
497    /// Example json query
498    ///
499    ///{
500    ///     "action": "findMany",
501    ///     "modelName": "User",
502    ///     "query": {
503    ///         "arguments": {
504    ///             "where": {
505    ///                 "id": 18446744073709550000 // too large
506    ///             }
507    ///         },
508    ///         "selection": {
509    ///             "$scalars": true
510    ///         }
511    ///     }
512    /// }
513    ///
514    pub fn value_too_large(selection_path: Vec<&str>, argument_path: Vec<&str>, value: String) -> Self {
515        let argument_name = argument_path.last().expect("Argument path cannot not be empty");
516        let message = format!(
517            "Unable to fit float value (or large JS integer serialized in exponent notation) '{value}' into a 64 Bit signed integer for field '{argument_name}'. If you're trying to store large integers, consider using `BigInt`",
518        );
519        let argument = ArgumentDescription::new(*argument_name, vec![Cow::Borrowed("BigInt")]);
520        ValidationError {
521            kind: ValidationErrorKind::ValueTooLarge,
522            message,
523            meta: Some(json!({"argumentPath": argument_path, "argument": argument, "selectionPath": selection_path})),
524        }
525    }
526}
527
528impl std::error::Error for ValidationError {}
529
530#[derive(Debug, Serialize)]
531#[serde(rename_all = "camelCase")]
532pub struct OutputTypeDescription {
533    name: String,
534    fields: Vec<OutputTypeDescriptionField>,
535}
536
537impl OutputTypeDescription {
538    pub fn new(name: String, fields: Vec<OutputTypeDescriptionField>) -> Self {
539        OutputTypeDescription { name, fields }
540    }
541}
542
543#[derive(Debug, Serialize)]
544#[serde(rename_all = "camelCase")]
545pub struct OutputTypeDescriptionField {
546    name: String,
547    type_name: String,
548    is_relation: bool,
549}
550
551impl OutputTypeDescriptionField {
552    pub fn new(name: String, type_name: String, is_relation: bool) -> Self {
553        Self {
554            name,
555            type_name,
556            is_relation,
557        }
558    }
559}
560#[derive(Debug, Serialize, Clone)]
561#[serde(tag = "kind", rename_all = "camelCase", rename_all_fields = "camelCase")]
562pub enum InputTypeDescription {
563    Object {
564        name: String,
565        fields: Vec<InputTypeDescriptionField>,
566    },
567    Scalar {
568        name: String,
569    },
570    List {
571        element_type: Box<InputTypeDescription>,
572    },
573    Enum {
574        name: String,
575    },
576}
577
578impl InputTypeDescription {
579    pub fn new_object(name: String, fields: Vec<InputTypeDescriptionField>) -> Self {
580        Self::Object { name, fields }
581    }
582}
583
584#[derive(Debug, Serialize, Clone)]
585#[serde(rename_all = "camelCase")]
586pub struct InputTypeDescriptionField {
587    name: String,
588    type_names: Vec<String>,
589    required: bool,
590}
591
592impl InputTypeDescriptionField {
593    pub fn new(name: String, type_names: Vec<String>, required: bool) -> Self {
594        Self {
595            name,
596            type_names,
597            required,
598        }
599    }
600}
601
602#[derive(Debug, Serialize, Clone)]
603pub struct InputTypeConstraints<'a> {
604    #[serde(rename = "minFieldCount")]
605    min: Option<usize>,
606    #[serde(rename = "maxFieldCount")]
607    max: Option<usize>,
608    #[serde(rename = "requiredFields")]
609    fields: Option<Vec<Cow<'a, str>>>,
610    #[serde(skip)]
611    got: usize,
612}
613
614impl<'a> InputTypeConstraints<'a> {
615    fn new(min: Option<usize>, max: Option<usize>, fields: Option<Vec<Cow<'a, str>>>, got: usize) -> Self {
616        Self { min, max, fields, got }
617    }
618}
619
620// Todo: we might not need this, having only the two kind of error types related to cardinality
621// TooManyFieldsGiven, SomeFieldsMissing
622impl fmt::Display for InputTypeConstraints<'_> {
623    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
624        match &self.fields {
625            None => match (self.min, self.max) {
626                (Some(1), Some(1)) => {
627                    write!(f, "Expected exactly one field to be present, got {}.", self.got)
628                }
629                (Some(min), Some(max)) => write!(
630                    f,
631                    "Expected a minimum of {} and at most {} fields to be present, got {}.",
632                    min, max, self.got
633                ),
634                (Some(min), None) => write!(
635                    f,
636                    "Expected a minimum of {} fields to be present, got {}.",
637                    min, self.got
638                ),
639                (None, Some(max)) => write!(f, "Expected at most {} fields to be present, got {}.", max, self.got),
640                (None, None) => write!(f, "Expected any selection of fields, got {}.", self.got),
641            },
642            Some(fields) => match (self.min, self.max) {
643                (Some(1), Some(1)) => {
644                    write!(
645                        f,
646                        "Expected exactly one field of ({}) to be present, got {}.",
647                        fields.iter().join(", "),
648                        self.got
649                    )
650                }
651                (Some(min), Some(max)) => write!(
652                    f,
653                    "Expected a minimum of {} and at most {} fields of ({}) to be present, got {}.",
654                    min,
655                    max,
656                    fields.iter().join(", "),
657                    self.got
658                ),
659                (Some(min), None) => write!(
660                    f,
661                    "Expected a minimum of {} fields of ({}) to be present, got {}.",
662                    min,
663                    fields.iter().join(", "),
664                    self.got
665                ),
666                (None, Some(max)) => write!(
667                    f,
668                    "Expected at most {} fields of ({}) to be present, got {}.",
669                    max,
670                    fields.iter().join(", "),
671                    self.got
672                ),
673                (None, None) => write!(f, "Expected any selection of fields, got {}.", self.got),
674            },
675        }
676    }
677}
678
679#[derive(Debug, Serialize)]
680#[serde(rename_all = "camelCase")]
681pub struct ArgumentDescription<'a> {
682    name: Cow<'a, str>,
683    type_names: Vec<Cow<'a, str>>,
684}
685
686impl<'a> ArgumentDescription<'a> {
687    pub fn new(name: impl Into<Cow<'a, str>>, type_names: Vec<Cow<'a, str>>) -> Self {
688        Self {
689            name: name.into(),
690            type_names,
691        }
692    }
693}