├── .github └── workflows │ └── ci.yaml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── renovate.json └── src ├── ast ├── collect_fields.rs ├── ext.rs ├── mod.rs ├── operation_transformer.rs ├── operation_visitor.rs └── schema_visitor.rs ├── introspection ├── introspection.rs ├── mod.rs └── test_files │ ├── github_introspection.json │ ├── product_introspection.json │ └── shopify_introspection.json ├── lib.rs └── validation ├── mod.rs ├── rules ├── defaults.rs ├── fields_on_correct_type.rs ├── fragments_on_composite_types.rs ├── known_argument_names.rs ├── known_directives.rs ├── known_fragment_names.rs ├── known_type_names.rs ├── leaf_field_selections.rs ├── lone_anonymous_operation.rs ├── mod.rs ├── no_fragments_cycle.rs ├── no_undefined_variables.rs ├── no_unused_fragments.rs ├── no_unused_variables.rs ├── overlapping_fields_can_be_merged.rs ├── possible_fragment_spreads.rs ├── provided_required_arguments.rs ├── rule.rs ├── single_field_subscriptions.rs ├── unique_argument_names.rs ├── unique_directives_per_location.rs ├── unique_fragment_names.rs ├── unique_operation_names.rs ├── unique_variable_names.rs ├── values_of_correct_type.rs ├── variables_are_input_types.rs └── variables_in_allowed_position.rs ├── test_utils.rs ├── utils.rs └── validate.rs /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: ci 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: 7 | branches: 8 | - master 9 | 10 | jobs: 11 | check: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Check out repository code 15 | uses: actions/checkout@v4 16 | 17 | - name: Cache Rust 18 | uses: Swatinem/rust-cache@v2 19 | 20 | - name: Test Rust 21 | run: | 22 | # features=graphql_parser_fork 23 | cargo test --features graphql_parser_fork --no-default-features 24 | # features=graphql_parser 25 | cargo test 26 | 27 | - name: Build Rust 28 | run: | 29 | # features=graphql_parser_fork 30 | cargo build --release --features graphql_parser_fork --no-default-features 31 | # features=graphql_parser 32 | cargo build --release 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "graphql-tools" 3 | version = "0.4.0" 4 | edition = "2021" 5 | description = "Tools for working with GraphQL in Rust, based on graphql-parser Document." 6 | license = "MIT/Apache-2.0" 7 | readme = "README.md" 8 | keywords = ["graphql", "tools", "gql", "validation", "ast"] 9 | homepage = "https://github.com/dotansimha/graphql-tools-rs" 10 | documentation = "https://github.com/dotansimha/graphql-tools-rs" 11 | authors = ["Dotan Simha "] 12 | 13 | [dependencies] 14 | graphql-parser = { version = "^0.4.0", optional = true } 15 | graphql-parser-hive-fork = { version = "^0.5.0", optional = true } 16 | lazy_static = "1.4.0" 17 | serde = { version = "1.0.200", features = ["derive"] } 18 | serde_json = "1.0" 19 | serde_with = "3.0.0" 20 | 21 | [features] 22 | default = ["graphql_parser"] 23 | graphql_parser_fork = ["dep:graphql-parser-hive-fork"] 24 | graphql_parser = ["dep:graphql-parser"] 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## `graphql-tools` (Rust) 2 | 3 | [Documentation](https://docs.rs/graphql-tools) | [Crate](https://crates.io/crates/graphql-tools) | [GitHub](https://github.com/dotansimha/graphql-tools-rs) 4 | 5 | The [`graphql_tools` crate](https://crates.io/crates/graphql-tools) implements tooling around GraphQL for Rust libraries. Most of the tools are based on `trait`s and `struct`s implemented in [`graphql_parser` crate](https://crates.io/crates/graphql-parser). 6 | 7 | The goal of this library is to create a common layer of tools that has similar/improved APIs to [`graphql-js` reference implementation](https://github.com/graphql/graphql-js) and [`graphql-tools` from the JS/TS ecosystem](https://github.com/ardatan/graphql-tools). 8 | 9 | ### Getting Started 10 | 11 | ![Crates.io](https://img.shields.io/crates/v/graphql-tools?label=graphql-tools%20%28crates.io%29) 12 | 13 | Add `graphql-tools` as a dependency of your project by adding the following to your `Cargo.toml` file: 14 | 15 | ```toml 16 | [dependencies] 17 | graphql-tools = "..." 18 | ``` 19 | 20 | Or, if you are using [`cargo-edit`](https://github.com/killercup/cargo-edit): 21 | 22 | ``` 23 | cargo add graphql-tools 24 | ``` 25 | 26 | By default, this crate is using the [`graphql-parser`](https://github.com/graphql-rust/graphql-parser) library for parsing. If you wish to use an alternative implementation such as [`graphql-hive/graphql-parser-hive-fork`](https://github.com/graphql-hive/graphql-parser-hive-fork), use the following `features` setup: 27 | 28 | ```toml 29 | [dependencies] 30 | graphql-tools = { version = "...", features = "graphql_parser_fork", default-features = false } 31 | ``` 32 | 33 | #### Validation Rules 34 | 35 | > This comparison is based on `graphql-js` reference implementation. 36 | 37 | - [x] ExecutableDefinitions (not actually needed) 38 | - [x] UniqueOperationNames 39 | - [x] LoneAnonymousOperation 40 | - [x] SingleFieldSubscriptions 41 | - [x] KnownTypeNames 42 | - [x] FragmentsOnCompositeTypes 43 | - [x] VariablesAreInputTypes 44 | - [x] LeafFieldSelections 45 | - [x] FieldsOnCorrectType 46 | - [x] UniqueFragmentNames 47 | - [x] KnownFragmentNames 48 | - [x] NoUnusedFragments 49 | - [x] PossibleFragmentSpreads 50 | - [x] NoFragmentCycles 51 | - [x] UniqueVariableNames 52 | - [x] NoUndefinedVariables 53 | - [x] NoUnusedVariables 54 | - [x] KnownDirectives 55 | - [x] UniqueDirectivesPerLocation 56 | - [x] KnownArgumentNames 57 | - [x] UniqueArgumentNames 58 | - [x] ValuesOfCorrectType 59 | - [x] ProvidedRequiredArguments 60 | - [x] VariablesInAllowedPosition 61 | - [x] OverlappingFieldsCanBeMerged 62 | - [ ] UniqueInputFieldNames (blocked by https://github.com/graphql-rust/graphql-parser/issues/59) 63 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /src/ast/collect_fields.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use super::{AbstractTypeDefinitionExtension, OperationVisitorContext, SchemaDocumentExtension}; 4 | use crate::ast::ext::{SubTypeExtension, TypeDefinitionExtension}; 5 | use crate::static_graphql::{ 6 | query::{self, Selection, TypeCondition}, 7 | schema::{self, TypeDefinition}, 8 | }; 9 | pub fn collect_fields<'a>( 10 | selection_set: &query::SelectionSet, 11 | parent_type: &schema::TypeDefinition, 12 | known_fragments: &HashMap<&str, &query::FragmentDefinition>, 13 | context: &'a OperationVisitorContext<'a>, 14 | ) -> HashMap> { 15 | let mut map = HashMap::new(); 16 | let mut visited_fragments_names: Vec = Vec::new(); 17 | 18 | collect_fields_inner( 19 | selection_set, 20 | parent_type, 21 | known_fragments, 22 | context, 23 | &mut map, 24 | &mut visited_fragments_names, 25 | ); 26 | 27 | map 28 | } 29 | 30 | fn does_fragment_condition_match<'a>( 31 | fragment_condition: &'a Option, 32 | current_selection_set_type: &'a TypeDefinition, 33 | context: &'a OperationVisitorContext<'a>, 34 | ) -> bool { 35 | if let Some(TypeCondition::On(type_name)) = fragment_condition { 36 | if let Some(conditional_type) = context.schema.type_by_name(type_name) { 37 | if conditional_type 38 | .name() 39 | .eq(current_selection_set_type.name()) 40 | { 41 | return true; 42 | } 43 | 44 | if conditional_type.is_abstract_type() { 45 | match conditional_type { 46 | TypeDefinition::Interface(interface_type) => { 47 | return interface_type.is_implemented_by(current_selection_set_type) 48 | } 49 | TypeDefinition::Union(union_type) => { 50 | return union_type.has_sub_type(current_selection_set_type.name()) 51 | } 52 | _ => return false, 53 | } 54 | } 55 | } 56 | 57 | false 58 | } else { 59 | true 60 | } 61 | } 62 | 63 | fn collect_fields_inner<'a>( 64 | selection_set: &query::SelectionSet, 65 | parent_type: &schema::TypeDefinition, 66 | known_fragments: &HashMap<&str, &query::FragmentDefinition>, 67 | context: &'a OperationVisitorContext<'a>, 68 | result_arr: &mut HashMap>, 69 | visited_fragments_names: &mut Vec, 70 | ) { 71 | selection_set.items.iter().for_each(|item| match item { 72 | Selection::Field(f) => { 73 | let existing = result_arr.entry(f.name.clone()).or_default(); 74 | existing.push(f.clone()); 75 | } 76 | Selection::InlineFragment(f) => { 77 | if does_fragment_condition_match(&f.type_condition, parent_type, context) { 78 | collect_fields_inner( 79 | &f.selection_set, 80 | parent_type, 81 | known_fragments, 82 | context, 83 | result_arr, 84 | visited_fragments_names, 85 | ); 86 | } 87 | } 88 | Selection::FragmentSpread(f) => { 89 | if !visited_fragments_names 90 | .iter() 91 | .any(|name| f.fragment_name.eq(name)) 92 | { 93 | visited_fragments_names.push(f.fragment_name.clone()); 94 | 95 | if let Some(fragment) = known_fragments.get(f.fragment_name.as_str()) { 96 | if does_fragment_condition_match( 97 | &Some(fragment.type_condition.clone()), 98 | parent_type, 99 | context, 100 | ) { 101 | collect_fields_inner( 102 | &fragment.selection_set, 103 | parent_type, 104 | known_fragments, 105 | context, 106 | result_arr, 107 | visited_fragments_names, 108 | ); 109 | } 110 | } 111 | } 112 | } 113 | }); 114 | } 115 | -------------------------------------------------------------------------------- /src/ast/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod collect_fields; 2 | pub mod ext; 3 | pub mod operation_transformer; 4 | pub mod operation_visitor; 5 | /// Utilities visiting GraphQL AST trees 6 | pub mod schema_visitor; 7 | 8 | pub use self::collect_fields::*; 9 | pub use self::ext::*; 10 | pub use self::operation_transformer::*; 11 | pub use self::operation_visitor::*; 12 | pub use self::schema_visitor::*; 13 | -------------------------------------------------------------------------------- /src/ast/schema_visitor.rs: -------------------------------------------------------------------------------- 1 | use crate::static_graphql::schema::{ 2 | Definition, DirectiveDefinition, Document, EnumType, EnumValue, Field, InputObjectType, 3 | InputValue, InterfaceType, ObjectType, ScalarType, SchemaDefinition, TypeDefinition, UnionType, 4 | }; 5 | 6 | /// A trait for implenenting a visitor for GraphQL schema definition. 7 | pub trait SchemaVisitor { 8 | fn visit_schema_document(&self, document: &Document, _visitor_context: &mut T) { 9 | self.enter_document(document, _visitor_context); 10 | 11 | for definition in &document.definitions { 12 | match definition { 13 | Definition::SchemaDefinition(schema_definition) => { 14 | self.enter_schema_definition(schema_definition, _visitor_context); 15 | self.leave_schema_definition(schema_definition, _visitor_context); 16 | } 17 | Definition::TypeDefinition(type_definition) => { 18 | self.enter_type_definition(type_definition, _visitor_context); 19 | 20 | match type_definition { 21 | TypeDefinition::Object(object) => { 22 | self.enter_object_type(object, _visitor_context); 23 | 24 | for field in &object.fields { 25 | self.enter_object_type_field(field, object, _visitor_context); 26 | // TODO: More advanced setup for fields: arguments, lists, null/non-null, directives 27 | self.leave_object_type_field(field, object, _visitor_context); 28 | } 29 | 30 | self.leave_object_type(object, _visitor_context); 31 | } 32 | TypeDefinition::Scalar(scalar) => { 33 | self.enter_scalar_type(scalar, _visitor_context); 34 | self.leave_scalar_type(scalar, _visitor_context); 35 | } 36 | TypeDefinition::Enum(enum_) => { 37 | self.enter_enum_type(enum_, _visitor_context); 38 | 39 | for value in &enum_.values { 40 | self.enter_enum_value(value, enum_, _visitor_context); 41 | self.leave_enum_value(value, enum_, _visitor_context); 42 | } 43 | 44 | self.leave_enum_type(enum_, _visitor_context); 45 | } 46 | TypeDefinition::Union(union) => { 47 | self.enter_union_type(union, _visitor_context); 48 | self.leave_union_type(union, _visitor_context); 49 | } 50 | TypeDefinition::InputObject(input_object) => { 51 | self.enter_input_object_type(input_object, _visitor_context); 52 | 53 | for field in &input_object.fields { 54 | self.enter_input_object_type_field( 55 | field, 56 | input_object, 57 | _visitor_context, 58 | ); 59 | self.leave_input_object_type_field( 60 | field, 61 | input_object, 62 | _visitor_context, 63 | ); 64 | } 65 | 66 | self.leave_input_object_type(input_object, _visitor_context); 67 | } 68 | TypeDefinition::Interface(interface) => { 69 | self.enter_interface_type(interface, _visitor_context); 70 | 71 | for field in &interface.fields { 72 | self.enter_interface_type_field(field, interface, _visitor_context); 73 | self.leave_interface_type_field(field, interface, _visitor_context); 74 | } 75 | 76 | self.leave_interface_type(interface, _visitor_context); 77 | } 78 | } 79 | 80 | self.leave_type_definition(type_definition, _visitor_context); 81 | } 82 | Definition::DirectiveDefinition(directive_definition) => { 83 | self.enter_directive_definition(directive_definition, _visitor_context); 84 | self.leave_directive_definition(directive_definition, _visitor_context); 85 | } 86 | Definition::TypeExtension(_type_extension) => { 87 | // TODO: implement this 88 | panic!("TypeExtension not supported at the moment"); 89 | } 90 | } 91 | } 92 | 93 | self.leave_document(document, _visitor_context); 94 | } 95 | 96 | fn enter_document(&self, _node: &Document, _visitor_context: &mut T) {} 97 | fn leave_document(&self, _node: &Document, _visitor_context: &mut T) {} 98 | 99 | fn enter_schema_definition(&self, _node: &SchemaDefinition, _visitor_context: &mut T) {} 100 | fn leave_schema_definition(&self, _node: &SchemaDefinition, _visitor_context: &mut T) {} 101 | 102 | fn enter_directive_definition(&self, _node: &DirectiveDefinition, _visitor_context: &mut T) {} 103 | fn leave_directive_definition(&self, _node: &DirectiveDefinition, _visitor_context: &mut T) {} 104 | 105 | fn enter_type_definition(&self, _node: &TypeDefinition, _visitor_context: &mut T) {} 106 | fn leave_type_definition(&self, _node: &TypeDefinition, _visitor_context: &mut T) {} 107 | 108 | fn enter_interface_type(&self, _node: &InterfaceType, _visitor_context: &mut T) {} 109 | fn leave_interface_type(&self, _node: &InterfaceType, _visitor_context: &mut T) {} 110 | 111 | fn enter_interface_type_field( 112 | &self, 113 | _node: &Field, 114 | _type_: &InterfaceType, 115 | _visitor_context: &mut T, 116 | ) { 117 | } 118 | fn leave_interface_type_field( 119 | &self, 120 | _node: &Field, 121 | _type_: &InterfaceType, 122 | _visitor_context: &mut T, 123 | ) { 124 | } 125 | 126 | fn enter_object_type(&self, _node: &ObjectType, _visitor_context: &mut T) {} 127 | fn leave_object_type(&self, _node: &ObjectType, _visitor_context: &mut T) {} 128 | 129 | fn enter_object_type_field( 130 | &self, 131 | _node: &Field, 132 | _type_: &ObjectType, 133 | _visitor_context: &mut T, 134 | ) { 135 | } 136 | fn leave_object_type_field( 137 | &self, 138 | _node: &Field, 139 | _type_: &ObjectType, 140 | _visitor_context: &mut T, 141 | ) { 142 | } 143 | 144 | fn enter_input_object_type(&self, _node: &InputObjectType, _visitor_context: &mut T) {} 145 | fn leave_input_object_type(&self, _node: &InputObjectType, _visitor_context: &mut T) {} 146 | 147 | fn enter_input_object_type_field( 148 | &self, 149 | _node: &InputValue, 150 | _input_type: &InputObjectType, 151 | _visitor_context: &mut T, 152 | ) { 153 | } 154 | fn leave_input_object_type_field( 155 | &self, 156 | _node: &InputValue, 157 | _input_type: &InputObjectType, 158 | _visitor_context: &mut T, 159 | ) { 160 | } 161 | 162 | fn enter_union_type(&self, _node: &UnionType, _visitor_context: &mut T) {} 163 | fn leave_union_type(&self, _node: &UnionType, _visitor_context: &mut T) {} 164 | 165 | fn enter_scalar_type(&self, _node: &ScalarType, _visitor_context: &mut T) {} 166 | fn leave_scalar_type(&self, _node: &ScalarType, _visitor_context: &mut T) {} 167 | 168 | fn enter_enum_type(&self, _node: &EnumType, _visitor_context: &mut T) {} 169 | fn leave_enum_type(&self, _node: &EnumType, _visitor_context: &mut T) {} 170 | 171 | fn enter_enum_value(&self, _node: &EnumValue, _enum: &EnumType, _visitor_context: &mut T) {} 172 | fn leave_enum_value(&self, _node: &EnumValue, _enum: &EnumType, _visitor_context: &mut T) {} 173 | } 174 | 175 | #[test] 176 | fn visit_schema() { 177 | use crate::parser::schema::parse_schema; 178 | let schema_ast = parse_schema( 179 | r#" 180 | scalar Date 181 | 182 | type Query { 183 | user(id: ID!): User! 184 | users(filter: UsersFilter): [User!]! 185 | now: Date 186 | } 187 | 188 | input UsersFilter { 189 | name: String 190 | } 191 | 192 | type User implements Node { 193 | id: ID! 194 | name: String! 195 | role: Role! 196 | } 197 | 198 | interface Node { 199 | id: ID! 200 | } 201 | 202 | type Test { 203 | foo: String! 204 | } 205 | 206 | enum Role { 207 | USER 208 | ADMIN 209 | } 210 | 211 | union TestUnion = Test | User 212 | 213 | "#, 214 | ) 215 | .expect("Failed to parse schema"); 216 | 217 | struct TestVisitorCollected { 218 | collected_object_type: Vec, 219 | collected_scalar_type: Vec, 220 | collected_union_type: Vec, 221 | collected_input_type: Vec, 222 | collected_enum_type: Vec, 223 | collected_enum_value: Vec, 224 | collected_interface_type: Vec, 225 | collected_object_type_field: Vec, 226 | collected_interface_type_field: Vec, 227 | collected_input_type_fields: Vec, 228 | } 229 | 230 | struct TestVisitor; 231 | 232 | impl TestVisitor { 233 | fn collect_visited_info(&self, document: &Document) -> TestVisitorCollected { 234 | let mut collected = TestVisitorCollected { 235 | collected_object_type: Vec::new(), 236 | collected_interface_type: Vec::new(), 237 | collected_object_type_field: Vec::new(), 238 | collected_interface_type_field: Vec::new(), 239 | collected_scalar_type: Vec::new(), 240 | collected_union_type: Vec::new(), 241 | collected_enum_type: Vec::new(), 242 | collected_enum_value: Vec::new(), 243 | collected_input_type: Vec::new(), 244 | collected_input_type_fields: Vec::new(), 245 | }; 246 | self.visit_schema_document(document, &mut collected); 247 | 248 | collected 249 | } 250 | } 251 | 252 | impl SchemaVisitor for TestVisitor { 253 | fn enter_object_type( 254 | &self, 255 | _node: &ObjectType, 256 | _visitor_context: &mut TestVisitorCollected, 257 | ) { 258 | _visitor_context 259 | .collected_object_type 260 | .push(_node.name.clone()); 261 | } 262 | 263 | fn enter_object_type_field( 264 | &self, 265 | _node: &Field, 266 | _type_: &ObjectType, 267 | _visitor_context: &mut TestVisitorCollected, 268 | ) { 269 | let field_id = format!("{}.{}", _type_.name.as_str(), _node.name.as_str()); 270 | _visitor_context.collected_object_type_field.push(field_id); 271 | } 272 | 273 | fn enter_interface_type( 274 | &self, 275 | _node: &InterfaceType, 276 | _visitor_context: &mut TestVisitorCollected, 277 | ) { 278 | _visitor_context 279 | .collected_interface_type 280 | .push(_node.name.clone()); 281 | } 282 | 283 | fn enter_interface_type_field( 284 | &self, 285 | _node: &Field, 286 | _type_: &InterfaceType, 287 | _visitor_context: &mut TestVisitorCollected, 288 | ) { 289 | _visitor_context 290 | .collected_interface_type_field 291 | .push(_node.name.clone()); 292 | } 293 | 294 | fn enter_scalar_type( 295 | &self, 296 | _node: &ScalarType, 297 | _visitor_context: &mut TestVisitorCollected, 298 | ) { 299 | _visitor_context 300 | .collected_scalar_type 301 | .push(_node.name.clone()); 302 | } 303 | 304 | fn enter_union_type(&self, _node: &UnionType, _visitor_context: &mut TestVisitorCollected) { 305 | _visitor_context 306 | .collected_union_type 307 | .push(_node.name.clone()); 308 | } 309 | 310 | fn enter_enum_type(&self, _node: &EnumType, _visitor_context: &mut TestVisitorCollected) { 311 | _visitor_context 312 | .collected_enum_type 313 | .push(_node.name.clone()); 314 | } 315 | 316 | fn enter_enum_value( 317 | &self, 318 | _node: &EnumValue, 319 | _enum: &EnumType, 320 | _visitor_context: &mut TestVisitorCollected, 321 | ) { 322 | let enum_value_id = format!("{}.{}", _enum.name.as_str(), _node.name.as_str()); 323 | _visitor_context.collected_enum_value.push(enum_value_id); 324 | } 325 | 326 | fn enter_input_object_type( 327 | &self, 328 | _node: &InputObjectType, 329 | _visitor_context: &mut TestVisitorCollected, 330 | ) { 331 | _visitor_context 332 | .collected_input_type 333 | .push(_node.name.clone()); 334 | } 335 | 336 | fn enter_input_object_type_field( 337 | &self, 338 | _node: &InputValue, 339 | _input_type: &InputObjectType, 340 | _visitor_context: &mut TestVisitorCollected, 341 | ) { 342 | let field_id = format!("{}.{}", _input_type.name.as_str(), _node.name.as_str()); 343 | _visitor_context.collected_input_type_fields.push(field_id); 344 | } 345 | } 346 | 347 | let visitor = TestVisitor {}; 348 | let collected = visitor.collect_visited_info(&schema_ast); 349 | 350 | assert_eq!( 351 | collected.collected_object_type, 352 | vec!["Query", "User", "Test"] 353 | ); 354 | assert_eq!( 355 | collected.collected_object_type_field, 356 | vec![ 357 | "Query.user", 358 | "Query.users", 359 | "Query.now", 360 | "User.id", 361 | "User.name", 362 | "User.role", 363 | "Test.foo" 364 | ] 365 | ); 366 | assert_eq!(collected.collected_interface_type, vec!["Node"]); 367 | assert_eq!(collected.collected_union_type, vec!["TestUnion"]); 368 | assert_eq!(collected.collected_scalar_type, vec!["Date"]); 369 | assert_eq!(collected.collected_enum_type, vec!["Role"]); 370 | assert_eq!( 371 | collected.collected_enum_value, 372 | vec!["Role.USER", "Role.ADMIN"] 373 | ); 374 | assert_eq!(collected.collected_input_type, vec!["UsersFilter"]); 375 | assert_eq!( 376 | collected.collected_input_type_fields, 377 | vec!["UsersFilter.name"] 378 | ); 379 | } 380 | -------------------------------------------------------------------------------- /src/introspection/introspection.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | use serde_json::{Result, Value}; 5 | 6 | #[derive(Serialize, Deserialize, Debug)] 7 | pub struct IntrospectionQuery { 8 | pub __schema: IntrospectionSchema, 9 | } 10 | 11 | #[derive(Serialize, Deserialize, Debug)] 12 | pub struct IntrospectionScalarType { 13 | pub name: String, 14 | pub description: Option, 15 | #[serde(rename = "specifiedByURL")] 16 | pub specified_by_url: Option, 17 | } 18 | 19 | #[derive(Serialize, Deserialize, Debug)] 20 | pub struct IntrospectionInputValue { 21 | pub name: String, 22 | pub description: Option, 23 | #[serde(rename = "defaultValue")] 24 | pub default_value: Option, 25 | #[serde(rename = "isDeprecated")] 26 | pub is_deprecated: Option, 27 | #[serde(rename = "deprecationReason")] 28 | pub deprecation_reason: Option, 29 | #[serde(rename = "type")] 30 | pub type_ref: Option, 31 | } 32 | 33 | #[derive(Serialize, Deserialize, Debug)] 34 | #[serde(tag = "kind")] 35 | pub struct IntrospectionField { 36 | pub name: String, 37 | pub description: Option, 38 | pub args: Vec, 39 | #[serde(rename = "isDeprecated")] 40 | pub is_deprecated: Option, 41 | #[serde(rename = "deprecationReason")] 42 | pub deprecation_reason: Option, 43 | #[serde(rename = "type")] 44 | pub type_ref: IntrospectionOutputTypeRef, 45 | } 46 | 47 | #[derive(Serialize, Deserialize, Debug)] 48 | pub struct IntrospectionObjectType { 49 | pub name: String, 50 | pub description: Option, 51 | pub fields: Vec, 52 | pub interfaces: Vec, 53 | } 54 | 55 | #[derive(Serialize, Deserialize, Debug)] 56 | pub struct IntrospectionInterfaceType { 57 | pub name: String, 58 | pub description: Option, 59 | pub fields: Vec, 60 | pub interfaces: Option>, 61 | #[serde(rename = "possibleTypes")] 62 | pub possible_types: Vec, 63 | } 64 | 65 | #[derive(Serialize, Deserialize, Debug)] 66 | pub struct IntrospectionUnionType { 67 | pub name: String, 68 | pub description: Option, 69 | #[serde(rename = "possibleTypes")] 70 | pub possible_types: Vec, 71 | } 72 | 73 | #[derive(Serialize, Deserialize, Debug)] 74 | pub struct IntrospectionEnumValue { 75 | pub name: String, 76 | pub description: Option, 77 | #[serde(rename = "isDeprecated")] 78 | pub is_deprecated: Option, 79 | #[serde(rename = "deprecationReason")] 80 | pub deprecation_reason: Option, 81 | } 82 | 83 | #[derive(Serialize, Deserialize, Debug)] 84 | pub struct IntrospectionEnumType { 85 | pub name: String, 86 | pub description: Option, 87 | #[serde(rename = "enumValues")] 88 | pub enum_values: Vec, 89 | } 90 | 91 | #[derive(Serialize, Deserialize, Debug)] 92 | pub struct IntrospectionInputObjectType { 93 | pub name: String, 94 | pub description: Option, 95 | #[serde(rename = "inputFields")] 96 | pub input_fields: Vec, 97 | } 98 | 99 | #[derive(Serialize, Deserialize, Debug)] 100 | #[serde(tag = "kind")] 101 | pub enum IntrospectionType { 102 | SCALAR(IntrospectionScalarType), 103 | OBJECT(IntrospectionObjectType), 104 | INTERFACE(IntrospectionInterfaceType), 105 | UNION(IntrospectionUnionType), 106 | ENUM(IntrospectionEnumType), 107 | INPUT_OBJECT(IntrospectionInputObjectType), 108 | } 109 | 110 | impl IntrospectionType { 111 | pub fn name(&self) -> &String { 112 | match &self { 113 | IntrospectionType::ENUM(e) => &e.name, 114 | IntrospectionType::OBJECT(o) => &o.name, 115 | IntrospectionType::INPUT_OBJECT(io) => &io.name, 116 | IntrospectionType::INTERFACE(i) => &i.name, 117 | IntrospectionType::SCALAR(s) => &s.name, 118 | IntrospectionType::UNION(u) => &u.name, 119 | } 120 | } 121 | } 122 | 123 | #[derive(Serialize, Deserialize, Debug)] 124 | #[serde(tag = "kind")] 125 | pub enum IntrospectionInputType { 126 | SCALAR(IntrospectionScalarType), 127 | ENUM(IntrospectionEnumType), 128 | INPUT_OBJECT(IntrospectionInputObjectType), 129 | } 130 | 131 | #[derive(Serialize, Deserialize, Debug)] 132 | #[serde(tag = "kind")] 133 | pub enum IntrospectionOutputType { 134 | SCALAR(IntrospectionScalarType), 135 | OBJECT(IntrospectionObjectType), 136 | INTERFACE(IntrospectionInterfaceType), 137 | UNION(IntrospectionUnionType), 138 | ENUM(IntrospectionEnumType), 139 | } 140 | 141 | #[derive(Serialize, Deserialize, Debug)] 142 | pub struct IntrospectionNamedTypeRef { 143 | pub name: String, 144 | } 145 | 146 | #[derive(Serialize, Deserialize, Debug)] 147 | #[serde(tag = "kind")] 148 | pub enum IntrospectionOutputTypeRef { 149 | SCALAR(IntrospectionNamedTypeRef), 150 | LIST { 151 | #[serde(rename = "ofType")] 152 | of_type: Option>, 153 | }, 154 | NON_NULL { 155 | #[serde(rename = "ofType")] 156 | of_type: Option>, 157 | }, 158 | ENUM(IntrospectionNamedTypeRef), 159 | INPUT_OBJECT(IntrospectionNamedTypeRef), 160 | UNION(IntrospectionNamedTypeRef), 161 | OBJECT(IntrospectionNamedTypeRef), 162 | INTERFACE(IntrospectionNamedTypeRef), 163 | } 164 | 165 | #[derive(Serialize, Deserialize, Debug)] 166 | #[serde(tag = "kind")] 167 | pub enum IntrospectionInputTypeRef { 168 | LIST { 169 | #[serde(rename = "ofType")] 170 | of_type: Option>, 171 | }, 172 | NON_NULL { 173 | #[serde(rename = "ofType")] 174 | of_type: Option>, 175 | }, 176 | SCALAR(IntrospectionNamedTypeRef), 177 | ENUM(IntrospectionNamedTypeRef), 178 | INPUT_OBJECT(IntrospectionNamedTypeRef), 179 | } 180 | 181 | #[derive(Serialize, Deserialize, Debug)] 182 | pub struct IntrospectionSchema { 183 | pub description: Option, 184 | #[serde(rename = "queryType")] 185 | pub query_type: IntrospectionNamedTypeRef, 186 | #[serde(rename = "mutationType")] 187 | pub mutation_type: Option, 188 | #[serde(rename = "subscriptionType")] 189 | pub subscription_type: Option, 190 | pub types: Vec, 191 | pub directives: Vec, 192 | } 193 | 194 | #[derive(Serialize, Deserialize, Debug)] 195 | pub enum DirectiveLocation { 196 | QUERY, 197 | MUTATION, 198 | SUBSCRIPTION, 199 | FIELD, 200 | FRAGMENT_DEFINITION, 201 | FRAGMENT_SPREAD, 202 | INLINE_FRAGMENT, 203 | VARIABLE_DEFINITION, 204 | /** Type System Definitions */ 205 | SCHEMA, 206 | SCALAR, 207 | OBJECT, 208 | FIELD_DEFINITION, 209 | ARGUMENT_DEFINITION, 210 | INTERFACE, 211 | UNION, 212 | ENUM, 213 | ENUM_VALUE, 214 | INPUT_OBJECT, 215 | INPUT_FIELD_DEFINITION, 216 | } 217 | 218 | #[derive(Serialize, Deserialize, Debug)] 219 | pub struct IntrospectionDirective { 220 | pub name: String, 221 | pub description: Option, 222 | #[serde(rename = "isRepeatable")] 223 | pub is_repeatable: Option, 224 | pub locations: Vec, 225 | pub args: Vec, 226 | } 227 | 228 | pub fn parse_introspection_from_string(input: &str) -> Result { 229 | serde_json::from_str(input) 230 | } 231 | 232 | pub fn parse_introspection(input: R) -> Result 233 | where 234 | R: io::Read, 235 | { 236 | serde_json::from_reader::(input) 237 | } 238 | 239 | #[test] 240 | fn test_product_introspection() { 241 | use std::fs::File; 242 | let json_file = File::open("./src/introspection/test_files/product_introspection.json") 243 | .expect("failed to open json file"); 244 | parse_introspection(json_file).expect("failed to parse introspection json"); 245 | } 246 | 247 | #[test] 248 | fn test_github_introspection() { 249 | use std::fs::File; 250 | let json_file = File::open("./src/introspection/test_files/github_introspection.json") 251 | .expect("failed to open json file"); 252 | parse_introspection(json_file).expect("failed to parse introspection json"); 253 | } 254 | 255 | #[test] 256 | fn test_shopify_introspection() { 257 | use std::fs::File; 258 | let json_file = File::open("./src/introspection/test_files/shopify_introspection.json") 259 | .expect("failed to open json file"); 260 | parse_introspection(json_file).expect("failed to parse introspection json"); 261 | } 262 | -------------------------------------------------------------------------------- /src/introspection/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_camel_case_types)] 2 | mod introspection; 3 | 4 | pub use self::introspection::*; 5 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! graphql-tools 2 | //! ============== 3 | //! 4 | //! This library implements tooling around GraphQL for Rust libraries. 5 | //! Most of the tools are based on `trait`s and `struct`s implemented in `graphql_parser` crate. 6 | //! 7 | 8 | pub mod ast; 9 | 10 | pub mod static_graphql { 11 | macro_rules! static_graphql { 12 | ($m:ident, $m2:ident, {$($n:ident,)*}) => { 13 | pub mod $m { 14 | use crate::parser::$m2 as $m; 15 | pub use $m::*; 16 | $( 17 | pub type $n = $m::$n<'static, String>; 18 | )* 19 | } 20 | }; 21 | } 22 | 23 | static_graphql!(query, query, { 24 | Document, Value, OperationDefinition, InlineFragment, TypeCondition, 25 | FragmentSpread, Field, Selection, SelectionSet, FragmentDefinition, 26 | Directive, VariableDefinition, Type, Query, Definition, Subscription, Mutation, 27 | }); 28 | static_graphql!(schema, schema, { 29 | Field, Directive, InterfaceType, ObjectType, Value, TypeDefinition, 30 | EnumType, Type, Document, ScalarType, InputValue, DirectiveDefinition, 31 | UnionType, InputObjectType, EnumValue, SchemaDefinition, 32 | }); 33 | } 34 | 35 | pub mod introspection; 36 | 37 | pub mod validation; 38 | 39 | #[cfg(feature = "graphql_parser")] 40 | pub extern crate graphql_parser as parser; 41 | #[cfg(feature = "graphql_parser_fork")] 42 | pub extern crate graphql_parser_hive_fork as parser; 43 | -------------------------------------------------------------------------------- /src/validation/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod rules; 2 | pub mod utils; 3 | pub mod validate; 4 | 5 | #[cfg(test)] 6 | pub mod test_utils; 7 | -------------------------------------------------------------------------------- /src/validation/rules/defaults.rs: -------------------------------------------------------------------------------- 1 | use crate::validation::validate::ValidationPlan; 2 | 3 | use super::{ 4 | FieldsOnCorrectType, FragmentsOnCompositeTypes, KnownArgumentNames, KnownDirectives, 5 | KnownFragmentNames, KnownTypeNames, LeafFieldSelections, LoneAnonymousOperation, 6 | NoFragmentsCycle, NoUndefinedVariables, NoUnusedFragments, NoUnusedVariables, 7 | OverlappingFieldsCanBeMerged, PossibleFragmentSpreads, ProvidedRequiredArguments, 8 | SingleFieldSubscriptions, UniqueArgumentNames, UniqueDirectivesPerLocation, 9 | UniqueFragmentNames, UniqueOperationNames, UniqueVariableNames, ValuesOfCorrectType, 10 | VariablesAreInputTypes, VariablesInAllowedPosition, 11 | }; 12 | 13 | pub fn default_rules_validation_plan() -> ValidationPlan { 14 | let mut plan = ValidationPlan { rules: vec![] }; 15 | 16 | plan.add_rule(Box::new(UniqueOperationNames::new())); 17 | plan.add_rule(Box::new(LoneAnonymousOperation::new())); 18 | plan.add_rule(Box::new(SingleFieldSubscriptions::new())); 19 | plan.add_rule(Box::new(KnownTypeNames::new())); 20 | plan.add_rule(Box::new(FragmentsOnCompositeTypes::new())); 21 | plan.add_rule(Box::new(VariablesAreInputTypes::new())); 22 | plan.add_rule(Box::new(LeafFieldSelections::new())); 23 | plan.add_rule(Box::new(FieldsOnCorrectType::new())); 24 | plan.add_rule(Box::new(UniqueFragmentNames::new())); 25 | plan.add_rule(Box::new(KnownFragmentNames::new())); 26 | plan.add_rule(Box::new(NoUnusedFragments::new())); 27 | plan.add_rule(Box::new(OverlappingFieldsCanBeMerged::new())); 28 | plan.add_rule(Box::new(NoFragmentsCycle::new())); 29 | plan.add_rule(Box::new(PossibleFragmentSpreads::new())); 30 | plan.add_rule(Box::new(NoUnusedVariables::new())); 31 | plan.add_rule(Box::new(NoUndefinedVariables::new())); 32 | plan.add_rule(Box::new(KnownArgumentNames::new())); 33 | plan.add_rule(Box::new(UniqueArgumentNames::new())); 34 | plan.add_rule(Box::new(UniqueVariableNames::new())); 35 | plan.add_rule(Box::new(ProvidedRequiredArguments::new())); 36 | plan.add_rule(Box::new(KnownDirectives::new())); 37 | plan.add_rule(Box::new(VariablesInAllowedPosition::new())); 38 | plan.add_rule(Box::new(ValuesOfCorrectType::new())); 39 | plan.add_rule(Box::new(UniqueDirectivesPerLocation::new())); 40 | 41 | plan 42 | } 43 | -------------------------------------------------------------------------------- /src/validation/rules/fragments_on_composite_types.rs: -------------------------------------------------------------------------------- 1 | use super::ValidationRule; 2 | use crate::ast::{ 3 | visit_document, OperationVisitor, OperationVisitorContext, SchemaDocumentExtension, 4 | TypeDefinitionExtension, 5 | }; 6 | use crate::static_graphql::query::*; 7 | use crate::validation::utils::{ValidationError, ValidationErrorContext}; 8 | 9 | /// Fragments on composite type 10 | /// 11 | /// Fragments use a type condition to determine if they apply, since fragments 12 | /// can only be spread into a composite type (object, interface, or union), the 13 | /// type condition must also be a composite type. 14 | /// 15 | /// https://spec.graphql.org/draft/#sec-Fragments-On-Composite-Types 16 | pub struct FragmentsOnCompositeTypes; 17 | 18 | impl Default for FragmentsOnCompositeTypes { 19 | fn default() -> Self { 20 | Self::new() 21 | } 22 | } 23 | 24 | impl FragmentsOnCompositeTypes { 25 | pub fn new() -> Self { 26 | FragmentsOnCompositeTypes 27 | } 28 | } 29 | 30 | impl<'a> OperationVisitor<'a, ValidationErrorContext> for FragmentsOnCompositeTypes { 31 | fn enter_inline_fragment( 32 | &mut self, 33 | visitor_context: &mut OperationVisitorContext, 34 | user_context: &mut ValidationErrorContext, 35 | inline_fragment: &InlineFragment, 36 | ) { 37 | if let Some(TypeCondition::On(type_condition)) = &inline_fragment.type_condition { 38 | if let Some(gql_type) = visitor_context.schema.type_by_name(type_condition) { 39 | if !gql_type.is_composite_type() { 40 | user_context.report_error(ValidationError { 41 | locations: vec![inline_fragment.position], 42 | error_code: self.error_code(), 43 | message: format!( 44 | "Fragment cannot condition on non composite type \"{}\".", 45 | type_condition 46 | ), 47 | }) 48 | } 49 | } 50 | } 51 | } 52 | 53 | fn enter_fragment_definition( 54 | &mut self, 55 | visitor_context: &mut OperationVisitorContext, 56 | user_context: &mut ValidationErrorContext, 57 | fragment_definition: &FragmentDefinition, 58 | ) { 59 | let TypeCondition::On(type_condition) = &fragment_definition.type_condition; 60 | 61 | if let Some(gql_type) = visitor_context.schema.type_by_name(type_condition) { 62 | if !gql_type.is_composite_type() { 63 | user_context.report_error(ValidationError { 64 | locations: vec![fragment_definition.position], 65 | error_code: self.error_code(), 66 | message: format!( 67 | "Fragment \"{}\" cannot condition on non composite type \"{}\".", 68 | fragment_definition.name, type_condition 69 | ), 70 | }) 71 | } 72 | } 73 | } 74 | } 75 | 76 | impl ValidationRule for FragmentsOnCompositeTypes { 77 | fn error_code<'a>(&self) -> &'a str { 78 | "FragmentsOnCompositeTypes" 79 | } 80 | 81 | fn validate( 82 | &self, 83 | ctx: &mut OperationVisitorContext, 84 | error_collector: &mut ValidationErrorContext, 85 | ) { 86 | visit_document( 87 | &mut FragmentsOnCompositeTypes::new(), 88 | ctx.operation, 89 | ctx, 90 | error_collector, 91 | ); 92 | } 93 | } 94 | 95 | #[test] 96 | fn object_is_valid_fragment_type() { 97 | use crate::validation::test_utils::*; 98 | 99 | let mut plan = create_plan_from_rule(Box::new(FragmentsOnCompositeTypes {})); 100 | let errors = test_operation_with_schema( 101 | "fragment validFragment on Dog { 102 | barks 103 | }", 104 | TEST_SCHEMA, 105 | &mut plan, 106 | ); 107 | 108 | assert_eq!(get_messages(&errors).len(), 0); 109 | } 110 | 111 | #[test] 112 | fn interface_is_valid_fragment_type() { 113 | use crate::validation::test_utils::*; 114 | 115 | let mut plan = create_plan_from_rule(Box::new(FragmentsOnCompositeTypes {})); 116 | let errors = test_operation_with_schema( 117 | "fragment validFragment on Pet { 118 | name 119 | }", 120 | TEST_SCHEMA, 121 | &mut plan, 122 | ); 123 | 124 | assert_eq!(get_messages(&errors).len(), 0); 125 | } 126 | 127 | #[test] 128 | fn object_is_valid_inline_fragment_type() { 129 | use crate::validation::test_utils::*; 130 | 131 | let mut plan = create_plan_from_rule(Box::new(FragmentsOnCompositeTypes {})); 132 | let errors = test_operation_with_schema( 133 | "fragment validFragment on Pet { 134 | ... on Dog { 135 | barks 136 | } 137 | }", 138 | TEST_SCHEMA, 139 | &mut plan, 140 | ); 141 | 142 | assert_eq!(get_messages(&errors).len(), 0); 143 | } 144 | 145 | #[test] 146 | fn interface_is_valid_inline_fragment_type() { 147 | use crate::validation::test_utils::*; 148 | 149 | let mut plan = create_plan_from_rule(Box::new(FragmentsOnCompositeTypes {})); 150 | let errors = test_operation_with_schema( 151 | "fragment validFragment on Mammal { 152 | ... on Canine { 153 | name 154 | } 155 | }", 156 | TEST_SCHEMA, 157 | &mut plan, 158 | ); 159 | 160 | assert_eq!(get_messages(&errors).len(), 0); 161 | } 162 | 163 | #[test] 164 | fn inline_fragment_without_type_is_valid() { 165 | use crate::validation::test_utils::*; 166 | 167 | let mut plan = create_plan_from_rule(Box::new(FragmentsOnCompositeTypes {})); 168 | let errors = test_operation_with_schema( 169 | "fragment validFragment on Pet { 170 | ... { 171 | name 172 | } 173 | }", 174 | TEST_SCHEMA, 175 | &mut plan, 176 | ); 177 | 178 | assert_eq!(get_messages(&errors).len(), 0); 179 | } 180 | 181 | #[test] 182 | fn union_is_valid_fragment_type() { 183 | use crate::validation::test_utils::*; 184 | 185 | let mut plan = create_plan_from_rule(Box::new(FragmentsOnCompositeTypes {})); 186 | let errors = test_operation_with_schema( 187 | "fragment validFragment on CatOrDog { 188 | __typename 189 | }", 190 | TEST_SCHEMA, 191 | &mut plan, 192 | ); 193 | 194 | assert_eq!(get_messages(&errors).len(), 0); 195 | } 196 | 197 | #[test] 198 | fn scalar_is_invalid_fragment_type() { 199 | use crate::validation::test_utils::*; 200 | 201 | let mut plan = create_plan_from_rule(Box::new(FragmentsOnCompositeTypes {})); 202 | let errors = test_operation_with_schema( 203 | "fragment scalarFragment on Boolean { 204 | bad 205 | }", 206 | TEST_SCHEMA, 207 | &mut plan, 208 | ); 209 | 210 | let messages = get_messages(&errors); 211 | assert_eq!(messages.len(), 1); 212 | assert_eq!( 213 | messages, 214 | vec!["Fragment \"scalarFragment\" cannot condition on non composite type \"Boolean\"."] 215 | ); 216 | } 217 | 218 | #[test] 219 | fn enum_is_invalid_fragment_type() { 220 | use crate::validation::test_utils::*; 221 | 222 | let mut plan = create_plan_from_rule(Box::new(FragmentsOnCompositeTypes {})); 223 | let errors = test_operation_with_schema( 224 | "fragment scalarFragment on FurColor { 225 | bad 226 | }", 227 | TEST_SCHEMA, 228 | &mut plan, 229 | ); 230 | 231 | let messages = get_messages(&errors); 232 | assert_eq!(messages.len(), 1); 233 | assert_eq!( 234 | messages, 235 | vec!["Fragment \"scalarFragment\" cannot condition on non composite type \"FurColor\"."] 236 | ); 237 | } 238 | 239 | #[test] 240 | fn input_object_is_invalid_fragment_type() { 241 | use crate::validation::test_utils::*; 242 | 243 | let mut plan = create_plan_from_rule(Box::new(FragmentsOnCompositeTypes {})); 244 | let errors = test_operation_with_schema( 245 | "fragment inputFragment on ComplexInput { 246 | stringField 247 | }", 248 | TEST_SCHEMA, 249 | &mut plan, 250 | ); 251 | 252 | let messages = get_messages(&errors); 253 | assert_eq!(messages.len(), 1); 254 | assert_eq!( 255 | messages, 256 | vec!["Fragment \"inputFragment\" cannot condition on non composite type \"ComplexInput\"."] 257 | ); 258 | } 259 | 260 | #[test] 261 | fn scalar_is_invalid_inline_fragment_type() { 262 | use crate::validation::test_utils::*; 263 | 264 | let mut plan = create_plan_from_rule(Box::new(FragmentsOnCompositeTypes {})); 265 | let errors = test_operation_with_schema( 266 | "fragment invalidFragment on Pet { 267 | ... on String { 268 | barks 269 | } 270 | }", 271 | TEST_SCHEMA, 272 | &mut plan, 273 | ); 274 | 275 | let messages = get_messages(&errors); 276 | assert_eq!(messages.len(), 1); 277 | assert_eq!( 278 | messages, 279 | vec!["Fragment cannot condition on non composite type \"String\"."] 280 | ); 281 | } 282 | -------------------------------------------------------------------------------- /src/validation/rules/known_argument_names.rs: -------------------------------------------------------------------------------- 1 | use super::ValidationRule; 2 | use crate::ast::ext::TypeDefinitionExtension; 3 | use crate::ast::{ 4 | visit_document, FieldByNameExtension, OperationVisitor, OperationVisitorContext, 5 | SchemaDocumentExtension, 6 | }; 7 | use crate::static_graphql::query::Directive; 8 | use crate::static_graphql::schema::{InputValue, TypeDefinition}; 9 | use crate::validation::utils::{ValidationError, ValidationErrorContext}; 10 | /// Known argument names 11 | /// 12 | /// A GraphQL field/directive is only valid if all supplied arguments are defined by 13 | /// that field. 14 | /// 15 | /// See https://spec.graphql.org/draft/#sec-Argument-Names 16 | /// See https://spec.graphql.org/draft/#sec-Directives-Are-In-Valid-Locations 17 | pub struct KnownArgumentNames<'a> { 18 | current_known_arguments: Option<(ArgumentParent<'a>, &'a Vec)>, 19 | } 20 | 21 | #[derive(Debug)] 22 | enum ArgumentParent<'a> { 23 | Field(&'a str, &'a TypeDefinition), 24 | Directive(&'a str), 25 | } 26 | 27 | impl<'a> Default for KnownArgumentNames<'a> { 28 | fn default() -> Self { 29 | Self::new() 30 | } 31 | } 32 | 33 | impl<'a> KnownArgumentNames<'a> { 34 | pub fn new() -> Self { 35 | KnownArgumentNames { 36 | current_known_arguments: None, 37 | } 38 | } 39 | } 40 | 41 | impl<'a> OperationVisitor<'a, ValidationErrorContext> for KnownArgumentNames<'a> { 42 | fn enter_directive( 43 | &mut self, 44 | visitor_context: &mut OperationVisitorContext<'a>, 45 | _: &mut ValidationErrorContext, 46 | directive: &Directive, 47 | ) { 48 | if let Some(directive_def) = visitor_context.schema.directive_by_name(&directive.name) { 49 | self.current_known_arguments = Some(( 50 | ArgumentParent::Directive(&directive_def.name), 51 | &directive_def.arguments, 52 | )); 53 | } 54 | } 55 | 56 | fn leave_directive( 57 | &mut self, 58 | _: &mut OperationVisitorContext, 59 | _: &mut ValidationErrorContext, 60 | _: &crate::static_graphql::query::Directive, 61 | ) { 62 | self.current_known_arguments = None; 63 | } 64 | 65 | fn enter_field( 66 | &mut self, 67 | visitor_context: &mut OperationVisitorContext<'a>, 68 | _: &mut ValidationErrorContext, 69 | field: &crate::static_graphql::query::Field, 70 | ) { 71 | if let Some(parent_type) = visitor_context.current_parent_type() { 72 | if let Some(field_def) = parent_type.field_by_name(&field.name) { 73 | self.current_known_arguments = Some(( 74 | ArgumentParent::Field( 75 | &field_def.name, 76 | visitor_context 77 | .current_parent_type() 78 | .expect("Missing parent type"), 79 | ), 80 | &field_def.arguments, 81 | )); 82 | } 83 | } 84 | } 85 | 86 | fn leave_field( 87 | &mut self, 88 | _: &mut OperationVisitorContext, 89 | _: &mut ValidationErrorContext, 90 | _: &crate::static_graphql::query::Field, 91 | ) { 92 | self.current_known_arguments = None; 93 | } 94 | 95 | fn enter_argument( 96 | &mut self, 97 | _: &mut OperationVisitorContext, 98 | user_context: &mut ValidationErrorContext, 99 | (argument_name, _argument_value): &(String, crate::static_graphql::query::Value), 100 | ) { 101 | if let Some((arg_position, args)) = &self.current_known_arguments { 102 | if !args.iter().any(|a| a.name.eq(argument_name)) { 103 | match arg_position { 104 | ArgumentParent::Field(field_name, type_name) => { 105 | user_context.report_error(ValidationError { 106 | error_code: self.error_code(), 107 | message: format!( 108 | "Unknown argument \"{}\" on field \"{}.{}\".", 109 | argument_name, 110 | type_name.name(), 111 | field_name 112 | ), 113 | locations: vec![], 114 | }) 115 | } 116 | ArgumentParent::Directive(directive_name) => { 117 | user_context.report_error(ValidationError { 118 | error_code: self.error_code(), 119 | message: format!( 120 | "Unknown argument \"{}\" on directive \"@{}\".", 121 | argument_name, directive_name 122 | ), 123 | locations: vec![], 124 | }) 125 | } 126 | }; 127 | } 128 | } 129 | } 130 | } 131 | 132 | impl<'k> ValidationRule for KnownArgumentNames<'k> { 133 | fn error_code<'a>(&self) -> &'a str { 134 | "KnownArgumentNames" 135 | } 136 | 137 | fn validate( 138 | &self, 139 | ctx: &mut OperationVisitorContext, 140 | error_collector: &mut ValidationErrorContext, 141 | ) { 142 | visit_document( 143 | &mut KnownArgumentNames::new(), 144 | ctx.operation, 145 | ctx, 146 | error_collector, 147 | ); 148 | } 149 | } 150 | 151 | #[test] 152 | fn single_arg_is_known() { 153 | use crate::validation::test_utils::*; 154 | 155 | let mut plan = create_plan_from_rule(Box::new(KnownArgumentNames::new())); 156 | let errors = test_operation_with_schema( 157 | "fragment argOnRequiredArg on Dog { 158 | doesKnowCommand(dogCommand: SIT) 159 | }", 160 | TEST_SCHEMA, 161 | &mut plan, 162 | ); 163 | 164 | assert_eq!(get_messages(&errors).len(), 0); 165 | } 166 | 167 | #[test] 168 | fn multple_args_are_known() { 169 | use crate::validation::test_utils::*; 170 | 171 | let mut plan = create_plan_from_rule(Box::new(KnownArgumentNames::new())); 172 | let errors = test_operation_with_schema( 173 | "fragment multipleArgs on ComplicatedArgs { 174 | multipleReqs(req1: 1, req2: 2) 175 | }", 176 | TEST_SCHEMA, 177 | &mut plan, 178 | ); 179 | 180 | assert_eq!(get_messages(&errors).len(), 0); 181 | } 182 | 183 | #[test] 184 | fn ignores_args_of_unknown_fields() { 185 | use crate::validation::test_utils::*; 186 | 187 | let mut plan = create_plan_from_rule(Box::new(KnownArgumentNames::new())); 188 | let errors = test_operation_with_schema( 189 | "fragment argOnUnknownField on Dog { 190 | unknownField(unknownArg: SIT) 191 | }", 192 | TEST_SCHEMA, 193 | &mut plan, 194 | ); 195 | 196 | assert_eq!(get_messages(&errors).len(), 0); 197 | } 198 | 199 | #[test] 200 | fn multiple_args_in_reverse_order_are_known() { 201 | use crate::validation::test_utils::*; 202 | 203 | let mut plan = create_plan_from_rule(Box::new(KnownArgumentNames::new())); 204 | let errors = test_operation_with_schema( 205 | "fragment multipleArgsReverseOrder on ComplicatedArgs { 206 | multipleReqs(req2: 2, req1: 1) 207 | }", 208 | TEST_SCHEMA, 209 | &mut plan, 210 | ); 211 | 212 | assert_eq!(get_messages(&errors).len(), 0); 213 | } 214 | 215 | #[test] 216 | fn no_args_on_optional_arg() { 217 | use crate::validation::test_utils::*; 218 | 219 | let mut plan = create_plan_from_rule(Box::new(KnownArgumentNames::new())); 220 | let errors = test_operation_with_schema( 221 | "fragment noArgOnOptionalArg on Dog { 222 | isHouseTrained 223 | }", 224 | TEST_SCHEMA, 225 | &mut plan, 226 | ); 227 | 228 | assert_eq!(get_messages(&errors).len(), 0); 229 | } 230 | 231 | #[test] 232 | fn args_are_known_deeply() { 233 | use crate::validation::test_utils::*; 234 | 235 | let mut plan = create_plan_from_rule(Box::new(KnownArgumentNames::new())); 236 | let errors = test_operation_with_schema( 237 | "{ 238 | dog { 239 | doesKnowCommand(dogCommand: SIT) 240 | } 241 | human { 242 | pet { 243 | ... on Dog { 244 | doesKnowCommand(dogCommand: SIT) 245 | } 246 | } 247 | } 248 | }", 249 | TEST_SCHEMA, 250 | &mut plan, 251 | ); 252 | 253 | assert_eq!(get_messages(&errors).len(), 0); 254 | } 255 | 256 | #[test] 257 | fn directive_args_are_known() { 258 | use crate::validation::test_utils::*; 259 | 260 | let mut plan = create_plan_from_rule(Box::new(KnownArgumentNames::new())); 261 | let errors = test_operation_with_schema( 262 | "{ 263 | dog @skip(if: true) 264 | }", 265 | TEST_SCHEMA, 266 | &mut plan, 267 | ); 268 | 269 | assert_eq!(get_messages(&errors).len(), 0); 270 | } 271 | 272 | #[test] 273 | fn field_args_are_invalid() { 274 | use crate::validation::test_utils::*; 275 | 276 | let mut plan = create_plan_from_rule(Box::new(KnownArgumentNames::new())); 277 | let errors = test_operation_with_schema( 278 | "{ 279 | dog @skip(unless: true) 280 | }", 281 | TEST_SCHEMA, 282 | &mut plan, 283 | ); 284 | 285 | let messages = get_messages(&errors); 286 | assert_eq!(messages.len(), 1); 287 | assert_eq!( 288 | messages, 289 | vec!["Unknown argument \"unless\" on directive \"@skip\"."] 290 | ); 291 | } 292 | 293 | #[test] 294 | fn directive_without_args_is_valid() { 295 | use crate::validation::test_utils::*; 296 | 297 | let mut plan = create_plan_from_rule(Box::new(KnownArgumentNames::new())); 298 | let errors = test_operation_with_schema( 299 | " { 300 | dog @onField 301 | }", 302 | TEST_SCHEMA, 303 | &mut plan, 304 | ); 305 | 306 | let messages = get_messages(&errors); 307 | assert_eq!(messages.len(), 0); 308 | } 309 | 310 | #[test] 311 | fn arg_passed_to_directive_without_arg_is_reported() { 312 | use crate::validation::test_utils::*; 313 | 314 | let mut plan = create_plan_from_rule(Box::new(KnownArgumentNames::new())); 315 | let errors = test_operation_with_schema( 316 | " { 317 | dog @onField(if: true) 318 | }", 319 | TEST_SCHEMA, 320 | &mut plan, 321 | ); 322 | 323 | let messages = get_messages(&errors); 324 | assert_eq!(messages.len(), 1); 325 | assert_eq!( 326 | messages, 327 | vec!["Unknown argument \"if\" on directive \"@onField\"."] 328 | ); 329 | } 330 | 331 | #[test] 332 | #[ignore = "Suggestions are not yet supported"] 333 | fn misspelled_directive_args_are_reported() { 334 | use crate::validation::test_utils::*; 335 | 336 | let mut plan = create_plan_from_rule(Box::new(KnownArgumentNames::new())); 337 | let errors = test_operation_with_schema( 338 | "{ 339 | dog @skip(iff: true) 340 | }", 341 | TEST_SCHEMA, 342 | &mut plan, 343 | ); 344 | 345 | let messages = get_messages(&errors); 346 | assert_eq!(messages.len(), 1); 347 | assert_eq!( 348 | messages, 349 | vec!["Unknown argument \"iff\" on directive \"@onField\". Did you mean \"if\"?"] 350 | ); 351 | } 352 | 353 | #[test] 354 | fn invalid_arg_name() { 355 | use crate::validation::test_utils::*; 356 | 357 | let mut plan = create_plan_from_rule(Box::new(KnownArgumentNames::new())); 358 | let errors = test_operation_with_schema( 359 | "fragment invalidArgName on Dog { 360 | doesKnowCommand(unknown: true) 361 | }", 362 | TEST_SCHEMA, 363 | &mut plan, 364 | ); 365 | 366 | let messages = get_messages(&errors); 367 | assert_eq!(messages.len(), 1); 368 | assert_eq!( 369 | messages, 370 | vec!["Unknown argument \"unknown\" on field \"Dog.doesKnowCommand\"."] 371 | ); 372 | } 373 | 374 | #[test] 375 | #[ignore = "Suggestions are not yet supported"] 376 | fn misspelled_arg_name_is_reported() { 377 | use crate::validation::test_utils::*; 378 | 379 | let mut plan = create_plan_from_rule(Box::new(KnownArgumentNames::new())); 380 | let errors = test_operation_with_schema( 381 | "fragment invalidArgName on Dog { 382 | doesKnowCommand(DogCommand: true) 383 | }", 384 | TEST_SCHEMA, 385 | &mut plan, 386 | ); 387 | 388 | let messages = get_messages(&errors); 389 | assert_eq!(messages.len(), 1); 390 | assert_eq!( 391 | messages, 392 | vec!["Unknown argument \"DogCommand\" on field \"Dog.doesKnowCommand\". Did you mean \"dogCommand\"?"] 393 | ); 394 | } 395 | 396 | #[test] 397 | fn unknown_args_amongst_known_args() { 398 | use crate::validation::test_utils::*; 399 | 400 | let mut plan = create_plan_from_rule(Box::new(KnownArgumentNames::new())); 401 | let errors = test_operation_with_schema( 402 | "fragment oneGoodArgOneInvalidArg on Dog { 403 | doesKnowCommand(whoKnows: 1, dogCommand: SIT, unknown: true) 404 | }", 405 | TEST_SCHEMA, 406 | &mut plan, 407 | ); 408 | 409 | let messages = get_messages(&errors); 410 | assert_eq!(messages.len(), 2); 411 | assert_eq!( 412 | messages, 413 | vec![ 414 | "Unknown argument \"whoKnows\" on field \"Dog.doesKnowCommand\".", 415 | "Unknown argument \"unknown\" on field \"Dog.doesKnowCommand\"." 416 | ] 417 | ); 418 | } 419 | 420 | #[test] 421 | fn unknown_args_deeply() { 422 | use crate::validation::test_utils::*; 423 | 424 | let mut plan = create_plan_from_rule(Box::new(KnownArgumentNames::new())); 425 | let errors = test_operation_with_schema( 426 | "{ 427 | dog { 428 | doesKnowCommand(unknown: true) 429 | } 430 | human { 431 | pet { 432 | ... on Dog { 433 | doesKnowCommand(unknown: true) 434 | } 435 | } 436 | } 437 | }", 438 | TEST_SCHEMA, 439 | &mut plan, 440 | ); 441 | 442 | let messages = get_messages(&errors); 443 | assert_eq!(messages.len(), 2); 444 | assert_eq!( 445 | messages, 446 | vec![ 447 | "Unknown argument \"unknown\" on field \"Dog.doesKnowCommand\".", 448 | "Unknown argument \"unknown\" on field \"Dog.doesKnowCommand\"." 449 | ] 450 | ); 451 | } 452 | -------------------------------------------------------------------------------- /src/validation/rules/known_directives.rs: -------------------------------------------------------------------------------- 1 | use super::ValidationRule; 2 | use crate::ast::{visit_document, OperationVisitor, OperationVisitorContext}; 3 | use crate::static_graphql::query::{ 4 | Directive, Field, FragmentDefinition, InlineFragment, OperationDefinition, 5 | }; 6 | use crate::static_graphql::schema::DirectiveLocation; 7 | use crate::validation::utils::{ValidationError, ValidationErrorContext}; 8 | 9 | /// Known Directives 10 | /// 11 | /// A GraphQL document is only valid if all `@directives` are known by the 12 | /// schema and legally positioned. 13 | /// 14 | /// See https://spec.graphql.org/draft/#sec-Directives-Are-Defined 15 | pub struct KnownDirectives { 16 | recent_location: Option, 17 | } 18 | 19 | impl Default for KnownDirectives { 20 | fn default() -> Self { 21 | Self::new() 22 | } 23 | } 24 | 25 | impl KnownDirectives { 26 | pub fn new() -> Self { 27 | KnownDirectives { 28 | recent_location: None, 29 | } 30 | } 31 | } 32 | 33 | impl<'a> OperationVisitor<'a, ValidationErrorContext> for KnownDirectives { 34 | fn enter_operation_definition( 35 | &mut self, 36 | _: &mut OperationVisitorContext<'a>, 37 | _: &mut ValidationErrorContext, 38 | operation_definition: &crate::static_graphql::query::OperationDefinition, 39 | ) { 40 | self.recent_location = Some(match operation_definition { 41 | OperationDefinition::Mutation(_) => DirectiveLocation::Mutation, 42 | OperationDefinition::Query(_) => DirectiveLocation::Query, 43 | OperationDefinition::SelectionSet(_) => DirectiveLocation::Query, 44 | OperationDefinition::Subscription(_) => DirectiveLocation::Subscription, 45 | }) 46 | } 47 | 48 | fn leave_operation_definition( 49 | &mut self, 50 | _: &mut OperationVisitorContext<'a>, 51 | _: &mut ValidationErrorContext, 52 | _: &OperationDefinition, 53 | ) { 54 | self.recent_location = None; 55 | } 56 | 57 | fn enter_field( 58 | &mut self, 59 | _: &mut OperationVisitorContext<'a>, 60 | _: &mut ValidationErrorContext, 61 | _: &Field, 62 | ) { 63 | self.recent_location = Some(DirectiveLocation::Field); 64 | } 65 | 66 | fn leave_field( 67 | &mut self, 68 | _: &mut OperationVisitorContext<'a>, 69 | _: &mut ValidationErrorContext, 70 | _: &Field, 71 | ) { 72 | self.recent_location = None; 73 | } 74 | 75 | fn enter_fragment_definition( 76 | &mut self, 77 | _: &mut OperationVisitorContext<'a>, 78 | _: &mut ValidationErrorContext, 79 | _: &FragmentDefinition, 80 | ) { 81 | self.recent_location = Some(DirectiveLocation::FragmentDefinition); 82 | } 83 | 84 | fn leave_fragment_definition( 85 | &mut self, 86 | _: &mut OperationVisitorContext<'a>, 87 | _: &mut ValidationErrorContext, 88 | _: &FragmentDefinition, 89 | ) { 90 | self.recent_location = None; 91 | } 92 | 93 | fn enter_fragment_spread( 94 | &mut self, 95 | _: &mut OperationVisitorContext<'a>, 96 | _: &mut ValidationErrorContext, 97 | _: &crate::static_graphql::query::FragmentSpread, 98 | ) { 99 | self.recent_location = Some(DirectiveLocation::FragmentSpread); 100 | } 101 | 102 | fn leave_fragment_spread( 103 | &mut self, 104 | _: &mut OperationVisitorContext<'a>, 105 | _: &mut ValidationErrorContext, 106 | _: &crate::static_graphql::query::FragmentSpread, 107 | ) { 108 | self.recent_location = None; 109 | } 110 | 111 | fn enter_inline_fragment( 112 | &mut self, 113 | _: &mut OperationVisitorContext<'a>, 114 | _: &mut ValidationErrorContext, 115 | _: &InlineFragment, 116 | ) { 117 | self.recent_location = Some(DirectiveLocation::InlineFragment); 118 | } 119 | 120 | fn leave_inline_fragment( 121 | &mut self, 122 | _: &mut OperationVisitorContext<'a>, 123 | _: &mut ValidationErrorContext, 124 | _: &InlineFragment, 125 | ) { 126 | self.recent_location = None; 127 | } 128 | 129 | fn enter_directive( 130 | &mut self, 131 | visitor_context: &mut OperationVisitorContext<'a>, 132 | user_context: &mut ValidationErrorContext, 133 | directive: &Directive, 134 | ) { 135 | if let Some(directive_type) = visitor_context.directives.get(&directive.name) { 136 | if let Some(current_location) = &self.recent_location { 137 | if !directive_type 138 | .locations 139 | .iter() 140 | .any(|l| l == current_location) 141 | { 142 | user_context.report_error(ValidationError { 143 | error_code: self.error_code(), 144 | locations: vec![directive.position], 145 | message: format!( 146 | "Directive \"@{}\" may not be used on {}", 147 | directive.name, 148 | current_location.as_str() 149 | ), 150 | }); 151 | } 152 | } 153 | } else { 154 | user_context.report_error(ValidationError { 155 | error_code: self.error_code(), 156 | locations: vec![directive.position], 157 | message: format!("Unknown directive \"@{}\".", directive.name), 158 | }); 159 | } 160 | } 161 | } 162 | 163 | impl ValidationRule for KnownDirectives { 164 | fn error_code<'a>(&self) -> &'a str { 165 | "KnownDirectives" 166 | } 167 | 168 | fn validate( 169 | &self, 170 | ctx: &mut OperationVisitorContext, 171 | error_collector: &mut ValidationErrorContext, 172 | ) { 173 | visit_document( 174 | &mut KnownDirectives::new(), 175 | ctx.operation, 176 | ctx, 177 | error_collector, 178 | ); 179 | } 180 | } 181 | 182 | #[test] 183 | fn no_directives() { 184 | use crate::validation::test_utils::*; 185 | 186 | let mut plan = create_plan_from_rule(Box::new(KnownDirectives::new())); 187 | let errors = test_operation_with_schema( 188 | "query Foo { 189 | name 190 | ...Frag 191 | } 192 | 193 | fragment Frag on Dog { 194 | name 195 | }", 196 | TEST_SCHEMA, 197 | &mut plan, 198 | ); 199 | 200 | assert_eq!(get_messages(&errors).len(), 0); 201 | } 202 | 203 | #[test] 204 | fn standard_directives() { 205 | use crate::validation::test_utils::*; 206 | 207 | let mut plan = create_plan_from_rule(Box::new(KnownDirectives::new())); 208 | let errors = test_operation_with_schema( 209 | "{ 210 | human @skip(if: false) { 211 | name 212 | pets { 213 | ... on Dog @include(if: true) { 214 | name 215 | } 216 | } 217 | } 218 | }", 219 | TEST_SCHEMA, 220 | &mut plan, 221 | ); 222 | 223 | assert_eq!(get_messages(&errors).len(), 0); 224 | } 225 | 226 | #[test] 227 | fn unknown_directive() { 228 | use crate::validation::test_utils::*; 229 | 230 | let mut plan = create_plan_from_rule(Box::new(KnownDirectives::new())); 231 | let errors = test_operation_with_schema( 232 | "{ 233 | human @unknown(directive: \"value\") { 234 | name 235 | } 236 | }", 237 | TEST_SCHEMA, 238 | &mut plan, 239 | ); 240 | 241 | assert_eq!(get_messages(&errors).len(), 1); 242 | } 243 | 244 | #[test] 245 | fn many_unknown_directives() { 246 | use crate::validation::test_utils::*; 247 | 248 | let mut plan = create_plan_from_rule(Box::new(KnownDirectives::new())); 249 | let errors = test_operation_with_schema( 250 | "{ 251 | __typename @unknown 252 | human @unknown { 253 | name 254 | pets @unknown { 255 | name 256 | } 257 | } 258 | }", 259 | TEST_SCHEMA, 260 | &mut plan, 261 | ); 262 | 263 | assert_eq!(get_messages(&errors).len(), 3); 264 | } 265 | 266 | #[test] 267 | fn well_placed_directives() { 268 | use crate::validation::test_utils::*; 269 | 270 | let mut plan = create_plan_from_rule(Box::new(KnownDirectives::new())); 271 | let errors = test_operation_with_schema( 272 | " 273 | # TODO: update once this is released https://github.com/graphql-rust/graphql-parser/issues/60 query ($var: Boolean @onVariableDefinition) 274 | query ($var: Boolean) @onQuery { 275 | human @onField { 276 | ...Frag @onFragmentSpread 277 | ... @onInlineFragment { 278 | name @onField 279 | } 280 | } 281 | } 282 | 283 | mutation @onMutation { 284 | someField @onField 285 | } 286 | 287 | subscription @onSubscription { 288 | someField @onField 289 | } 290 | 291 | fragment Frag on Human @onFragmentDefinition { 292 | name @onField 293 | }", 294 | TEST_SCHEMA, 295 | &mut plan, 296 | ); 297 | 298 | assert_eq!(get_messages(&errors).len(), 0); 299 | } 300 | 301 | #[test] 302 | fn misplaced_directives() { 303 | use crate::validation::test_utils::*; 304 | 305 | let mut plan = create_plan_from_rule(Box::new(KnownDirectives::new())); 306 | let errors = test_operation_with_schema( 307 | " query ($var: Boolean) @onMutation { 308 | human @onQuery { 309 | ...Frag @onQuery 310 | ... @onQuery { 311 | name @onQuery 312 | } 313 | } 314 | } 315 | 316 | mutation @onQuery { 317 | someField @onQuery 318 | } 319 | 320 | subscription @onQuery { 321 | someField @onQuery 322 | } 323 | 324 | fragment Frag on Human @onQuery { 325 | name @onQuery 326 | }", 327 | TEST_SCHEMA, 328 | &mut plan, 329 | ); 330 | 331 | assert_eq!(get_messages(&errors).len(), 11); 332 | } 333 | -------------------------------------------------------------------------------- /src/validation/rules/known_fragment_names.rs: -------------------------------------------------------------------------------- 1 | use super::ValidationRule; 2 | use crate::ast::{visit_document, OperationVisitor, OperationVisitorContext}; 3 | use crate::static_graphql::query::*; 4 | use crate::validation::utils::{ValidationError, ValidationErrorContext}; 5 | 6 | /// Known fragment names 7 | /// 8 | /// A GraphQL document is only valid if all `...Fragment` fragment spreads refer 9 | /// to fragments defined in the same document. 10 | /// 11 | /// See https://spec.graphql.org/draft/#sec-Fragment-spread-target-defined 12 | pub struct KnownFragmentNames; 13 | 14 | impl Default for KnownFragmentNames { 15 | fn default() -> Self { 16 | Self::new() 17 | } 18 | } 19 | 20 | impl KnownFragmentNames { 21 | pub fn new() -> Self { 22 | KnownFragmentNames 23 | } 24 | } 25 | 26 | impl<'a> OperationVisitor<'a, ValidationErrorContext> for KnownFragmentNames { 27 | fn enter_fragment_spread( 28 | &mut self, 29 | visitor_context: &mut OperationVisitorContext, 30 | user_context: &mut ValidationErrorContext, 31 | fragment_spread: &FragmentSpread, 32 | ) { 33 | if !visitor_context 34 | .known_fragments 35 | .contains_key(fragment_spread.fragment_name.as_str()) 36 | { 37 | user_context.report_error(ValidationError { 38 | error_code: self.error_code(), 39 | locations: vec![fragment_spread.position], 40 | message: format!("Unknown fragment \"{}\".", fragment_spread.fragment_name), 41 | }) 42 | } 43 | } 44 | } 45 | 46 | impl ValidationRule for KnownFragmentNames { 47 | fn error_code<'a>(&self) -> &'a str { 48 | "KnownFragmentNames" 49 | } 50 | 51 | fn validate( 52 | &self, 53 | ctx: &mut OperationVisitorContext, 54 | error_collector: &mut ValidationErrorContext, 55 | ) { 56 | visit_document( 57 | &mut KnownFragmentNames::new(), 58 | ctx.operation, 59 | ctx, 60 | error_collector, 61 | ); 62 | } 63 | } 64 | 65 | #[test] 66 | fn valid_fragment() { 67 | use crate::validation::test_utils::*; 68 | 69 | let mut plan = create_plan_from_rule(Box::new(KnownFragmentNames {})); 70 | let errors = test_operation_with_schema( 71 | "{ 72 | human(id: 4) { 73 | ...HumanFields1 74 | ... on Human { 75 | ...HumanFields2 76 | } 77 | ... { 78 | name 79 | } 80 | } 81 | } 82 | fragment HumanFields1 on Human { 83 | name 84 | ...HumanFields3 85 | } 86 | fragment HumanFields2 on Human { 87 | name 88 | } 89 | fragment HumanFields3 on Human { 90 | name 91 | }", 92 | TEST_SCHEMA, 93 | &mut plan, 94 | ); 95 | 96 | assert_eq!(get_messages(&errors).len(), 0); 97 | } 98 | 99 | #[test] 100 | fn invalid_fragment() { 101 | use crate::validation::test_utils::*; 102 | 103 | let mut plan = create_plan_from_rule(Box::new(KnownFragmentNames {})); 104 | let errors = test_operation_with_schema( 105 | "{ 106 | human(id: 4) { 107 | ...UnknownFragment1 108 | ... on Human { 109 | ...UnknownFragment2 110 | } 111 | } 112 | } 113 | fragment HumanFields on Human { 114 | name 115 | ...UnknownFragment3 116 | }", 117 | TEST_SCHEMA, 118 | &mut plan, 119 | ); 120 | 121 | let messages = get_messages(&errors); 122 | assert_eq!(messages.len(), 3); 123 | assert_eq!( 124 | messages, 125 | vec![ 126 | "Unknown fragment \"UnknownFragment1\".", 127 | "Unknown fragment \"UnknownFragment2\".", 128 | "Unknown fragment \"UnknownFragment3\".", 129 | ] 130 | ); 131 | } 132 | -------------------------------------------------------------------------------- /src/validation/rules/known_type_names.rs: -------------------------------------------------------------------------------- 1 | use super::ValidationRule; 2 | use crate::ast::{ 3 | visit_document, OperationVisitor, OperationVisitorContext, SchemaDocumentExtension, 4 | TypeExtension, 5 | }; 6 | use crate::static_graphql::query::TypeCondition; 7 | use crate::validation::utils::{ValidationError, ValidationErrorContext}; 8 | 9 | /// Known type names 10 | /// 11 | /// A GraphQL document is only valid if referenced types (specifically 12 | /// variable definitions and fragment conditions) are defined by the type schema. 13 | /// 14 | /// See https://spec.graphql.org/draft/#sec-Fragment-Spread-Type-Existence 15 | pub struct KnownTypeNames; 16 | 17 | impl Default for KnownTypeNames { 18 | fn default() -> Self { 19 | Self::new() 20 | } 21 | } 22 | 23 | impl KnownTypeNames { 24 | pub fn new() -> Self { 25 | KnownTypeNames 26 | } 27 | } 28 | 29 | impl<'a> OperationVisitor<'a, ValidationErrorContext> for KnownTypeNames { 30 | fn enter_fragment_definition( 31 | &mut self, 32 | visitor_context: &mut crate::ast::OperationVisitorContext, 33 | user_context: &mut ValidationErrorContext, 34 | fragment_definition: &crate::static_graphql::query::FragmentDefinition, 35 | ) { 36 | let TypeCondition::On(fragment_type_name) = &fragment_definition.type_condition; 37 | 38 | if visitor_context.schema.type_by_name(fragment_type_name).is_none() && !fragment_type_name.starts_with("__") { 39 | user_context.report_error(ValidationError { 40 | error_code: self.error_code(), 41 | locations: vec![fragment_definition.position], 42 | message: format!("Unknown type \"{}\".", fragment_type_name), 43 | }); 44 | } 45 | } 46 | 47 | fn enter_inline_fragment( 48 | &mut self, 49 | visitor_context: &mut OperationVisitorContext, 50 | user_context: &mut ValidationErrorContext, 51 | inline_fragment: &crate::static_graphql::query::InlineFragment, 52 | ) { 53 | if let Some(TypeCondition::On(fragment_type_name)) = &inline_fragment.type_condition { 54 | if visitor_context.schema.type_by_name(fragment_type_name).is_none() && !fragment_type_name.starts_with("__") { 55 | user_context.report_error(ValidationError { 56 | error_code: self.error_code(), 57 | locations: vec![inline_fragment.position], 58 | message: format!("Unknown type \"{}\".", fragment_type_name), 59 | }); 60 | } 61 | } 62 | } 63 | 64 | fn enter_variable_definition( 65 | &mut self, 66 | visitor_context: &mut OperationVisitorContext, 67 | user_context: &mut ValidationErrorContext, 68 | variable_definition: &crate::static_graphql::query::VariableDefinition, 69 | ) { 70 | let base_type = variable_definition.var_type.inner_type(); 71 | 72 | if visitor_context.schema.type_by_name(base_type).is_none() && !base_type.starts_with("__") { 73 | user_context.report_error(ValidationError { 74 | error_code: self.error_code(), 75 | locations: vec![variable_definition.position], 76 | message: format!("Unknown type \"{}\".", base_type), 77 | }); 78 | } 79 | } 80 | } 81 | 82 | impl ValidationRule for KnownTypeNames { 83 | fn error_code<'a>(&self) -> &'a str { 84 | "KnownTypeNames" 85 | } 86 | 87 | fn validate( 88 | &self, 89 | ctx: &mut OperationVisitorContext, 90 | error_collector: &mut ValidationErrorContext, 91 | ) { 92 | visit_document( 93 | &mut KnownTypeNames::new(), 94 | ctx.operation, 95 | ctx, 96 | error_collector, 97 | ); 98 | } 99 | } 100 | 101 | #[test] 102 | fn known_type_names_are_valid() { 103 | use crate::validation::test_utils::*; 104 | 105 | let mut plan = create_plan_from_rule(Box::new(KnownTypeNames {})); 106 | let errors = test_operation_with_schema( 107 | " 108 | query Foo( 109 | $var: String 110 | $required: [Int!]! 111 | $introspectionType: __EnumValue 112 | ) { 113 | user(id: 4) { 114 | pets { ... on Pet { name }, ...PetFields, ... { name } } 115 | } 116 | } 117 | fragment PetFields on Pet { 118 | name 119 | }", 120 | TEST_SCHEMA, 121 | &mut plan, 122 | ); 123 | 124 | let messages = get_messages(&errors); 125 | assert_eq!(messages.len(), 0); 126 | } 127 | 128 | #[test] 129 | fn unknown_type_names_are_invalid() { 130 | use crate::validation::test_utils::*; 131 | 132 | let mut plan = create_plan_from_rule(Box::new(KnownTypeNames {})); 133 | let errors = test_operation_with_schema( 134 | "query Foo($var: [JumbledUpLetters!]!) { 135 | user(id: 4) { 136 | name 137 | pets { ... on Badger { name }, ...PetFields } 138 | } 139 | } 140 | 141 | fragment PetFields on Peat { 142 | name 143 | }", 144 | TEST_SCHEMA, 145 | &mut plan, 146 | ); 147 | 148 | let messages = get_messages(&errors); 149 | assert_eq!(messages.len(), 3); 150 | assert_eq!( 151 | messages, 152 | vec![ 153 | "Unknown type \"JumbledUpLetters\".", 154 | "Unknown type \"Badger\".", 155 | "Unknown type \"Peat\"." 156 | ] 157 | ); 158 | } 159 | -------------------------------------------------------------------------------- /src/validation/rules/leaf_field_selections.rs: -------------------------------------------------------------------------------- 1 | use super::ValidationRule; 2 | use crate::{ 3 | ast::{visit_document, OperationVisitor, OperationVisitorContext, TypeDefinitionExtension}, 4 | validation::utils::{ValidationError, ValidationErrorContext}, 5 | }; 6 | 7 | /// Leaf Field Selections 8 | /// 9 | /// Field selections on scalars or enums are never allowed, because they are the leaf nodes of any GraphQL operation. 10 | /// 11 | /// https://spec.graphql.org/draft/#sec-Leaf-Field-Selections 12 | pub struct LeafFieldSelections; 13 | 14 | impl Default for LeafFieldSelections { 15 | fn default() -> Self { 16 | Self::new() 17 | } 18 | } 19 | 20 | impl LeafFieldSelections { 21 | pub fn new() -> Self { 22 | LeafFieldSelections 23 | } 24 | } 25 | 26 | impl<'a> OperationVisitor<'a, ValidationErrorContext> for LeafFieldSelections { 27 | fn enter_field( 28 | &mut self, 29 | visitor_context: &mut OperationVisitorContext, 30 | user_context: &mut ValidationErrorContext, 31 | field: &crate::static_graphql::query::Field, 32 | ) { 33 | if let (Some(field_type), Some(field_type_literal)) = ( 34 | (visitor_context.current_type()), 35 | (visitor_context.current_type_literal()), 36 | ) { 37 | let field_selection_count = field.selection_set.items.len(); 38 | 39 | if field_type.is_leaf_type() { 40 | if field_selection_count > 0 { 41 | user_context.report_error(ValidationError { 42 | error_code: self.error_code(), 43 | locations: vec![field.position], 44 | message: format!( 45 | "Field \"{}\" must not have a selection since type \"{}\" has no subfields.", 46 | field.name, 47 | field_type_literal 48 | ), 49 | }); 50 | } 51 | } else if field_selection_count == 0 { 52 | user_context.report_error(ValidationError {error_code: self.error_code(), 53 | locations: vec![field.position], 54 | message: format!( 55 | "Field \"{}\" of type \"{}\" must have a selection of subfields. Did you mean \"{} {{ ... }}\"?", 56 | field.name, 57 | field_type_literal, 58 | field.name 59 | ), 60 | }); 61 | } 62 | } 63 | } 64 | } 65 | 66 | impl ValidationRule for LeafFieldSelections { 67 | fn error_code<'a>(&self) -> &'a str { 68 | "LeafFieldSelections" 69 | } 70 | 71 | fn validate( 72 | &self, 73 | ctx: &mut OperationVisitorContext, 74 | error_collector: &mut ValidationErrorContext, 75 | ) { 76 | visit_document( 77 | &mut LeafFieldSelections::new(), 78 | ctx.operation, 79 | ctx, 80 | error_collector, 81 | ); 82 | } 83 | } 84 | 85 | #[test] 86 | fn valid_scalar_selection() { 87 | use crate::validation::test_utils::*; 88 | 89 | let mut plan = create_plan_from_rule(Box::new(LeafFieldSelections {})); 90 | let errors = test_operation_with_schema( 91 | "fragment scalarSelection on Dog { 92 | barks 93 | }", 94 | TEST_SCHEMA, 95 | &mut plan, 96 | ); 97 | 98 | assert_eq!(get_messages(&errors).len(), 0); 99 | } 100 | 101 | #[test] 102 | fn object_type_missing_selection() { 103 | use crate::validation::test_utils::*; 104 | 105 | let mut plan = create_plan_from_rule(Box::new(LeafFieldSelections {})); 106 | let errors = test_operation_with_schema( 107 | "query directQueryOnObjectWithoutSubFields { 108 | human 109 | }", 110 | TEST_SCHEMA, 111 | &mut plan, 112 | ); 113 | 114 | let messages = get_messages(&errors); 115 | assert_eq!(messages.len(), 1); 116 | assert_eq!( 117 | messages, 118 | vec!["Field \"human\" of type \"Human\" must have a selection of subfields. Did you mean \"human { ... }\"?"] 119 | ); 120 | } 121 | 122 | #[test] 123 | fn interface_type_missing_selection() { 124 | use crate::validation::test_utils::*; 125 | 126 | let mut plan = create_plan_from_rule(Box::new(LeafFieldSelections {})); 127 | let errors = test_operation_with_schema( 128 | "{ 129 | human { pets } 130 | }", 131 | TEST_SCHEMA, 132 | &mut plan, 133 | ); 134 | 135 | let messages = get_messages(&errors); 136 | assert_eq!(messages.len(), 1); 137 | assert_eq!( 138 | messages, 139 | vec!["Field \"pets\" of type \"[Pet]\" must have a selection of subfields. Did you mean \"pets { ... }\"?"] 140 | ); 141 | } 142 | 143 | #[test] 144 | fn selection_not_allowed_on_scalar() { 145 | use crate::validation::test_utils::*; 146 | 147 | let mut plan = create_plan_from_rule(Box::new(LeafFieldSelections {})); 148 | let errors = test_operation_with_schema( 149 | "fragment scalarSelectionsNotAllowedOnBoolean on Dog { 150 | barks { sinceWhen } 151 | }", 152 | TEST_SCHEMA, 153 | &mut plan, 154 | ); 155 | 156 | let messages = get_messages(&errors); 157 | assert_eq!(messages.len(), 1); 158 | assert_eq!( 159 | messages, 160 | vec!["Field \"barks\" must not have a selection since type \"Boolean\" has no subfields."] 161 | ); 162 | } 163 | 164 | #[test] 165 | fn selection_not_allowed_on_enum() { 166 | use crate::validation::test_utils::*; 167 | 168 | let mut plan = create_plan_from_rule(Box::new(LeafFieldSelections {})); 169 | let errors = test_operation_with_schema( 170 | "fragment scalarSelectionsNotAllowedOnEnum on Cat { 171 | furColor { inHexDec } 172 | }", 173 | TEST_SCHEMA, 174 | &mut plan, 175 | ); 176 | 177 | let messages = get_messages(&errors); 178 | assert_eq!(messages.len(), 1); 179 | assert_eq!( 180 | messages, 181 | vec!["Field \"furColor\" must not have a selection since type \"FurColor\" has no subfields."] 182 | ); 183 | } 184 | 185 | #[test] 186 | fn scalar_selection_not_allowed_with_args() { 187 | use crate::validation::test_utils::*; 188 | 189 | let mut plan = create_plan_from_rule(Box::new(LeafFieldSelections {})); 190 | let errors = test_operation_with_schema( 191 | "fragment scalarSelectionsNotAllowedWithArgs on Dog { 192 | doesKnowCommand(dogCommand: SIT) { sinceWhen } 193 | }", 194 | TEST_SCHEMA, 195 | &mut plan, 196 | ); 197 | 198 | let messages = get_messages(&errors); 199 | assert_eq!(messages.len(), 1); 200 | assert_eq!( 201 | messages, 202 | vec!["Field \"doesKnowCommand\" must not have a selection since type \"Boolean\" has no subfields."] 203 | ); 204 | } 205 | 206 | #[test] 207 | fn scalar_selection_not_allowed_with_directives() { 208 | use crate::validation::test_utils::*; 209 | 210 | let mut plan = create_plan_from_rule(Box::new(LeafFieldSelections {})); 211 | let errors = test_operation_with_schema( 212 | "fragment scalarSelectionsNotAllowedWithDirectives on Dog { 213 | name @include(if: true) { isAlsoHumanName } 214 | }", 215 | TEST_SCHEMA, 216 | &mut plan, 217 | ); 218 | 219 | let messages = get_messages(&errors); 220 | assert_eq!(messages.len(), 1); 221 | assert_eq!( 222 | messages, 223 | vec!["Field \"name\" must not have a selection since type \"String\" has no subfields."] 224 | ); 225 | } 226 | 227 | #[test] 228 | fn scalar_selection_not_allowed_with_directives_and_args() { 229 | use crate::validation::test_utils::*; 230 | 231 | let mut plan = create_plan_from_rule(Box::new(LeafFieldSelections {})); 232 | let errors = test_operation_with_schema( 233 | "fragment scalarSelectionsNotAllowedWithDirectivesAndArgs on Dog { 234 | doesKnowCommand(dogCommand: SIT) @include(if: true) { sinceWhen } 235 | }", 236 | TEST_SCHEMA, 237 | &mut plan, 238 | ); 239 | 240 | let messages = get_messages(&errors); 241 | assert_eq!(messages.len(), 1); 242 | assert_eq!( 243 | messages, 244 | vec!["Field \"doesKnowCommand\" must not have a selection since type \"Boolean\" has no subfields."] 245 | ); 246 | } 247 | -------------------------------------------------------------------------------- /src/validation/rules/lone_anonymous_operation.rs: -------------------------------------------------------------------------------- 1 | use super::ValidationRule; 2 | use crate::ast::{visit_document, OperationVisitor, OperationVisitorContext}; 3 | use crate::static_graphql::query::*; 4 | use crate::validation::utils::{ValidationError, ValidationErrorContext}; 5 | 6 | /// Lone Anonymous Operation 7 | /// 8 | /// A GraphQL document is only valid if when it contains an anonymous operation 9 | /// (the query short-hand) that it contains only that one operation definition. 10 | /// 11 | /// https://spec.graphql.org/draft/#sec-Lone-Anonymous-Operation 12 | pub struct LoneAnonymousOperation; 13 | 14 | impl Default for LoneAnonymousOperation { 15 | fn default() -> Self { 16 | Self::new() 17 | } 18 | } 19 | 20 | impl LoneAnonymousOperation { 21 | pub fn new() -> Self { 22 | LoneAnonymousOperation 23 | } 24 | } 25 | 26 | impl<'a> OperationVisitor<'a, ValidationErrorContext> for LoneAnonymousOperation { 27 | fn enter_document( 28 | &mut self, 29 | _: &mut OperationVisitorContext, 30 | user_context: &mut ValidationErrorContext, 31 | document: &Document, 32 | ) { 33 | let operations_count = document 34 | .definitions 35 | .iter() 36 | .filter(|n| match n { 37 | Definition::Operation(OperationDefinition::SelectionSet(_)) => true, 38 | Definition::Operation(OperationDefinition::Query(_)) => true, 39 | Definition::Operation(OperationDefinition::Mutation(_)) => true, 40 | Definition::Operation(OperationDefinition::Subscription(_)) => true, 41 | _ => false, 42 | }) 43 | .count(); 44 | 45 | for definition in &document.definitions { 46 | match definition { 47 | Definition::Operation(OperationDefinition::SelectionSet(_)) => { 48 | if operations_count > 1 { 49 | user_context.report_error(ValidationError { 50 | error_code: self.error_code(), 51 | message: "This anonymous operation must be the only defined operation." 52 | .to_string(), 53 | locations: vec![], 54 | }) 55 | } 56 | } 57 | Definition::Operation(OperationDefinition::Query(query)) => { 58 | if query.name.is_none() && operations_count > 1 { 59 | user_context.report_error(ValidationError { 60 | error_code: self.error_code(), 61 | message: "This anonymous operation must be the only defined operation." 62 | .to_string(), 63 | locations: vec![query.position], 64 | }) 65 | } 66 | } 67 | Definition::Operation(OperationDefinition::Mutation(mutation)) => { 68 | if mutation.name.is_none() && operations_count > 1 { 69 | user_context.report_error(ValidationError { 70 | error_code: self.error_code(), 71 | message: "This anonymous operation must be the only defined operation." 72 | .to_string(), 73 | locations: vec![mutation.position], 74 | }) 75 | } 76 | } 77 | Definition::Operation(OperationDefinition::Subscription(subscription)) => { 78 | if subscription.name.is_none() && operations_count > 1 { 79 | user_context.report_error(ValidationError { 80 | error_code: self.error_code(), 81 | message: "This anonymous operation must be the only defined operation." 82 | .to_string(), 83 | locations: vec![subscription.position], 84 | }) 85 | } 86 | } 87 | _ => {} 88 | }; 89 | } 90 | } 91 | } 92 | 93 | impl ValidationRule for LoneAnonymousOperation { 94 | fn error_code<'a>(&self) -> &'a str { 95 | "LoneAnonymousOperation" 96 | } 97 | 98 | fn validate( 99 | &self, 100 | ctx: &mut OperationVisitorContext, 101 | error_collector: &mut ValidationErrorContext, 102 | ) { 103 | visit_document( 104 | &mut LoneAnonymousOperation::new(), 105 | ctx.operation, 106 | ctx, 107 | error_collector, 108 | ); 109 | } 110 | } 111 | 112 | #[test] 113 | fn no_operations() { 114 | use crate::validation::test_utils::*; 115 | 116 | let mut plan = create_plan_from_rule(Box::new(LoneAnonymousOperation {})); 117 | let errors = test_operation_with_schema( 118 | "fragment fragA on Type { 119 | field 120 | }", 121 | TEST_SCHEMA, 122 | &mut plan, 123 | ); 124 | 125 | assert_eq!(get_messages(&errors).len(), 0); 126 | } 127 | 128 | #[test] 129 | fn one_anon_operation() { 130 | use crate::validation::test_utils::*; 131 | 132 | let mut plan = create_plan_from_rule(Box::new(LoneAnonymousOperation {})); 133 | let errors = test_operation_with_schema( 134 | "{ 135 | field 136 | }", 137 | TEST_SCHEMA, 138 | &mut plan, 139 | ); 140 | 141 | assert_eq!(get_messages(&errors).len(), 0); 142 | } 143 | 144 | #[test] 145 | fn mutiple_named() { 146 | use crate::validation::test_utils::*; 147 | 148 | let mut plan = create_plan_from_rule(Box::new(LoneAnonymousOperation {})); 149 | let errors = test_operation_with_schema( 150 | "query Foo { 151 | field 152 | } 153 | query Bar { 154 | field 155 | }", 156 | TEST_SCHEMA, 157 | &mut plan, 158 | ); 159 | 160 | assert_eq!(get_messages(&errors).len(), 0); 161 | } 162 | 163 | #[test] 164 | fn anon_operation_with_fragment() { 165 | use crate::validation::test_utils::*; 166 | 167 | let mut plan = create_plan_from_rule(Box::new(LoneAnonymousOperation {})); 168 | let errors = test_operation_with_schema( 169 | "{ 170 | ...Foo 171 | } 172 | fragment Foo on Type { 173 | field 174 | }", 175 | TEST_SCHEMA, 176 | &mut plan, 177 | ); 178 | 179 | assert_eq!(get_messages(&errors).len(), 0); 180 | } 181 | 182 | #[test] 183 | fn multiple_anon_operations() { 184 | use crate::validation::test_utils::*; 185 | 186 | let mut plan = create_plan_from_rule(Box::new(LoneAnonymousOperation {})); 187 | let errors = test_operation_with_schema( 188 | "{ 189 | fieldA 190 | } 191 | { 192 | fieldB 193 | }", 194 | TEST_SCHEMA, 195 | &mut plan, 196 | ); 197 | 198 | let messages = get_messages(&errors); 199 | assert_eq!(messages.len(), 2); 200 | assert_eq!( 201 | messages, 202 | vec![ 203 | "This anonymous operation must be the only defined operation.", 204 | "This anonymous operation must be the only defined operation." 205 | ] 206 | ); 207 | } 208 | 209 | #[test] 210 | fn anon_operation_with_mutation() { 211 | use crate::validation::test_utils::*; 212 | 213 | let mut plan = create_plan_from_rule(Box::new(LoneAnonymousOperation {})); 214 | let errors = test_operation_with_schema( 215 | "{ 216 | fieldA 217 | } 218 | mutation Foo { 219 | fieldB 220 | }", 221 | TEST_SCHEMA, 222 | &mut plan, 223 | ); 224 | 225 | let messages = get_messages(&errors); 226 | assert_eq!(messages.len(), 1); 227 | assert_eq!( 228 | messages, 229 | vec!["This anonymous operation must be the only defined operation."] 230 | ); 231 | } 232 | 233 | #[test] 234 | fn anon_operation_with_subscription() { 235 | use crate::validation::test_utils::*; 236 | 237 | let mut plan = create_plan_from_rule(Box::new(LoneAnonymousOperation {})); 238 | let errors = test_operation_with_schema( 239 | "{ 240 | fieldA 241 | } 242 | subscription Foo { 243 | fieldB 244 | }", 245 | TEST_SCHEMA, 246 | &mut plan, 247 | ); 248 | 249 | let messages = get_messages(&errors); 250 | assert_eq!(messages.len(), 1); 251 | assert_eq!( 252 | messages, 253 | vec!["This anonymous operation must be the only defined operation."] 254 | ); 255 | } 256 | -------------------------------------------------------------------------------- /src/validation/rules/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod defaults; 2 | pub mod rule; 3 | 4 | pub mod fields_on_correct_type; 5 | pub mod fragments_on_composite_types; 6 | pub mod known_argument_names; 7 | pub mod known_directives; 8 | pub mod known_fragment_names; 9 | pub mod known_type_names; 10 | pub mod leaf_field_selections; 11 | pub mod lone_anonymous_operation; 12 | pub mod no_fragments_cycle; 13 | pub mod no_undefined_variables; 14 | pub mod no_unused_fragments; 15 | pub mod no_unused_variables; 16 | pub mod overlapping_fields_can_be_merged; 17 | pub mod possible_fragment_spreads; 18 | pub mod provided_required_arguments; 19 | pub mod single_field_subscriptions; 20 | pub mod unique_argument_names; 21 | pub mod unique_directives_per_location; 22 | pub mod unique_fragment_names; 23 | pub mod unique_operation_names; 24 | pub mod unique_variable_names; 25 | pub mod values_of_correct_type; 26 | pub mod variables_are_input_types; 27 | pub mod variables_in_allowed_position; 28 | 29 | pub use self::defaults::*; 30 | pub use self::rule::*; 31 | 32 | pub use self::fields_on_correct_type::*; 33 | pub use self::fragments_on_composite_types::*; 34 | pub use self::known_argument_names::*; 35 | pub use self::known_directives::*; 36 | pub use self::known_fragment_names::*; 37 | pub use self::known_type_names::*; 38 | pub use self::leaf_field_selections::*; 39 | pub use self::lone_anonymous_operation::*; 40 | pub use self::no_fragments_cycle::*; 41 | pub use self::no_undefined_variables::*; 42 | pub use self::no_unused_fragments::*; 43 | pub use self::no_unused_variables::*; 44 | pub use self::overlapping_fields_can_be_merged::*; 45 | pub use self::possible_fragment_spreads::*; 46 | pub use self::provided_required_arguments::*; 47 | pub use self::single_field_subscriptions::*; 48 | pub use self::unique_argument_names::*; 49 | pub use self::unique_directives_per_location::*; 50 | pub use self::unique_fragment_names::*; 51 | pub use self::unique_operation_names::*; 52 | pub use self::unique_variable_names::*; 53 | pub use self::values_of_correct_type::*; 54 | pub use self::variables_are_input_types::*; 55 | pub use self::variables_in_allowed_position::*; 56 | -------------------------------------------------------------------------------- /src/validation/rules/no_fragments_cycle.rs: -------------------------------------------------------------------------------- 1 | use super::ValidationRule; 2 | use crate::ast::ext::{AstNodeWithName, FragmentSpreadExtraction}; 3 | use crate::ast::{visit_document, OperationVisitor, OperationVisitorContext}; 4 | use crate::static_graphql::query::{FragmentDefinition, FragmentSpread}; 5 | use crate::validation::utils::{ValidationError, ValidationErrorContext}; 6 | use std::collections::{HashMap, HashSet}; 7 | 8 | /// No fragment cycles 9 | /// 10 | /// The graph of fragment spreads must not form any cycles including spreading itself. 11 | /// Otherwise an operation could infinitely spread or infinitely execute on cycles in the underlying data. 12 | /// 13 | /// https://spec.graphql.org/draft/#sec-Fragment-spreads-must-not-form-cycles 14 | pub struct NoFragmentsCycle { 15 | visited_fragments: HashSet, 16 | } 17 | 18 | impl Default for NoFragmentsCycle { 19 | fn default() -> Self { 20 | Self::new() 21 | } 22 | } 23 | 24 | impl NoFragmentsCycle { 25 | pub fn new() -> Self { 26 | Self { 27 | visited_fragments: HashSet::new(), 28 | } 29 | } 30 | 31 | /// This does a straight-forward DFS to find cycles. 32 | /// It does not terminate when a cycle was found but continues to explore 33 | /// the graph to find all possible cycles. 34 | fn detect_cycles<'a>( 35 | &mut self, 36 | fragment: &'a FragmentDefinition, 37 | spread_paths: &mut Vec<&'a FragmentSpread>, 38 | spread_path_index_by_name: &mut HashMap, 39 | known_fragments: &'a HashMap<&'a str, &'a FragmentDefinition>, 40 | error_context: &mut ValidationErrorContext, 41 | ) { 42 | if self.visited_fragments.contains(&fragment.name) { 43 | return; 44 | } 45 | 46 | self.visited_fragments.insert(fragment.name.clone()); 47 | 48 | let spread_nodes = fragment.selection_set.get_recursive_fragment_spreads(); 49 | 50 | if spread_nodes.is_empty() { 51 | return; 52 | } 53 | 54 | spread_path_index_by_name.insert(fragment.name.clone(), spread_paths.len()); 55 | 56 | for spread_node in spread_nodes { 57 | let spread_name = spread_node.fragment_name.clone(); 58 | spread_paths.push(spread_node); 59 | 60 | match spread_path_index_by_name.get(&spread_name) { 61 | None => { 62 | if let Some(spread_def) = known_fragments.get(spread_name.as_str()) { 63 | self.detect_cycles( 64 | spread_def, 65 | spread_paths, 66 | spread_path_index_by_name, 67 | known_fragments, 68 | error_context, 69 | ); 70 | } 71 | } 72 | Some(cycle_index) => { 73 | let cycle_path = &spread_paths[*cycle_index..]; 74 | let via_path = match cycle_path.len() { 75 | 0 => vec![], 76 | _ => cycle_path[0..cycle_path.len() - 1] 77 | .iter() 78 | .map(|s| format!("\"{}\"", s.node_name().unwrap())) 79 | .collect::>(), 80 | }; 81 | 82 | error_context.report_error(ValidationError { 83 | error_code: self.error_code(), 84 | locations: cycle_path.iter().map(|f| f.position).collect(), 85 | message: match via_path.len() { 86 | 0 => { 87 | format!("Cannot spread fragment \"{}\" within itself.", spread_name) 88 | } 89 | _ => format!( 90 | "Cannot spread fragment \"{}\" within itself via {}.", 91 | spread_name, 92 | via_path.join(", ") 93 | ), 94 | }, 95 | }) 96 | } 97 | } 98 | 99 | spread_paths.pop(); 100 | } 101 | 102 | spread_path_index_by_name.remove(&fragment.name); 103 | } 104 | } 105 | 106 | impl<'a> OperationVisitor<'a, ValidationErrorContext> for NoFragmentsCycle { 107 | fn enter_fragment_definition( 108 | &mut self, 109 | visitor_context: &mut OperationVisitorContext, 110 | user_context: &mut ValidationErrorContext, 111 | fragment: &FragmentDefinition, 112 | ) { 113 | let mut spread_paths: Vec<&FragmentSpread> = vec![]; 114 | let mut spread_path_index_by_name: HashMap = HashMap::new(); 115 | 116 | self.detect_cycles( 117 | fragment, 118 | &mut spread_paths, 119 | &mut spread_path_index_by_name, 120 | &visitor_context.known_fragments, 121 | user_context, 122 | ); 123 | } 124 | } 125 | 126 | impl ValidationRule for NoFragmentsCycle { 127 | fn error_code<'a>(&self) -> &'a str { 128 | "NoFragmentsCycle" 129 | } 130 | 131 | fn validate( 132 | &self, 133 | ctx: &mut OperationVisitorContext, 134 | error_collector: &mut ValidationErrorContext, 135 | ) { 136 | visit_document( 137 | &mut NoFragmentsCycle::new(), 138 | ctx.operation, 139 | ctx, 140 | error_collector, 141 | ); 142 | } 143 | } 144 | 145 | #[test] 146 | fn single_reference_is_valid() { 147 | use crate::validation::test_utils::*; 148 | 149 | let mut plan = create_plan_from_rule(Box::new(NoFragmentsCycle::new())); 150 | let errors = test_operation_with_schema( 151 | "fragment fragA on Dog { ...fragB } 152 | fragment fragB on Dog { name }", 153 | TEST_SCHEMA, 154 | &mut plan, 155 | ); 156 | 157 | let mes = get_messages(&errors).len(); 158 | assert_eq!(mes, 0); 159 | } 160 | 161 | #[test] 162 | fn spreading_twice_is_not_circular() { 163 | use crate::validation::test_utils::*; 164 | 165 | let mut plan = create_plan_from_rule(Box::new(NoFragmentsCycle::new())); 166 | let errors = test_operation_with_schema( 167 | "fragment fragA on Dog { ...fragB, ...fragB } 168 | fragment fragB on Dog { name }", 169 | TEST_SCHEMA, 170 | &mut plan, 171 | ); 172 | 173 | let mes = get_messages(&errors).len(); 174 | assert_eq!(mes, 0); 175 | } 176 | 177 | #[test] 178 | fn spreading_twice_indirectly_is_not_circular() { 179 | use crate::validation::test_utils::*; 180 | 181 | let mut plan = create_plan_from_rule(Box::new(NoFragmentsCycle::new())); 182 | let errors = test_operation_with_schema( 183 | "fragment fragA on Dog { ...fragB, ...fragC } 184 | fragment fragB on Dog { ...fragC } 185 | fragment fragC on Dog { name }", 186 | TEST_SCHEMA, 187 | &mut plan, 188 | ); 189 | 190 | let mes = get_messages(&errors).len(); 191 | assert_eq!(mes, 0); 192 | } 193 | 194 | #[test] 195 | fn double_spread_within_abstract_types() { 196 | use crate::validation::test_utils::*; 197 | 198 | let mut plan = create_plan_from_rule(Box::new(NoFragmentsCycle::new())); 199 | let errors = test_operation_with_schema( 200 | "fragment nameFragment on Pet { 201 | ... on Dog { name } 202 | ... on Cat { name } 203 | } 204 | 205 | fragment spreadsInAnon on Pet { 206 | ... on Dog { ...nameFragment } 207 | ... on Cat { ...nameFragment } 208 | }", 209 | TEST_SCHEMA, 210 | &mut plan, 211 | ); 212 | 213 | let mes = get_messages(&errors).len(); 214 | assert_eq!(mes, 0); 215 | } 216 | 217 | #[test] 218 | fn does_not_false_positive_on_unknown_fragment() { 219 | use crate::validation::test_utils::*; 220 | 221 | let mut plan = create_plan_from_rule(Box::new(NoFragmentsCycle::new())); 222 | let errors = test_operation_with_schema( 223 | "fragment nameFragment on Pet { 224 | ...UnknownFragment 225 | }", 226 | TEST_SCHEMA, 227 | &mut plan, 228 | ); 229 | 230 | let mes = get_messages(&errors).len(); 231 | assert_eq!(mes, 0); 232 | } 233 | 234 | #[test] 235 | fn spreading_recursively_within_field_fails() { 236 | use crate::validation::test_utils::*; 237 | 238 | let mut plan = create_plan_from_rule(Box::new(NoFragmentsCycle::new())); 239 | let errors = test_operation_with_schema( 240 | "fragment fragA on Human { relatives { ...fragA } }", 241 | TEST_SCHEMA, 242 | &mut plan, 243 | ); 244 | 245 | let mes = get_messages(&errors); 246 | assert_eq!(mes.len(), 1); 247 | assert_eq!(mes, vec!["Cannot spread fragment \"fragA\" within itself."]); 248 | } 249 | 250 | #[test] 251 | fn no_spreading_itself_directly() { 252 | use crate::validation::test_utils::*; 253 | 254 | let mut plan = create_plan_from_rule(Box::new(NoFragmentsCycle::new())); 255 | let errors = test_operation_with_schema( 256 | " 257 | fragment fragA on Dog { ...fragA }", 258 | TEST_SCHEMA, 259 | &mut plan, 260 | ); 261 | 262 | let mes = get_messages(&errors); 263 | assert_eq!(mes.len(), 1); 264 | assert_eq!(mes, vec!["Cannot spread fragment \"fragA\" within itself."]); 265 | } 266 | 267 | #[test] 268 | fn no_spreading_itself_directly_within_inline_fragment() { 269 | use crate::validation::test_utils::*; 270 | 271 | let mut plan = create_plan_from_rule(Box::new(NoFragmentsCycle::new())); 272 | let errors = test_operation_with_schema( 273 | "fragment fragA on Pet { 274 | ... on Dog { 275 | ...fragA 276 | } 277 | }", 278 | TEST_SCHEMA, 279 | &mut plan, 280 | ); 281 | 282 | let mes = get_messages(&errors); 283 | assert_eq!(mes.len(), 1); 284 | assert_eq!(mes, vec!["Cannot spread fragment \"fragA\" within itself."]); 285 | } 286 | 287 | #[test] 288 | fn no_spreading_itself_indirectly() { 289 | use crate::validation::test_utils::*; 290 | 291 | let mut plan = create_plan_from_rule(Box::new(NoFragmentsCycle::new())); 292 | let errors = test_operation_with_schema( 293 | "fragment fragA on Dog { ...fragB } 294 | fragment fragB on Dog { ...fragA }", 295 | TEST_SCHEMA, 296 | &mut plan, 297 | ); 298 | 299 | let mes = get_messages(&errors); 300 | assert_eq!(mes.len(), 1); 301 | assert_eq!( 302 | mes, 303 | vec!["Cannot spread fragment \"fragA\" within itself via \"fragB\"."] 304 | ); 305 | } 306 | 307 | #[test] 308 | fn no_spreading_itself_indirectly_reports_opposite_order() { 309 | use crate::validation::test_utils::*; 310 | 311 | let mut plan = create_plan_from_rule(Box::new(NoFragmentsCycle::new())); 312 | let errors = test_operation_with_schema( 313 | "fragment fragB on Dog { ...fragA } 314 | fragment fragA on Dog { ...fragB }", 315 | TEST_SCHEMA, 316 | &mut plan, 317 | ); 318 | 319 | let mes = get_messages(&errors); 320 | assert_eq!(mes.len(), 1); 321 | assert_eq!( 322 | mes, 323 | vec!["Cannot spread fragment \"fragB\" within itself via \"fragA\"."] 324 | ); 325 | } 326 | 327 | #[test] 328 | fn no_spreading_itself_indirectly_within_inline_fragment() { 329 | use crate::validation::test_utils::*; 330 | 331 | let mut plan = create_plan_from_rule(Box::new(NoFragmentsCycle::new())); 332 | let errors = test_operation_with_schema( 333 | "fragment fragA on Pet { 334 | ... on Dog { 335 | ...fragB 336 | } 337 | } 338 | fragment fragB on Pet { 339 | ... on Dog { 340 | ...fragA 341 | } 342 | }", 343 | TEST_SCHEMA, 344 | &mut plan, 345 | ); 346 | 347 | let mes = get_messages(&errors); 348 | assert_eq!(mes.len(), 1); 349 | assert_eq!( 350 | mes, 351 | vec!["Cannot spread fragment \"fragA\" within itself via \"fragB\"."] 352 | ); 353 | } 354 | 355 | #[test] 356 | fn no_spreading_itself_deeply() { 357 | use crate::validation::test_utils::*; 358 | 359 | let mut plan = create_plan_from_rule(Box::new(NoFragmentsCycle::new())); 360 | let errors = test_operation_with_schema( 361 | "fragment fragA on Dog { ...fragB } 362 | fragment fragB on Dog { ...fragC } 363 | fragment fragC on Dog { ...fragO } 364 | fragment fragX on Dog { ...fragY } 365 | fragment fragY on Dog { ...fragZ } 366 | fragment fragZ on Dog { ...fragO } 367 | fragment fragO on Dog { ...fragP } 368 | fragment fragP on Dog { ...fragA, ...fragX }", 369 | TEST_SCHEMA, 370 | &mut plan, 371 | ); 372 | 373 | let mes = get_messages(&errors); 374 | assert_eq!(mes.len(), 2); 375 | assert_eq!( 376 | mes, 377 | vec![ 378 | "Cannot spread fragment \"fragA\" within itself via \"fragB\", \"fragC\", \"fragO\", \"fragP\".", 379 | "Cannot spread fragment \"fragO\" within itself via \"fragP\", \"fragX\", \"fragY\", \"fragZ\".", 380 | ] 381 | ); 382 | } 383 | 384 | #[test] 385 | fn no_spreading_itself_deeply_two_paths() { 386 | use crate::validation::test_utils::*; 387 | 388 | let mut plan = create_plan_from_rule(Box::new(NoFragmentsCycle::new())); 389 | let errors = test_operation_with_schema( 390 | "fragment fragA on Dog { ...fragB, ...fragC } 391 | fragment fragB on Dog { ...fragA } 392 | fragment fragC on Dog { ...fragA }", 393 | TEST_SCHEMA, 394 | &mut plan, 395 | ); 396 | 397 | let mes = get_messages(&errors); 398 | assert_eq!(mes.len(), 2); 399 | assert_eq!( 400 | mes, 401 | vec![ 402 | "Cannot spread fragment \"fragA\" within itself via \"fragB\".", 403 | "Cannot spread fragment \"fragA\" within itself via \"fragC\".", 404 | ] 405 | ); 406 | } 407 | 408 | #[test] 409 | fn no_spreading_itself_deeply_two_paths_alt_traverse_order() { 410 | use crate::validation::test_utils::*; 411 | 412 | let mut plan = create_plan_from_rule(Box::new(NoFragmentsCycle::new())); 413 | let errors = test_operation_with_schema( 414 | " 415 | fragment fragA on Dog { ...fragC } 416 | fragment fragB on Dog { ...fragC } 417 | fragment fragC on Dog { ...fragA, ...fragB } 418 | ", 419 | TEST_SCHEMA, 420 | &mut plan, 421 | ); 422 | 423 | let mes = get_messages(&errors); 424 | assert_eq!(mes.len(), 2); 425 | assert_eq!( 426 | mes, 427 | vec![ 428 | "Cannot spread fragment \"fragA\" within itself via \"fragC\".", 429 | "Cannot spread fragment \"fragC\" within itself via \"fragB\".", 430 | ] 431 | ); 432 | } 433 | 434 | #[test] 435 | fn no_spreading_itself_deeply_and_immediately() { 436 | use crate::validation::test_utils::*; 437 | 438 | let mut plan = create_plan_from_rule(Box::new(NoFragmentsCycle::new())); 439 | let errors = test_operation_with_schema( 440 | " 441 | fragment fragA on Dog { ...fragB } 442 | fragment fragB on Dog { ...fragB, ...fragC } 443 | fragment fragC on Dog { ...fragA, ...fragB } 444 | ", 445 | TEST_SCHEMA, 446 | &mut plan, 447 | ); 448 | 449 | let mes = get_messages(&errors); 450 | assert_eq!(mes.len(), 3); 451 | assert_eq!( 452 | mes, 453 | vec![ 454 | "Cannot spread fragment \"fragB\" within itself.", 455 | "Cannot spread fragment \"fragA\" within itself via \"fragB\", \"fragC\".", 456 | "Cannot spread fragment \"fragB\" within itself via \"fragC\".", 457 | ] 458 | ); 459 | } 460 | -------------------------------------------------------------------------------- /src/validation/rules/no_unused_fragments.rs: -------------------------------------------------------------------------------- 1 | use super::ValidationRule; 2 | use crate::ast::{visit_document, OperationVisitor, OperationVisitorContext}; 3 | use crate::static_graphql::query::*; 4 | use crate::validation::utils::{ValidationError, ValidationErrorContext}; 5 | 6 | /// No unused fragments 7 | /// 8 | /// A GraphQL document is only valid if all fragment definitions are spread 9 | /// within operations, or spread within other fragments spread within operations. 10 | /// 11 | /// See https://spec.graphql.org/draft/#sec-Fragments-Must-Be-Used 12 | pub struct NoUnusedFragments<'a> { 13 | fragments_in_use: Vec<&'a str>, 14 | } 15 | 16 | impl<'a> OperationVisitor<'a, ValidationErrorContext> for NoUnusedFragments<'a> { 17 | fn enter_fragment_spread( 18 | &mut self, 19 | _: &mut OperationVisitorContext, 20 | _: &mut ValidationErrorContext, 21 | fragment_spread: &'a FragmentSpread, 22 | ) { 23 | self.fragments_in_use 24 | .push(fragment_spread.fragment_name.as_str()); 25 | } 26 | 27 | fn leave_document( 28 | &mut self, 29 | visitor_context: &mut OperationVisitorContext, 30 | user_context: &mut ValidationErrorContext, 31 | _document: &Document, 32 | ) { 33 | visitor_context 34 | .known_fragments 35 | .iter() 36 | .filter_map(|(fragment_name, _fragment)| { 37 | if !self.fragments_in_use.contains(fragment_name) { 38 | Some(fragment_name) 39 | } else { 40 | None 41 | } 42 | }) 43 | .for_each(|unused_fragment_name| { 44 | user_context.report_error(ValidationError { 45 | error_code: self.error_code(), 46 | locations: vec![], 47 | message: format!("Fragment \"{}\" is never used.", unused_fragment_name), 48 | }); 49 | }); 50 | } 51 | } 52 | 53 | impl<'a> Default for NoUnusedFragments<'a> { 54 | fn default() -> Self { 55 | Self::new() 56 | } 57 | } 58 | 59 | impl<'a> NoUnusedFragments<'a> { 60 | pub fn new() -> Self { 61 | NoUnusedFragments { 62 | fragments_in_use: Vec::new(), 63 | } 64 | } 65 | } 66 | 67 | impl<'n> ValidationRule for NoUnusedFragments<'n> { 68 | fn error_code<'a>(&self) -> &'a str { 69 | "NoUnusedFragments" 70 | } 71 | 72 | fn validate( 73 | &self, 74 | ctx: &mut OperationVisitorContext, 75 | error_collector: &mut ValidationErrorContext, 76 | ) { 77 | visit_document( 78 | &mut NoUnusedFragments::new(), 79 | ctx.operation, 80 | ctx, 81 | error_collector, 82 | ); 83 | } 84 | } 85 | 86 | #[test] 87 | fn all_fragment_names_are_used() { 88 | use crate::validation::test_utils::*; 89 | 90 | let mut plan = create_plan_from_rule(Box::new(NoUnusedFragments::new())); 91 | let errors = test_operation_with_schema( 92 | "{ 93 | human(id: 4) { 94 | ...HumanFields1 95 | ... on Human { 96 | ...HumanFields2 97 | } 98 | } 99 | } 100 | fragment HumanFields1 on Human { 101 | name 102 | ...HumanFields3 103 | } 104 | fragment HumanFields2 on Human { 105 | name 106 | } 107 | fragment HumanFields3 on Human { 108 | name 109 | }", 110 | TEST_SCHEMA, 111 | &mut plan, 112 | ); 113 | 114 | assert_eq!(get_messages(&errors).len(), 0); 115 | } 116 | 117 | #[test] 118 | fn all_fragment_names_are_used_by_multiple_operations() { 119 | use crate::validation::test_utils::*; 120 | 121 | let mut plan = create_plan_from_rule(Box::new(NoUnusedFragments::new())); 122 | let errors = test_operation_with_schema( 123 | "query Foo { 124 | human(id: 4) { 125 | ...HumanFields1 126 | } 127 | } 128 | query Bar { 129 | human(id: 4) { 130 | ...HumanFields2 131 | } 132 | } 133 | fragment HumanFields1 on Human { 134 | name 135 | ...HumanFields3 136 | } 137 | fragment HumanFields2 on Human { 138 | name 139 | } 140 | fragment HumanFields3 on Human { 141 | name 142 | } 143 | ", 144 | TEST_SCHEMA, 145 | &mut plan, 146 | ); 147 | 148 | assert_eq!(get_messages(&errors).len(), 0); 149 | } 150 | 151 | #[test] 152 | fn contains_unknown_fragments() { 153 | use crate::validation::test_utils::*; 154 | 155 | let mut plan = create_plan_from_rule(Box::new(NoUnusedFragments::new())); 156 | let errors = test_operation_with_schema( 157 | "query Foo { 158 | human(id: 4) { 159 | ...HumanFields1 160 | } 161 | } 162 | query Bar { 163 | human(id: 4) { 164 | ...HumanFields2 165 | } 166 | } 167 | fragment HumanFields1 on Human { 168 | name 169 | ...HumanFields3 170 | } 171 | fragment HumanFields2 on Human { 172 | name 173 | } 174 | fragment HumanFields3 on Human { 175 | name 176 | } 177 | fragment Unused1 on Human { 178 | name 179 | } 180 | fragment Unused2 on Human { 181 | name 182 | } 183 | ", 184 | TEST_SCHEMA, 185 | &mut plan, 186 | ); 187 | 188 | let messages = get_messages(&errors); 189 | assert_eq!(messages.len(), 2); 190 | } 191 | 192 | // TODO: Fix this one :( It's not working 193 | #[test] 194 | #[ignore = "Fix this one :( It's not working"] 195 | fn contains_unknown_fragments_with_ref_cycle() { 196 | use crate::validation::test_utils::*; 197 | 198 | let mut plan = create_plan_from_rule(Box::new(NoUnusedFragments::new())); 199 | let errors = test_operation_with_schema( 200 | "query Foo { 201 | human(id: 4) { 202 | ...HumanFields1 203 | } 204 | } 205 | query Bar { 206 | human(id: 4) { 207 | ...HumanFields2 208 | } 209 | } 210 | fragment HumanFields1 on Human { 211 | name 212 | ...HumanFields3 213 | } 214 | fragment HumanFields2 on Human { 215 | name 216 | } 217 | fragment HumanFields3 on Human { 218 | name 219 | } 220 | fragment Unused1 on Human { 221 | name 222 | ...Unused2 223 | } 224 | fragment Unused2 on Human { 225 | name 226 | ...Unused1 227 | } 228 | ", 229 | TEST_SCHEMA, 230 | &mut plan, 231 | ); 232 | 233 | let messages = get_messages(&errors); 234 | assert_eq!(messages.len(), 2); 235 | assert_eq!( 236 | messages, 237 | vec![ 238 | "Fragment \"Unused1\" is never used.", 239 | "Fragment \"Unused2\" is never used." 240 | ] 241 | ); 242 | } 243 | 244 | #[test] 245 | fn contains_unknown_and_undef_fragments() { 246 | use crate::validation::test_utils::*; 247 | 248 | let mut plan = create_plan_from_rule(Box::new(NoUnusedFragments::new())); 249 | let errors = test_operation_with_schema( 250 | "query Foo { 251 | human(id: 4) { 252 | ...bar 253 | } 254 | } 255 | fragment foo on Human { 256 | name 257 | } 258 | ", 259 | TEST_SCHEMA, 260 | &mut plan, 261 | ); 262 | 263 | let messages = get_messages(&errors); 264 | assert_eq!(messages.len(), 1); 265 | assert_eq!(messages, vec!["Fragment \"foo\" is never used.",]); 266 | } 267 | -------------------------------------------------------------------------------- /src/validation/rules/no_unused_variables.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{HashMap, HashSet}; 2 | 3 | use super::ValidationRule; 4 | use crate::ast::{ 5 | visit_document, AstNodeWithName, OperationVisitor, OperationVisitorContext, ValueExtension, 6 | }; 7 | use crate::static_graphql::query::{self, OperationDefinition}; 8 | use crate::validation::utils::{ValidationError, ValidationErrorContext}; 9 | 10 | /// No unused fragments 11 | /// 12 | /// A GraphQL operation is only valid if all variables defined by an operation 13 | /// are used, either directly or within a spread fragment. 14 | /// 15 | /// See https://spec.graphql.org/draft/#sec-All-Variables-Used 16 | pub struct NoUnusedVariables<'a> { 17 | current_scope: Option>, 18 | defined_variables: HashMap, HashSet<&'a str>>, 19 | used_variables: HashMap, Vec<&'a str>>, 20 | spreads: HashMap, Vec<&'a str>>, 21 | } 22 | 23 | impl<'a> Default for NoUnusedVariables<'a> { 24 | fn default() -> Self { 25 | Self::new() 26 | } 27 | } 28 | 29 | impl<'a> NoUnusedVariables<'a> { 30 | pub fn new() -> Self { 31 | Self { 32 | current_scope: None, 33 | defined_variables: HashMap::new(), 34 | used_variables: HashMap::new(), 35 | spreads: HashMap::new(), 36 | } 37 | } 38 | } 39 | 40 | impl<'a> NoUnusedVariables<'a> { 41 | fn find_used_vars( 42 | &self, 43 | from: &NoUnusedVariablesScope<'a>, 44 | defined: &HashSet<&str>, 45 | used: &mut HashSet<&'a str>, 46 | visited: &mut HashSet>, 47 | ) { 48 | if visited.contains(from) { 49 | return; 50 | } 51 | 52 | visited.insert(from.clone()); 53 | 54 | if let Some(used_vars) = self.used_variables.get(from) { 55 | for var in used_vars { 56 | if defined.contains(var) { 57 | used.insert(var); 58 | } 59 | } 60 | } 61 | 62 | if let Some(spreads) = self.spreads.get(from) { 63 | for spread in spreads { 64 | self.find_used_vars( 65 | &NoUnusedVariablesScope::Fragment(spread), 66 | defined, 67 | used, 68 | visited, 69 | ); 70 | } 71 | } 72 | } 73 | } 74 | 75 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 76 | pub enum NoUnusedVariablesScope<'a> { 77 | Operation(Option<&'a str>), 78 | Fragment(&'a str), 79 | } 80 | 81 | impl<'a> OperationVisitor<'a, ValidationErrorContext> for NoUnusedVariables<'a> { 82 | fn enter_operation_definition( 83 | &mut self, 84 | _: &mut OperationVisitorContext, 85 | _: &mut ValidationErrorContext, 86 | operation_definition: &'a OperationDefinition, 87 | ) { 88 | let op_name = operation_definition.node_name(); 89 | self.current_scope = Some(NoUnusedVariablesScope::Operation(op_name)); 90 | self.defined_variables.insert(op_name, HashSet::new()); 91 | } 92 | 93 | fn enter_fragment_definition( 94 | &mut self, 95 | _: &mut OperationVisitorContext, 96 | _: &mut ValidationErrorContext, 97 | fragment_definition: &'a query::FragmentDefinition, 98 | ) { 99 | self.current_scope = Some(NoUnusedVariablesScope::Fragment(&fragment_definition.name)); 100 | } 101 | 102 | fn enter_fragment_spread( 103 | &mut self, 104 | _: &mut OperationVisitorContext, 105 | _: &mut ValidationErrorContext, 106 | fragment_spread: &'a query::FragmentSpread, 107 | ) { 108 | if let Some(scope) = &self.current_scope { 109 | self.spreads 110 | .entry(scope.clone()) 111 | .or_default() 112 | .push(&fragment_spread.fragment_name); 113 | } 114 | } 115 | 116 | fn enter_variable_definition( 117 | &mut self, 118 | _: &mut OperationVisitorContext, 119 | _: &mut ValidationErrorContext, 120 | variable_definition: &'a query::VariableDefinition, 121 | ) { 122 | if let Some(NoUnusedVariablesScope::Operation(ref name)) = self.current_scope { 123 | if let Some(vars) = self.defined_variables.get_mut(name) { 124 | vars.insert(&variable_definition.name); 125 | } 126 | } 127 | } 128 | 129 | fn enter_argument( 130 | &mut self, 131 | _: &mut OperationVisitorContext, 132 | _: &mut ValidationErrorContext, 133 | (_arg_name, arg_value): &'a (String, query::Value), 134 | ) { 135 | if let Some(ref scope) = self.current_scope { 136 | self.used_variables 137 | .entry(scope.clone()) 138 | .or_default() 139 | .append(&mut arg_value.variables_in_use()); 140 | } 141 | } 142 | 143 | fn leave_document( 144 | &mut self, 145 | _: &mut OperationVisitorContext, 146 | user_context: &mut ValidationErrorContext, 147 | _: &query::Document, 148 | ) { 149 | for (op_name, def_vars) in &self.defined_variables { 150 | let mut used = HashSet::new(); 151 | let mut visited = HashSet::new(); 152 | 153 | self.find_used_vars( 154 | &NoUnusedVariablesScope::Operation(*op_name), 155 | def_vars, 156 | &mut used, 157 | &mut visited, 158 | ); 159 | 160 | def_vars 161 | .iter() 162 | .filter(|var| !used.contains(*var)) 163 | .for_each(|var| { 164 | user_context.report_error(ValidationError { 165 | error_code: self.error_code(), 166 | message: error_message(var, op_name), 167 | locations: vec![], 168 | }) 169 | }) 170 | } 171 | } 172 | } 173 | 174 | fn error_message(var_name: &str, op_name: &Option<&str>) -> String { 175 | if let Some(op_name) = op_name { 176 | format!( 177 | r#"Variable "${}" is never used in operation "{}"."#, 178 | var_name, op_name 179 | ) 180 | } else { 181 | format!(r#"Variable "${}" is never used."#, var_name) 182 | } 183 | } 184 | 185 | impl<'n> ValidationRule for NoUnusedVariables<'n> { 186 | fn error_code<'a>(&self) -> &'a str { 187 | "NoUnusedVariables" 188 | } 189 | 190 | fn validate( 191 | &self, 192 | ctx: &mut OperationVisitorContext, 193 | error_collector: &mut ValidationErrorContext, 194 | ) { 195 | visit_document( 196 | &mut NoUnusedVariables::new(), 197 | ctx.operation, 198 | ctx, 199 | error_collector, 200 | ); 201 | } 202 | } 203 | 204 | #[test] 205 | fn use_all_variables() { 206 | use crate::validation::test_utils::*; 207 | 208 | let mut plan = create_plan_from_rule(Box::new(NoUnusedVariables::new())); 209 | let errors = test_operation_with_schema( 210 | "query ($a: String, $b: String, $c: String) { 211 | field(a: $a, b: $b, c: $c) 212 | }", 213 | TEST_SCHEMA, 214 | &mut plan, 215 | ); 216 | 217 | assert_eq!(get_messages(&errors).len(), 0); 218 | } 219 | 220 | #[test] 221 | fn use_all_variables_deeply() { 222 | use crate::validation::test_utils::*; 223 | 224 | let mut plan = create_plan_from_rule(Box::new(NoUnusedVariables::new())); 225 | let errors = test_operation_with_schema( 226 | "query Foo($a: String, $b: String, $c: String) { 227 | field(a: $a) { 228 | field(b: $b) { 229 | field(c: $c) 230 | } 231 | } 232 | } 233 | ", 234 | TEST_SCHEMA, 235 | &mut plan, 236 | ); 237 | 238 | assert_eq!(get_messages(&errors).len(), 0); 239 | } 240 | 241 | #[test] 242 | fn use_all_variables_deeply_in_inline_fragments() { 243 | use crate::validation::test_utils::*; 244 | 245 | let mut plan = create_plan_from_rule(Box::new(NoUnusedVariables::new())); 246 | let errors = test_operation_with_schema( 247 | " query Foo($a: String, $b: String, $c: String) { 248 | ... on Type { 249 | field(a: $a) { 250 | field(b: $b) { 251 | ... on Type { 252 | field(c: $c) 253 | } 254 | } 255 | } 256 | } 257 | } 258 | ", 259 | TEST_SCHEMA, 260 | &mut plan, 261 | ); 262 | 263 | assert_eq!(get_messages(&errors).len(), 0); 264 | } 265 | 266 | #[test] 267 | fn use_all_variables_in_fragments() { 268 | use crate::validation::test_utils::*; 269 | 270 | let mut plan = create_plan_from_rule(Box::new(NoUnusedVariables::new())); 271 | let errors = test_operation_with_schema( 272 | "query Foo($a: String, $b: String, $c: String) { 273 | ...FragA 274 | } 275 | fragment FragA on Type { 276 | field(a: $a) { 277 | ...FragB 278 | } 279 | } 280 | fragment FragB on Type { 281 | field(b: $b) { 282 | ...FragC 283 | } 284 | } 285 | fragment FragC on Type { 286 | field(c: $c) 287 | }", 288 | TEST_SCHEMA, 289 | &mut plan, 290 | ); 291 | 292 | assert_eq!(get_messages(&errors).len(), 0); 293 | } 294 | 295 | #[test] 296 | fn variables_used_by_fragment_in_multiple_operations() { 297 | use crate::validation::test_utils::*; 298 | 299 | let mut plan = create_plan_from_rule(Box::new(NoUnusedVariables::new())); 300 | let errors = test_operation_with_schema( 301 | "query Foo($a: String) { 302 | ...FragA 303 | } 304 | query Bar($b: String) { 305 | ...FragB 306 | } 307 | fragment FragA on Type { 308 | field(a: $a) 309 | } 310 | fragment FragB on Type { 311 | field(b: $b) 312 | }", 313 | TEST_SCHEMA, 314 | &mut plan, 315 | ); 316 | 317 | assert_eq!(get_messages(&errors).len(), 0); 318 | } 319 | 320 | #[test] 321 | fn variables_used_by_recursive_fragment() { 322 | use crate::validation::test_utils::*; 323 | 324 | let mut plan = create_plan_from_rule(Box::new(NoUnusedVariables::new())); 325 | let errors = test_operation_with_schema( 326 | "query Foo($a: String) { 327 | ...FragA 328 | } 329 | fragment FragA on Type { 330 | field(a: $a) { 331 | ...FragA 332 | } 333 | }", 334 | TEST_SCHEMA, 335 | &mut plan, 336 | ); 337 | 338 | assert_eq!(get_messages(&errors).len(), 0); 339 | } 340 | 341 | #[test] 342 | fn variables_not_used() { 343 | use crate::validation::test_utils::*; 344 | 345 | let mut plan = create_plan_from_rule(Box::new(NoUnusedVariables::new())); 346 | let errors = test_operation_with_schema( 347 | "query ($a: String, $b: String, $c: String) { 348 | field(a: $a, b: $b) 349 | }", 350 | TEST_SCHEMA, 351 | &mut plan, 352 | ); 353 | 354 | let messages = get_messages(&errors); 355 | 356 | assert_eq!(messages.len(), 1); 357 | assert!(messages.contains(&&"Variable \"$c\" is never used.".to_owned())); 358 | } 359 | 360 | #[test] 361 | fn multiple_variables_not_used() { 362 | use crate::validation::test_utils::*; 363 | 364 | let mut plan = create_plan_from_rule(Box::new(NoUnusedVariables::new())); 365 | let errors = test_operation_with_schema( 366 | "query Foo($a: String, $b: String, $c: String) { 367 | field(b: $b) 368 | }", 369 | TEST_SCHEMA, 370 | &mut plan, 371 | ); 372 | 373 | let messages = get_messages(&errors); 374 | 375 | assert_eq!(messages.len(), 2); 376 | assert!(messages.contains(&&"Variable \"$a\" is never used in operation \"Foo\".".to_owned())); 377 | assert!(messages.contains(&&"Variable \"$c\" is never used in operation \"Foo\".".to_owned())); 378 | } 379 | 380 | #[test] 381 | fn variables_not_used_in_fragments() { 382 | use crate::validation::test_utils::*; 383 | 384 | let mut plan = create_plan_from_rule(Box::new(NoUnusedVariables::new())); 385 | let errors = test_operation_with_schema( 386 | "query Foo($a: String, $b: String, $c: String) { 387 | ...FragA 388 | } 389 | fragment FragA on Type { 390 | field(a: $a) { 391 | ...FragB 392 | } 393 | } 394 | fragment FragB on Type { 395 | field(b: $b) { 396 | ...FragC 397 | } 398 | } 399 | fragment FragC on Type { 400 | field 401 | }", 402 | TEST_SCHEMA, 403 | &mut plan, 404 | ); 405 | 406 | let messages = get_messages(&errors); 407 | 408 | assert_eq!(messages.len(), 1); 409 | assert!(messages.contains(&&"Variable \"$c\" is never used in operation \"Foo\".".to_owned())); 410 | } 411 | 412 | #[test] 413 | fn multiple_variables_not_used_in_fragments() { 414 | use crate::validation::test_utils::*; 415 | 416 | let mut plan = create_plan_from_rule(Box::new(NoUnusedVariables::new())); 417 | let errors = test_operation_with_schema( 418 | "query Foo($a: String, $b: String, $c: String) { 419 | ...FragA 420 | } 421 | fragment FragA on Type { 422 | field { 423 | ...FragB 424 | } 425 | } 426 | fragment FragB on Type { 427 | field(b: $b) { 428 | ...FragC 429 | } 430 | } 431 | fragment FragC on Type { 432 | field 433 | }", 434 | TEST_SCHEMA, 435 | &mut plan, 436 | ); 437 | 438 | let messages = get_messages(&errors); 439 | 440 | assert_eq!(messages.len(), 2); 441 | assert!(messages.contains(&&"Variable \"$a\" is never used in operation \"Foo\".".to_owned())); 442 | assert!(messages.contains(&&"Variable \"$c\" is never used in operation \"Foo\".".to_owned())); 443 | } 444 | 445 | #[test] 446 | fn variables_not_used_by_unreferences_fragment() { 447 | use crate::validation::test_utils::*; 448 | 449 | let mut plan = create_plan_from_rule(Box::new(NoUnusedVariables::new())); 450 | let errors = test_operation_with_schema( 451 | "query Foo($b: String) { 452 | ...FragA 453 | } 454 | fragment FragA on Type { 455 | field(a: $a) 456 | } 457 | fragment FragB on Type { 458 | field(b: $b) 459 | }", 460 | TEST_SCHEMA, 461 | &mut plan, 462 | ); 463 | 464 | let messages = get_messages(&errors); 465 | 466 | assert_eq!(messages.len(), 1); 467 | assert!(messages.contains(&&"Variable \"$b\" is never used in operation \"Foo\".".to_owned())); 468 | } 469 | 470 | #[test] 471 | fn variables_not_used_by_fragment_used_by_other_operation() { 472 | use crate::validation::test_utils::*; 473 | 474 | let mut plan = create_plan_from_rule(Box::new(NoUnusedVariables::new())); 475 | let errors = test_operation_with_schema( 476 | "query Foo($b: String) { 477 | ...FragA 478 | } 479 | query Bar($a: String) { 480 | ...FragB 481 | } 482 | fragment FragA on Type { 483 | field(a: $a) 484 | } 485 | fragment FragB on Type { 486 | field(b: $b) 487 | }", 488 | TEST_SCHEMA, 489 | &mut plan, 490 | ); 491 | 492 | let messages = get_messages(&errors); 493 | 494 | assert_eq!(messages.len(), 2); 495 | assert!(messages.contains(&&"Variable \"$b\" is never used in operation \"Foo\".".to_owned())); 496 | assert!(messages.contains(&&"Variable \"$a\" is never used in operation \"Bar\".".to_owned())); 497 | } 498 | 499 | #[test] 500 | fn should_also_check_directives_usage() { 501 | use crate::validation::test_utils::*; 502 | 503 | let mut plan = create_plan_from_rule(Box::new(NoUnusedVariables::new())); 504 | let errors = test_operation_with_schema( 505 | "query foo($skip: Boolean!) { 506 | field @skip(if: $skip) 507 | } 508 | ", 509 | TEST_SCHEMA, 510 | &mut plan, 511 | ); 512 | 513 | let messages = get_messages(&errors); 514 | assert_eq!(messages.len(), 0); 515 | } 516 | 517 | #[test] 518 | fn nested_variable_should_work_as_well() { 519 | use crate::validation::test_utils::*; 520 | 521 | let mut plan = create_plan_from_rule(Box::new(NoUnusedVariables::new())); 522 | let errors = test_operation_with_schema( 523 | "query foo($t: Boolean!) { 524 | field(boop: { test: $t}) 525 | } 526 | ", 527 | TEST_SCHEMA, 528 | &mut plan, 529 | ); 530 | 531 | let messages = get_messages(&errors); 532 | assert_eq!(messages.len(), 0); 533 | } 534 | -------------------------------------------------------------------------------- /src/validation/rules/provided_required_arguments.rs: -------------------------------------------------------------------------------- 1 | use super::ValidationRule; 2 | use crate::ast::{ 3 | visit_document, FieldByNameExtension, InputValueHelpers, OperationVisitor, 4 | OperationVisitorContext, 5 | }; 6 | use crate::static_graphql::query::Value; 7 | use crate::static_graphql::schema::InputValue; 8 | use crate::validation::utils::{ValidationError, ValidationErrorContext}; 9 | 10 | /// Provided required arguments 11 | /// 12 | /// A field or directive is only valid if all required (non-null without a 13 | /// default value) field arguments have been provided. 14 | /// 15 | /// See https://spec.graphql.org/draft/#sec-Required-Arguments 16 | pub struct ProvidedRequiredArguments; 17 | 18 | impl Default for ProvidedRequiredArguments { 19 | fn default() -> Self { 20 | Self::new() 21 | } 22 | } 23 | 24 | impl ProvidedRequiredArguments { 25 | pub fn new() -> Self { 26 | ProvidedRequiredArguments 27 | } 28 | } 29 | 30 | impl<'a> OperationVisitor<'a, ValidationErrorContext> for ProvidedRequiredArguments { 31 | fn enter_field( 32 | &mut self, 33 | visitor_context: &mut OperationVisitorContext, 34 | user_context: &mut ValidationErrorContext, 35 | field: &crate::static_graphql::query::Field, 36 | ) { 37 | if let Some(parent_type) = visitor_context.current_parent_type() { 38 | if let Some(field_def) = parent_type.field_by_name(&field.name) { 39 | let missing_required_args = 40 | validate_arguments(&field.arguments, &field_def.arguments); 41 | 42 | for missing in missing_required_args { 43 | user_context.report_error(ValidationError {error_code: self.error_code(), 44 | locations: vec![field.position], 45 | message: format!("Field \"{}\" argument \"{}\" of type \"{}\" is required, but it was not provided.", 46 | field.name, missing.name, missing.value_type), 47 | }); 48 | } 49 | } 50 | } 51 | } 52 | 53 | fn enter_directive( 54 | &mut self, 55 | visitor_context: &mut OperationVisitorContext, 56 | user_context: &mut ValidationErrorContext, 57 | directive: &crate::static_graphql::query::Directive, 58 | ) { 59 | let known_directives = &visitor_context.directives; 60 | 61 | if let Some(directive_def) = known_directives.get(&directive.name) { 62 | let missing_required_args = 63 | validate_arguments(&directive.arguments, &directive_def.arguments); 64 | 65 | for missing in missing_required_args { 66 | user_context.report_error(ValidationError {error_code: self.error_code(), 67 | locations: vec![directive.position], 68 | message: format!("Directive \"@{}\" argument \"{}\" of type \"{}\" is required, but it was not provided.", 69 | directive.name, missing.name, missing.value_type), 70 | }); 71 | } 72 | } 73 | } 74 | } 75 | 76 | fn validate_arguments( 77 | arguments_used: &[(String, Value)], 78 | arguments_defined: &[InputValue], 79 | ) -> Vec { 80 | arguments_defined 81 | .iter() 82 | .filter_map(|field_arg_def| { 83 | if field_arg_def.is_required() 84 | && !arguments_used 85 | .iter() 86 | .any(|(name, _value)| name.eq(&field_arg_def.name)) 87 | { 88 | Some(field_arg_def.clone()) 89 | } else { 90 | None 91 | } 92 | }) 93 | .collect() 94 | } 95 | 96 | impl ValidationRule for ProvidedRequiredArguments { 97 | fn error_code<'a>(&self) -> &'a str { 98 | "ProvidedRequiredArguments" 99 | } 100 | 101 | fn validate( 102 | &self, 103 | ctx: &mut OperationVisitorContext, 104 | error_collector: &mut ValidationErrorContext, 105 | ) { 106 | visit_document( 107 | &mut ProvidedRequiredArguments::new(), 108 | ctx.operation, 109 | ctx, 110 | error_collector, 111 | ); 112 | } 113 | } 114 | 115 | #[test] 116 | fn ignores_unknown_arguments() { 117 | use crate::validation::test_utils::*; 118 | 119 | let mut plan = create_plan_from_rule(Box::new(ProvidedRequiredArguments {})); 120 | let errors = test_operation_with_schema( 121 | "{ 122 | dog { 123 | isHouseTrained(unknownArgument: true) 124 | } 125 | }", 126 | TEST_SCHEMA, 127 | &mut plan, 128 | ); 129 | 130 | assert_eq!(get_messages(&errors).len(), 0); 131 | } 132 | 133 | #[test] 134 | fn arg_on_optional_arg() { 135 | use crate::validation::test_utils::*; 136 | 137 | let mut plan = create_plan_from_rule(Box::new(ProvidedRequiredArguments {})); 138 | let errors = test_operation_with_schema( 139 | "{ 140 | dog { 141 | isHouseTrained(atOtherHomes: true) 142 | } 143 | }", 144 | TEST_SCHEMA, 145 | &mut plan, 146 | ); 147 | 148 | assert_eq!(get_messages(&errors).len(), 0); 149 | } 150 | 151 | #[test] 152 | fn no_arg_on_optional_arg() { 153 | use crate::validation::test_utils::*; 154 | 155 | let mut plan = create_plan_from_rule(Box::new(ProvidedRequiredArguments {})); 156 | let errors = test_operation_with_schema( 157 | "{ 158 | dog { 159 | isHouseTrained 160 | } 161 | }", 162 | TEST_SCHEMA, 163 | &mut plan, 164 | ); 165 | 166 | assert_eq!(get_messages(&errors).len(), 0); 167 | } 168 | 169 | #[test] 170 | fn multiple_args() { 171 | use crate::validation::test_utils::*; 172 | 173 | let mut plan = create_plan_from_rule(Box::new(ProvidedRequiredArguments {})); 174 | let errors = test_operation_with_schema( 175 | "{ 176 | complicatedArgs { 177 | multipleReqs(req1: 1, req2: 2) 178 | } 179 | }", 180 | TEST_SCHEMA, 181 | &mut plan, 182 | ); 183 | 184 | assert_eq!(get_messages(&errors).len(), 0); 185 | } 186 | 187 | #[test] 188 | fn multiple_args_reverse_order() { 189 | use crate::validation::test_utils::*; 190 | 191 | let mut plan = create_plan_from_rule(Box::new(ProvidedRequiredArguments {})); 192 | let errors = test_operation_with_schema( 193 | "{ 194 | complicatedArgs { 195 | multipleReqs(req2: 2, req1: 1) 196 | } 197 | }", 198 | TEST_SCHEMA, 199 | &mut plan, 200 | ); 201 | 202 | assert_eq!(get_messages(&errors).len(), 0); 203 | } 204 | 205 | #[test] 206 | fn no_args_on_multiple_optional() { 207 | use crate::validation::test_utils::*; 208 | 209 | let mut plan = create_plan_from_rule(Box::new(ProvidedRequiredArguments {})); 210 | let errors = test_operation_with_schema( 211 | "{ 212 | complicatedArgs { 213 | multipleOpts 214 | } 215 | }", 216 | TEST_SCHEMA, 217 | &mut plan, 218 | ); 219 | 220 | assert_eq!(get_messages(&errors).len(), 0); 221 | } 222 | 223 | #[test] 224 | fn one_arg_on_multiple_optional() { 225 | use crate::validation::test_utils::*; 226 | 227 | let mut plan = create_plan_from_rule(Box::new(ProvidedRequiredArguments {})); 228 | let errors = test_operation_with_schema( 229 | "{ 230 | complicatedArgs { 231 | multipleOpts(opt1: 1) 232 | } 233 | }", 234 | TEST_SCHEMA, 235 | &mut plan, 236 | ); 237 | 238 | assert_eq!(get_messages(&errors).len(), 0); 239 | } 240 | 241 | #[test] 242 | fn second_arg_on_multiple_optional() { 243 | use crate::validation::test_utils::*; 244 | 245 | let mut plan = create_plan_from_rule(Box::new(ProvidedRequiredArguments {})); 246 | let errors = test_operation_with_schema( 247 | "{ 248 | complicatedArgs { 249 | multipleOpts(opt2: 1) 250 | } 251 | }", 252 | TEST_SCHEMA, 253 | &mut plan, 254 | ); 255 | 256 | assert_eq!(get_messages(&errors).len(), 0); 257 | } 258 | 259 | #[test] 260 | fn multiple_required_args_on_mixed_list() { 261 | use crate::validation::test_utils::*; 262 | 263 | let mut plan = create_plan_from_rule(Box::new(ProvidedRequiredArguments {})); 264 | let errors = test_operation_with_schema( 265 | "{ 266 | complicatedArgs { 267 | multipleOptAndReq(req1: 3, req2: 4) 268 | } 269 | }", 270 | TEST_SCHEMA, 271 | &mut plan, 272 | ); 273 | 274 | assert_eq!(get_messages(&errors).len(), 0); 275 | } 276 | 277 | #[test] 278 | fn multiple_required_and_one_optional_arg_on_mixedlist() { 279 | use crate::validation::test_utils::*; 280 | 281 | let mut plan = create_plan_from_rule(Box::new(ProvidedRequiredArguments {})); 282 | let errors = test_operation_with_schema( 283 | "{ 284 | complicatedArgs { 285 | multipleOptAndReq(req1: 3, req2: 4, opt1: 5) 286 | } 287 | }", 288 | TEST_SCHEMA, 289 | &mut plan, 290 | ); 291 | 292 | assert_eq!(get_messages(&errors).len(), 0); 293 | } 294 | 295 | #[test] 296 | fn all_required_and_optional_args_on_mixedlist() { 297 | use crate::validation::test_utils::*; 298 | 299 | let mut plan = create_plan_from_rule(Box::new(ProvidedRequiredArguments {})); 300 | let errors = test_operation_with_schema( 301 | "{ 302 | complicatedArgs { 303 | multipleOptAndReq(req1: 3, req2: 4, opt1: 5, opt2: 6) 304 | } 305 | }", 306 | TEST_SCHEMA, 307 | &mut plan, 308 | ); 309 | 310 | assert_eq!(get_messages(&errors).len(), 0); 311 | } 312 | 313 | #[test] 314 | fn missing_one_non_nullable_argument() { 315 | use crate::validation::test_utils::*; 316 | 317 | let mut plan = create_plan_from_rule(Box::new(ProvidedRequiredArguments {})); 318 | let errors = test_operation_with_schema( 319 | "{ 320 | complicatedArgs { 321 | multipleReqs(req2: 2) 322 | } 323 | }", 324 | TEST_SCHEMA, 325 | &mut plan, 326 | ); 327 | 328 | let messages = get_messages(&errors); 329 | assert_eq!(messages.len(), 1); 330 | assert_eq!(messages, vec![ 331 | "Field \"multipleReqs\" argument \"req1\" of type \"Int!\" is required, but it was not provided." 332 | ]); 333 | } 334 | 335 | #[test] 336 | fn missing_multiple_non_nullable_arguments() { 337 | use crate::validation::test_utils::*; 338 | 339 | let mut plan = create_plan_from_rule(Box::new(ProvidedRequiredArguments {})); 340 | let errors = test_operation_with_schema( 341 | "{ 342 | complicatedArgs { 343 | multipleReqs 344 | } 345 | }", 346 | TEST_SCHEMA, 347 | &mut plan, 348 | ); 349 | 350 | let messages = get_messages(&errors); 351 | assert_eq!(messages.len(), 2); 352 | assert_eq!(messages, vec![ 353 | "Field \"multipleReqs\" argument \"req1\" of type \"Int!\" is required, but it was not provided.", 354 | "Field \"multipleReqs\" argument \"req2\" of type \"Int!\" is required, but it was not provided." 355 | ]); 356 | } 357 | 358 | #[test] 359 | fn incorrect_value_and_missing_argument() { 360 | use crate::validation::test_utils::*; 361 | 362 | let mut plan = create_plan_from_rule(Box::new(ProvidedRequiredArguments {})); 363 | let errors = test_operation_with_schema( 364 | "{ 365 | complicatedArgs { 366 | multipleReqs(req1: \"one\") 367 | } 368 | }", 369 | TEST_SCHEMA, 370 | &mut plan, 371 | ); 372 | 373 | let messages = get_messages(&errors); 374 | assert_eq!(messages.len(), 1); 375 | assert_eq!(messages, vec![ 376 | "Field \"multipleReqs\" argument \"req2\" of type \"Int!\" is required, but it was not provided." 377 | ]); 378 | } 379 | 380 | #[test] 381 | fn ignores_unknown_directives() { 382 | use crate::validation::test_utils::*; 383 | 384 | let mut plan = create_plan_from_rule(Box::new(ProvidedRequiredArguments {})); 385 | let errors = test_operation_with_schema( 386 | "{ 387 | dog @unknown 388 | }", 389 | TEST_SCHEMA, 390 | &mut plan, 391 | ); 392 | 393 | let messages = get_messages(&errors); 394 | assert_eq!(messages.len(), 0); 395 | } 396 | 397 | #[test] 398 | fn with_directives_of_valid_types() { 399 | use crate::validation::test_utils::*; 400 | 401 | let mut plan = create_plan_from_rule(Box::new(ProvidedRequiredArguments {})); 402 | let errors = test_operation_with_schema( 403 | "{ 404 | dog @include(if: true) { 405 | name 406 | } 407 | human @skip(if: false) { 408 | name 409 | } 410 | }", 411 | TEST_SCHEMA, 412 | &mut plan, 413 | ); 414 | 415 | let messages = get_messages(&errors); 416 | assert_eq!(messages.len(), 0); 417 | } 418 | 419 | #[test] 420 | fn with_directive_with_missing_types() { 421 | use crate::validation::test_utils::*; 422 | 423 | let mut plan = create_plan_from_rule(Box::new(ProvidedRequiredArguments {})); 424 | let errors = test_operation_with_schema( 425 | "{ 426 | dog @include { 427 | name @skip 428 | } 429 | }", 430 | TEST_SCHEMA, 431 | &mut plan, 432 | ); 433 | 434 | let messages = get_messages(&errors); 435 | assert_eq!(messages.len(), 2); 436 | assert_eq!(messages, vec![ 437 | "Directive \"@include\" argument \"if\" of type \"Boolean!\" is required, but it was not provided.", 438 | "Directive \"@skip\" argument \"if\" of type \"Boolean!\" is required, but it was not provided." 439 | ]) 440 | } 441 | -------------------------------------------------------------------------------- /src/validation/rules/rule.rs: -------------------------------------------------------------------------------- 1 | use crate::{ast::OperationVisitorContext, validation::utils::ValidationErrorContext}; 2 | 3 | pub trait ValidationRule: Send + Sync { 4 | fn validate( 5 | &self, 6 | _ctx: &mut OperationVisitorContext<'_>, 7 | _error_collector: &mut ValidationErrorContext, 8 | ); 9 | 10 | fn error_code<'a>(&self) -> &'a str; 11 | } 12 | -------------------------------------------------------------------------------- /src/validation/rules/single_field_subscriptions.rs: -------------------------------------------------------------------------------- 1 | use super::ValidationRule; 2 | use crate::ast::{ 3 | collect_fields, visit_document, OperationVisitor, OperationVisitorContext, 4 | SchemaDocumentExtension, 5 | }; 6 | use crate::static_graphql::query::OperationDefinition; 7 | use crate::static_graphql::schema::TypeDefinition; 8 | use crate::validation::utils::{ValidationError, ValidationErrorContext}; 9 | 10 | /// Unique operation names 11 | /// 12 | /// A GraphQL document is only valid if all defined operations have unique names. 13 | /// 14 | /// See https://spec.graphql.org/draft/#sec-Operation-Name-Uniqueness 15 | pub struct SingleFieldSubscriptions; 16 | 17 | impl Default for SingleFieldSubscriptions { 18 | fn default() -> Self { 19 | Self::new() 20 | } 21 | } 22 | 23 | impl SingleFieldSubscriptions { 24 | pub fn new() -> Self { 25 | Self 26 | } 27 | } 28 | 29 | impl<'a> OperationVisitor<'a, ValidationErrorContext> for SingleFieldSubscriptions { 30 | fn enter_operation_definition( 31 | &mut self, 32 | visitor_context: &mut OperationVisitorContext, 33 | user_context: &mut ValidationErrorContext, 34 | operation: &OperationDefinition, 35 | ) { 36 | if let OperationDefinition::Subscription(subscription) = operation { 37 | if let Some(subscription_type) = visitor_context.schema.subscription_type() { 38 | let operation_name = subscription.name.as_ref(); 39 | 40 | let selection_set_fields = collect_fields( 41 | &subscription.selection_set, 42 | &TypeDefinition::Object(subscription_type.clone()), 43 | &visitor_context.known_fragments, 44 | visitor_context, 45 | ); 46 | 47 | if selection_set_fields.len() > 1 { 48 | let error_message = match operation_name { 49 | Some(operation_name) => format!( 50 | "Subscription \"{}\" must select only one top level field.", 51 | operation_name 52 | ), 53 | None => "Anonymous Subscription must select only one top level field." 54 | .to_owned(), 55 | }; 56 | 57 | user_context.report_error(ValidationError { 58 | error_code: self.error_code(), 59 | locations: vec![subscription.position], 60 | message: error_message, 61 | }); 62 | } 63 | 64 | selection_set_fields 65 | .into_iter() 66 | .filter_map(|(field_name, fields_records)| { 67 | if field_name.starts_with("__") { 68 | return Some((field_name, fields_records)); 69 | } 70 | 71 | None 72 | }) 73 | .for_each(|(_field_name, _fields_records)| { 74 | let error_message = match operation_name { 75 | Some(operation_name) => format!( 76 | "Subscription \"{}\" must not select an introspection top level field.", 77 | operation_name 78 | ), 79 | None => "Anonymous Subscription must not select an introspection top level field." 80 | .to_owned(), 81 | }; 82 | 83 | user_context.report_error(ValidationError {error_code: self.error_code(), 84 | locations: vec![subscription.position], 85 | message: error_message, 86 | }); 87 | }) 88 | } 89 | } 90 | } 91 | } 92 | 93 | impl ValidationRule for SingleFieldSubscriptions { 94 | fn error_code<'a>(&self) -> &'a str { 95 | "SingleFieldSubscriptions" 96 | } 97 | 98 | fn validate( 99 | &self, 100 | ctx: &mut OperationVisitorContext, 101 | error_collector: &mut ValidationErrorContext, 102 | ) { 103 | visit_document( 104 | &mut SingleFieldSubscriptions::new(), 105 | ctx.operation, 106 | ctx, 107 | error_collector, 108 | ); 109 | } 110 | } 111 | 112 | #[cfg(test)] 113 | pub static TEST_SCHEMA_SUBSCRIPTION: &str = " 114 | type Message { 115 | body: String 116 | sender: String 117 | } 118 | type SubscriptionRoot { 119 | importantEmails: [String] 120 | notImportantEmails: [String] 121 | moreImportantEmails: [String] 122 | spamEmails: [String] 123 | deletedEmails: [String] 124 | newMessage: Message 125 | } 126 | type QueryRoot { 127 | dummy: String 128 | } 129 | schema { 130 | query: QueryRoot 131 | subscription: SubscriptionRoot 132 | } 133 | "; 134 | 135 | #[test] 136 | fn valid_subscription_with_fragment() { 137 | use crate::validation::test_utils::*; 138 | 139 | let mut plan = create_plan_from_rule(Box::new(SingleFieldSubscriptions {})); 140 | let errors = test_operation_with_schema( 141 | "subscription sub { 142 | ...newMessageFields 143 | } 144 | fragment newMessageFields on SubscriptionRoot { 145 | newMessage { 146 | body 147 | sender 148 | } 149 | }", 150 | TEST_SCHEMA_SUBSCRIPTION, 151 | &mut plan, 152 | ); 153 | 154 | assert_eq!(get_messages(&errors).len(), 0); 155 | } 156 | 157 | #[test] 158 | fn valid_subscription_with_fragment_and_field() { 159 | use crate::validation::test_utils::*; 160 | 161 | let mut plan = create_plan_from_rule(Box::new(SingleFieldSubscriptions {})); 162 | let errors = test_operation_with_schema( 163 | "subscription sub { 164 | newMessage { 165 | body 166 | } 167 | ...newMessageFields 168 | } 169 | fragment newMessageFields on SubscriptionRoot { 170 | newMessage { 171 | body 172 | sender 173 | } 174 | }", 175 | TEST_SCHEMA_SUBSCRIPTION, 176 | &mut plan, 177 | ); 178 | 179 | assert_eq!(get_messages(&errors).len(), 0); 180 | } 181 | 182 | #[test] 183 | fn fails_with_more_than_one_root_field() { 184 | use crate::validation::test_utils::*; 185 | 186 | let mut plan = create_plan_from_rule(Box::new(SingleFieldSubscriptions {})); 187 | let errors = test_operation_with_schema( 188 | "subscription ImportantEmails { 189 | importantEmails 190 | notImportantEmails 191 | }", 192 | TEST_SCHEMA_SUBSCRIPTION, 193 | &mut plan, 194 | ); 195 | 196 | let messages = get_messages(&errors); 197 | assert_eq!(messages.len(), 1); 198 | assert_eq!( 199 | messages, 200 | vec!["Subscription \"ImportantEmails\" must select only one top level field."] 201 | ); 202 | } 203 | 204 | #[test] 205 | fn fails_with_more_than_one_root_field_including_introspection() { 206 | use crate::validation::test_utils::*; 207 | 208 | let mut plan = create_plan_from_rule(Box::new(SingleFieldSubscriptions {})); 209 | let errors = test_operation_with_schema( 210 | "subscription ImportantEmails { 211 | importantEmails 212 | __typename 213 | }", 214 | TEST_SCHEMA_SUBSCRIPTION, 215 | &mut plan, 216 | ); 217 | 218 | let messages = get_messages(&errors); 219 | assert_eq!(messages.len(), 2); 220 | assert_eq!( 221 | messages, 222 | vec![ 223 | "Subscription \"ImportantEmails\" must select only one top level field.", 224 | "Subscription \"ImportantEmails\" must not select an introspection top level field." 225 | ] 226 | ); 227 | } 228 | 229 | #[test] 230 | fn fails_with_more_than_one_root_field_including_aliased_introspection_via_fragment() { 231 | use crate::validation::test_utils::*; 232 | 233 | let mut plan = create_plan_from_rule(Box::new(SingleFieldSubscriptions {})); 234 | let errors = test_operation_with_schema( 235 | "subscription ImportantEmails { 236 | importantEmails 237 | ...Introspection 238 | } 239 | fragment Introspection on SubscriptionRoot { 240 | typename: __typename 241 | }", 242 | TEST_SCHEMA_SUBSCRIPTION, 243 | &mut plan, 244 | ); 245 | 246 | let messages = get_messages(&errors); 247 | assert_eq!(messages.len(), 2); 248 | assert_eq!( 249 | messages, 250 | vec![ 251 | "Subscription \"ImportantEmails\" must select only one top level field.", 252 | "Subscription \"ImportantEmails\" must not select an introspection top level field." 253 | ] 254 | ); 255 | } 256 | 257 | #[test] 258 | fn fails_with_many_more_than_one_root_field() { 259 | use crate::validation::test_utils::*; 260 | 261 | let mut plan = create_plan_from_rule(Box::new(SingleFieldSubscriptions {})); 262 | let errors = test_operation_with_schema( 263 | "subscription ImportantEmails { 264 | importantEmails 265 | notImportantEmails 266 | spamEmails 267 | }", 268 | TEST_SCHEMA_SUBSCRIPTION, 269 | &mut plan, 270 | ); 271 | 272 | let messages = get_messages(&errors); 273 | assert_eq!(messages.len(), 1); 274 | assert_eq!( 275 | messages, 276 | vec!["Subscription \"ImportantEmails\" must select only one top level field.",] 277 | ); 278 | } 279 | 280 | #[test] 281 | fn fails_with_many_more_than_one_root_field_via_fragments() { 282 | use crate::validation::test_utils::*; 283 | 284 | let mut plan = create_plan_from_rule(Box::new(SingleFieldSubscriptions {})); 285 | let errors = test_operation_with_schema( 286 | "subscription ImportantEmails { 287 | importantEmails 288 | ... { 289 | more: moreImportantEmails 290 | } 291 | ...NotImportantEmails 292 | } 293 | fragment NotImportantEmails on SubscriptionRoot { 294 | notImportantEmails 295 | deleted: deletedEmails 296 | ...SpamEmails 297 | } 298 | fragment SpamEmails on SubscriptionRoot { 299 | spamEmails 300 | }", 301 | TEST_SCHEMA_SUBSCRIPTION, 302 | &mut plan, 303 | ); 304 | 305 | let messages = get_messages(&errors); 306 | assert_eq!(messages.len(), 1); 307 | assert_eq!( 308 | messages, 309 | vec!["Subscription \"ImportantEmails\" must select only one top level field.",] 310 | ); 311 | } 312 | 313 | #[test] 314 | fn does_not_infinite_loop_on_recursive_fragments() { 315 | use crate::validation::test_utils::*; 316 | 317 | let mut plan = create_plan_from_rule(Box::new(SingleFieldSubscriptions {})); 318 | let errors = test_operation_with_schema( 319 | "subscription NoInfiniteLoop { 320 | ...A 321 | } 322 | fragment A on SubscriptionRoot { 323 | ...A 324 | }", 325 | TEST_SCHEMA_SUBSCRIPTION, 326 | &mut plan, 327 | ); 328 | 329 | let messages = get_messages(&errors); 330 | assert_eq!(messages.len(), 0); 331 | } 332 | 333 | #[test] 334 | fn fails_with_many_more_than_one_root_field_via_fragments_anonymous() { 335 | use crate::validation::test_utils::*; 336 | 337 | let mut plan = create_plan_from_rule(Box::new(SingleFieldSubscriptions {})); 338 | let errors = test_operation_with_schema( 339 | "subscription { 340 | importantEmails 341 | ... { 342 | more: moreImportantEmails 343 | ...NotImportantEmails 344 | } 345 | ...NotImportantEmails 346 | } 347 | fragment NotImportantEmails on SubscriptionRoot { 348 | notImportantEmails 349 | deleted: deletedEmails 350 | ... { 351 | ... { 352 | archivedEmails 353 | } 354 | } 355 | ...SpamEmails 356 | } 357 | fragment SpamEmails on SubscriptionRoot { 358 | spamEmails 359 | ...NonExistentFragment 360 | }", 361 | TEST_SCHEMA_SUBSCRIPTION, 362 | &mut plan, 363 | ); 364 | 365 | let messages = get_messages(&errors); 366 | assert_eq!(messages.len(), 1); 367 | assert_eq!( 368 | messages, 369 | vec!["Anonymous Subscription must select only one top level field.",] 370 | ); 371 | } 372 | 373 | #[test] 374 | fn fails_with_more_than_one_root_field_in_anonymous_subscriptions() { 375 | use crate::validation::test_utils::*; 376 | 377 | let mut plan = create_plan_from_rule(Box::new(SingleFieldSubscriptions {})); 378 | let errors = test_operation_with_schema( 379 | "subscription { 380 | importantEmails 381 | notImportantEmails 382 | }", 383 | TEST_SCHEMA_SUBSCRIPTION, 384 | &mut plan, 385 | ); 386 | 387 | let messages = get_messages(&errors); 388 | assert_eq!(messages.len(), 1); 389 | assert_eq!( 390 | messages, 391 | vec!["Anonymous Subscription must select only one top level field.",] 392 | ); 393 | } 394 | 395 | #[test] 396 | fn fails_with_introspection_field() { 397 | use crate::validation::test_utils::*; 398 | 399 | let mut plan = create_plan_from_rule(Box::new(SingleFieldSubscriptions {})); 400 | let errors = test_operation_with_schema( 401 | "subscription ImportantEmails { 402 | __typename 403 | }", 404 | TEST_SCHEMA_SUBSCRIPTION, 405 | &mut plan, 406 | ); 407 | 408 | let messages = get_messages(&errors); 409 | assert_eq!(messages.len(), 1); 410 | assert_eq!( 411 | messages, 412 | vec!["Subscription \"ImportantEmails\" must not select an introspection top level field."] 413 | ); 414 | } 415 | 416 | #[test] 417 | fn fails_with_introspection_field_in_anonymous_subscription() { 418 | use crate::validation::test_utils::*; 419 | 420 | let mut plan = create_plan_from_rule(Box::new(SingleFieldSubscriptions {})); 421 | let errors = test_operation_with_schema( 422 | "subscription { 423 | __typename 424 | }", 425 | TEST_SCHEMA_SUBSCRIPTION, 426 | &mut plan, 427 | ); 428 | 429 | let messages = get_messages(&errors); 430 | assert_eq!(messages.len(), 1); 431 | assert_eq!( 432 | messages, 433 | vec!["Anonymous Subscription must not select an introspection top level field."] 434 | ); 435 | } 436 | 437 | #[test] 438 | fn skips_if_not_subscription_type() { 439 | use crate::validation::test_utils::*; 440 | 441 | let mut plan = create_plan_from_rule(Box::new(SingleFieldSubscriptions {})); 442 | let errors = test_operation_with_schema( 443 | "subscription { 444 | __typename 445 | }", 446 | "type Query { 447 | dummy: String 448 | }", 449 | &mut plan, 450 | ); 451 | 452 | let messages = get_messages(&errors); 453 | assert_eq!(messages.len(), 0); 454 | } 455 | -------------------------------------------------------------------------------- /src/validation/rules/unique_argument_names.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use crate::parser::Pos; 4 | 5 | use super::ValidationRule; 6 | use crate::ast::{visit_document, OperationVisitor, OperationVisitorContext}; 7 | use crate::static_graphql::query::Value; 8 | use crate::validation::utils::{ValidationError, ValidationErrorContext}; 9 | 10 | /// Unique argument names 11 | /// 12 | /// A GraphQL field or directive is only valid if all supplied arguments are 13 | /// uniquely named. 14 | /// 15 | /// See https://spec.graphql.org/draft/#sec-Argument-Names 16 | pub struct UniqueArgumentNames; 17 | 18 | impl Default for UniqueArgumentNames { 19 | fn default() -> Self { 20 | Self::new() 21 | } 22 | } 23 | 24 | impl UniqueArgumentNames { 25 | pub fn new() -> Self { 26 | UniqueArgumentNames 27 | } 28 | } 29 | 30 | impl<'a> OperationVisitor<'a, ValidationErrorContext> for UniqueArgumentNames { 31 | fn enter_field( 32 | &mut self, 33 | _: &mut OperationVisitorContext, 34 | user_context: &mut ValidationErrorContext, 35 | field: &crate::static_graphql::query::Field, 36 | ) { 37 | let found_args = collect_from_arguments(field.position, &field.arguments); 38 | 39 | found_args.iter().for_each(|(arg_name, positions)| { 40 | if positions.len() > 1 { 41 | user_context.report_error(ValidationError { 42 | error_code: self.error_code(), 43 | message: format!("There can be only one argument named \"{}\".", arg_name), 44 | locations: positions.clone(), 45 | }) 46 | } 47 | }); 48 | } 49 | 50 | fn enter_directive( 51 | &mut self, 52 | _: &mut OperationVisitorContext, 53 | user_context: &mut ValidationErrorContext, 54 | directive: &crate::static_graphql::query::Directive, 55 | ) { 56 | let found_args = collect_from_arguments(directive.position, &directive.arguments); 57 | 58 | found_args.iter().for_each(|(arg_name, positions)| { 59 | if positions.len() > 1 { 60 | user_context.report_error(ValidationError { 61 | error_code: self.error_code(), 62 | message: format!("There can be only one argument named \"{}\".", arg_name), 63 | locations: positions.clone(), 64 | }) 65 | } 66 | }); 67 | } 68 | } 69 | 70 | fn collect_from_arguments( 71 | reported_position: Pos, 72 | arguments: &Vec<(String, Value)>, 73 | ) -> HashMap> { 74 | let mut found_args = HashMap::>::new(); 75 | 76 | for (arg_name, _arg_value) in arguments { 77 | found_args 78 | .entry(arg_name.clone()) 79 | .or_default() 80 | .push(reported_position); 81 | } 82 | 83 | found_args 84 | } 85 | 86 | impl ValidationRule for UniqueArgumentNames { 87 | fn error_code<'a>(&self) -> &'a str { 88 | "UniqueArgumentNames" 89 | } 90 | 91 | fn validate( 92 | &self, 93 | ctx: &mut OperationVisitorContext, 94 | error_collector: &mut ValidationErrorContext, 95 | ) { 96 | visit_document( 97 | &mut UniqueArgumentNames::new(), 98 | ctx.operation, 99 | ctx, 100 | error_collector, 101 | ); 102 | } 103 | } 104 | 105 | #[test] 106 | fn no_arguments_on_field() { 107 | use crate::validation::test_utils::*; 108 | 109 | let mut plan = create_plan_from_rule(Box::new(UniqueArgumentNames {})); 110 | let errors = test_operation_with_schema( 111 | "{ 112 | field 113 | }", 114 | TEST_SCHEMA, 115 | &mut plan, 116 | ); 117 | 118 | assert_eq!(get_messages(&errors).len(), 0); 119 | } 120 | 121 | #[test] 122 | fn no_arguments_on_directive() { 123 | use crate::validation::test_utils::*; 124 | 125 | let mut plan = create_plan_from_rule(Box::new(UniqueArgumentNames {})); 126 | let errors = test_operation_with_schema( 127 | "{ 128 | field @directive 129 | }", 130 | TEST_SCHEMA, 131 | &mut plan, 132 | ); 133 | 134 | assert_eq!(get_messages(&errors).len(), 0); 135 | } 136 | 137 | #[test] 138 | fn argument_on_field() { 139 | use crate::validation::test_utils::*; 140 | 141 | let mut plan = create_plan_from_rule(Box::new(UniqueArgumentNames {})); 142 | let errors = test_operation_with_schema( 143 | "{ 144 | field(arg: \"value\") 145 | }", 146 | TEST_SCHEMA, 147 | &mut plan, 148 | ); 149 | 150 | assert_eq!(get_messages(&errors).len(), 0); 151 | } 152 | 153 | #[test] 154 | fn argument_on_directive() { 155 | use crate::validation::test_utils::*; 156 | 157 | let mut plan = create_plan_from_rule(Box::new(UniqueArgumentNames {})); 158 | let errors = test_operation_with_schema( 159 | "{ 160 | field @directive(arg: \"value\") 161 | }", 162 | TEST_SCHEMA, 163 | &mut plan, 164 | ); 165 | 166 | assert_eq!(get_messages(&errors).len(), 0); 167 | } 168 | 169 | #[test] 170 | fn same_argument_on_two_fields() { 171 | use crate::validation::test_utils::*; 172 | 173 | let mut plan = create_plan_from_rule(Box::new(UniqueArgumentNames {})); 174 | let errors = test_operation_with_schema( 175 | "{ 176 | one: field(arg: \"value\") 177 | two: field(arg: \"value\") 178 | }", 179 | TEST_SCHEMA, 180 | &mut plan, 181 | ); 182 | 183 | assert_eq!(get_messages(&errors).len(), 0); 184 | } 185 | 186 | #[test] 187 | fn same_argument_on_field_and_directive() { 188 | use crate::validation::test_utils::*; 189 | 190 | let mut plan = create_plan_from_rule(Box::new(UniqueArgumentNames {})); 191 | let errors = test_operation_with_schema( 192 | "{ 193 | field(arg: \"value\") @directive(arg: \"value\") 194 | }", 195 | TEST_SCHEMA, 196 | &mut plan, 197 | ); 198 | 199 | assert_eq!(get_messages(&errors).len(), 0); 200 | } 201 | 202 | #[test] 203 | fn same_argument_on_two_directives() { 204 | use crate::validation::test_utils::*; 205 | 206 | let mut plan = create_plan_from_rule(Box::new(UniqueArgumentNames {})); 207 | let errors = test_operation_with_schema( 208 | "{ 209 | field @directive1(arg: \"value\") @directive2(arg: \"value\") 210 | }", 211 | TEST_SCHEMA, 212 | &mut plan, 213 | ); 214 | 215 | assert_eq!(get_messages(&errors).len(), 0); 216 | } 217 | 218 | #[test] 219 | fn multiple_field_arguments() { 220 | use crate::validation::test_utils::*; 221 | 222 | let mut plan = create_plan_from_rule(Box::new(UniqueArgumentNames {})); 223 | let errors = test_operation_with_schema( 224 | "{ 225 | field(arg1: \"value\", arg2: \"value\", arg3: \"value\") 226 | }", 227 | TEST_SCHEMA, 228 | &mut plan, 229 | ); 230 | 231 | assert_eq!(get_messages(&errors).len(), 0); 232 | } 233 | 234 | #[test] 235 | fn multiple_directive_argument() { 236 | use crate::validation::test_utils::*; 237 | 238 | let mut plan = create_plan_from_rule(Box::new(UniqueArgumentNames {})); 239 | let errors = test_operation_with_schema( 240 | "{ 241 | field @directive(arg1: \"value\", arg2: \"value\", arg3: \"value\") 242 | }", 243 | TEST_SCHEMA, 244 | &mut plan, 245 | ); 246 | 247 | assert_eq!(get_messages(&errors).len(), 0); 248 | } 249 | 250 | #[test] 251 | fn duplicate_field_arguments() { 252 | use crate::validation::test_utils::*; 253 | 254 | let mut plan = create_plan_from_rule(Box::new(UniqueArgumentNames {})); 255 | let errors = test_operation_with_schema( 256 | "{ 257 | field(arg1: \"value\", arg1: \"value\") 258 | }", 259 | TEST_SCHEMA, 260 | &mut plan, 261 | ); 262 | 263 | let messages = get_messages(&errors); 264 | assert_eq!(messages.len(), 1); 265 | assert_eq!( 266 | messages, 267 | vec!["There can be only one argument named \"arg1\"."] 268 | ); 269 | } 270 | 271 | #[test] 272 | fn many_duplicate_field_arguments() { 273 | use crate::validation::test_utils::*; 274 | 275 | let mut plan = create_plan_from_rule(Box::new(UniqueArgumentNames {})); 276 | let errors = test_operation_with_schema( 277 | "{ 278 | field(arg1: \"value\", arg1: \"value\", arg1: \"value\") 279 | }", 280 | TEST_SCHEMA, 281 | &mut plan, 282 | ); 283 | 284 | let messages = get_messages(&errors); 285 | assert_eq!(messages.len(), 1); 286 | assert_eq!( 287 | messages, 288 | vec!["There can be only one argument named \"arg1\"."] 289 | ); 290 | } 291 | 292 | #[test] 293 | fn duplicate_directive_arguments() { 294 | use crate::validation::test_utils::*; 295 | 296 | let mut plan = create_plan_from_rule(Box::new(UniqueArgumentNames {})); 297 | let errors = test_operation_with_schema( 298 | "{ 299 | field @directive(arg1: \"value\", arg1: \"value\") 300 | }", 301 | TEST_SCHEMA, 302 | &mut plan, 303 | ); 304 | 305 | let messages = get_messages(&errors); 306 | assert_eq!(messages.len(), 1); 307 | assert_eq!( 308 | messages, 309 | vec!["There can be only one argument named \"arg1\"."] 310 | ); 311 | } 312 | 313 | #[test] 314 | fn many_duplicate_directive_arguments() { 315 | use crate::validation::test_utils::*; 316 | 317 | let mut plan = create_plan_from_rule(Box::new(UniqueArgumentNames {})); 318 | let errors = test_operation_with_schema( 319 | "{ 320 | field @directive(arg1: \"value\", arg1: \"value\", arg1: \"value\") 321 | }", 322 | TEST_SCHEMA, 323 | &mut plan, 324 | ); 325 | 326 | let messages = get_messages(&errors); 327 | assert_eq!(messages.len(), 1); 328 | assert_eq!( 329 | messages, 330 | vec!["There can be only one argument named \"arg1\"."] 331 | ); 332 | } 333 | -------------------------------------------------------------------------------- /src/validation/rules/unique_directives_per_location.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | use super::ValidationRule; 4 | use crate::ast::OperationDefinitionExtension; 5 | use crate::static_graphql::query::{ 6 | Directive, Field, FragmentDefinition, FragmentSpread, InlineFragment, OperationDefinition, 7 | }; 8 | use crate::{ 9 | ast::{visit_document, OperationVisitor, OperationVisitorContext}, 10 | validation::utils::{ValidationError, ValidationErrorContext}, 11 | }; 12 | 13 | /// Unique directive names per location 14 | /// 15 | /// A GraphQL document is only valid if all non-repeatable directives at 16 | /// a given location are uniquely named. 17 | /// 18 | /// See https://spec.graphql.org/draft/#sec-Directives-Are-Unique-Per-Location 19 | pub struct UniqueDirectivesPerLocation {} 20 | 21 | impl Default for UniqueDirectivesPerLocation { 22 | fn default() -> Self { 23 | Self::new() 24 | } 25 | } 26 | 27 | impl UniqueDirectivesPerLocation { 28 | pub fn new() -> Self { 29 | UniqueDirectivesPerLocation {} 30 | } 31 | 32 | pub fn check_duplicate_directive( 33 | &self, 34 | ctx: &mut OperationVisitorContext, 35 | err_context: &mut ValidationErrorContext, 36 | directives: &[Directive], 37 | ) { 38 | let mut exists = HashSet::new(); 39 | 40 | for directive in directives { 41 | if let Some(meta_directive) = ctx.directives.get(&directive.name) { 42 | if !meta_directive.repeatable { 43 | if exists.contains(&directive.name) { 44 | err_context.report_error(ValidationError { 45 | error_code: self.error_code(), 46 | locations: vec![directive.position], 47 | message: format!("Duplicate directive \"{}\"", &directive.name), 48 | }); 49 | 50 | continue; 51 | } 52 | 53 | exists.insert(directive.name.clone()); 54 | } 55 | } 56 | } 57 | } 58 | } 59 | 60 | impl<'a> OperationVisitor<'a, ValidationErrorContext> for UniqueDirectivesPerLocation { 61 | fn enter_operation_definition( 62 | &mut self, 63 | ctx: &mut OperationVisitorContext<'a>, 64 | err_ctx: &mut ValidationErrorContext, 65 | operation: &OperationDefinition, 66 | ) { 67 | self.check_duplicate_directive(ctx, err_ctx, operation.directives()); 68 | } 69 | 70 | fn enter_field( 71 | &mut self, 72 | ctx: &mut OperationVisitorContext<'a>, 73 | err_ctx: &mut ValidationErrorContext, 74 | field: &Field, 75 | ) { 76 | self.check_duplicate_directive(ctx, err_ctx, &field.directives); 77 | } 78 | 79 | fn enter_fragment_definition( 80 | &mut self, 81 | ctx: &mut OperationVisitorContext<'a>, 82 | err_ctx: &mut ValidationErrorContext, 83 | fragment: &FragmentDefinition, 84 | ) { 85 | self.check_duplicate_directive(ctx, err_ctx, &fragment.directives); 86 | } 87 | 88 | fn enter_fragment_spread( 89 | &mut self, 90 | ctx: &mut OperationVisitorContext<'a>, 91 | err_ctx: &mut ValidationErrorContext, 92 | fragment_spread: &FragmentSpread, 93 | ) { 94 | self.check_duplicate_directive(ctx, err_ctx, &fragment_spread.directives) 95 | } 96 | 97 | fn enter_inline_fragment( 98 | &mut self, 99 | ctx: &mut OperationVisitorContext<'a>, 100 | err_ctx: &mut ValidationErrorContext, 101 | inline_fragment: &InlineFragment, 102 | ) { 103 | self.check_duplicate_directive(ctx, err_ctx, &inline_fragment.directives) 104 | } 105 | } 106 | 107 | impl ValidationRule for UniqueDirectivesPerLocation { 108 | fn error_code<'a>(&self) -> &'a str { 109 | "UniqueDirectivesPerLocation" 110 | } 111 | 112 | fn validate( 113 | &self, 114 | ctx: &mut OperationVisitorContext, 115 | error_collector: &mut ValidationErrorContext, 116 | ) { 117 | visit_document( 118 | &mut UniqueDirectivesPerLocation::new(), 119 | ctx.operation, 120 | ctx, 121 | error_collector, 122 | ); 123 | } 124 | } 125 | 126 | #[test] 127 | fn no_directives() { 128 | use crate::validation::test_utils::*; 129 | 130 | let mut plan = create_plan_from_rule(Box::new(UniqueDirectivesPerLocation::new())); 131 | let errors = test_operation_with_schema( 132 | "fragment Test on Type { 133 | field 134 | }", 135 | TEST_SCHEMA, 136 | &mut plan, 137 | ); 138 | 139 | assert_eq!(get_messages(&errors).len(), 0); 140 | } 141 | 142 | #[test] 143 | fn unique_directives_in_different_locations() { 144 | use crate::validation::test_utils::*; 145 | 146 | let mut plan = create_plan_from_rule(Box::new(UniqueDirectivesPerLocation::new())); 147 | let errors = test_operation_with_schema( 148 | "fragment Test on Type @directiveA { 149 | field @directiveB 150 | }", 151 | TEST_SCHEMA, 152 | &mut plan, 153 | ); 154 | 155 | assert_eq!(get_messages(&errors).len(), 0); 156 | } 157 | 158 | #[test] 159 | fn unique_directives_in_same_location() { 160 | use crate::validation::test_utils::*; 161 | 162 | let mut plan = create_plan_from_rule(Box::new(UniqueDirectivesPerLocation::new())); 163 | let errors = test_operation_with_schema( 164 | "fragment Test on Type @directiveA @directiveB { 165 | field @directiveA @directiveB 166 | }", 167 | TEST_SCHEMA, 168 | &mut plan, 169 | ); 170 | 171 | assert_eq!(get_messages(&errors).len(), 0); 172 | } 173 | 174 | #[test] 175 | fn same_directives_in_different_locations() { 176 | use crate::validation::test_utils::*; 177 | 178 | let mut plan = create_plan_from_rule(Box::new(UniqueDirectivesPerLocation::new())); 179 | let errors = test_operation_with_schema( 180 | "fragment Test on Type @directiveA { 181 | field @directiveA 182 | }", 183 | TEST_SCHEMA, 184 | &mut plan, 185 | ); 186 | 187 | assert_eq!(get_messages(&errors).len(), 0); 188 | } 189 | 190 | #[test] 191 | fn same_directives_in_similar_locations() { 192 | use crate::validation::test_utils::*; 193 | 194 | let mut plan = create_plan_from_rule(Box::new(UniqueDirectivesPerLocation::new())); 195 | let errors = test_operation_with_schema( 196 | "fragment Test on Type { 197 | field @directive 198 | field @directive 199 | }", 200 | TEST_SCHEMA, 201 | &mut plan, 202 | ); 203 | 204 | assert_eq!(get_messages(&errors).len(), 0); 205 | } 206 | 207 | #[test] 208 | fn repeatable_directives_in_same_location() { 209 | use crate::validation::test_utils::*; 210 | 211 | let mut plan = create_plan_from_rule(Box::new(UniqueDirectivesPerLocation::new())); 212 | let errors = test_operation_with_schema( 213 | "fragment Test on Type @repeatable @repeatable { 214 | field @repeatable @repeatable 215 | }", 216 | TEST_SCHEMA, 217 | &mut plan, 218 | ); 219 | 220 | assert_eq!(get_messages(&errors).len(), 0); 221 | } 222 | 223 | #[test] 224 | fn unknown_directives_must_be_ignored() { 225 | use crate::validation::test_utils::*; 226 | 227 | let mut plan = create_plan_from_rule(Box::new(UniqueDirectivesPerLocation::new())); 228 | let errors = test_operation_with_schema( 229 | "fragment Test on Type @repeatable @repeatable { 230 | field @repeatable @repeatable 231 | }", 232 | TEST_SCHEMA, 233 | &mut plan, 234 | ); 235 | 236 | assert_eq!(get_messages(&errors).len(), 0); 237 | } 238 | 239 | #[test] 240 | fn duplicate_directives_in_one_location() { 241 | use crate::validation::test_utils::*; 242 | 243 | let mut plan = create_plan_from_rule(Box::new(UniqueDirectivesPerLocation::new())); 244 | let errors = test_operation_with_schema( 245 | "fragment Test on Type { 246 | field @onField @onField 247 | }", 248 | TEST_SCHEMA, 249 | &mut plan, 250 | ); 251 | let messages = get_messages(&errors); 252 | assert_eq!(messages.len(), 1); 253 | assert_eq!(messages, vec!["Duplicate directive \"onField\""]) 254 | } 255 | 256 | #[test] 257 | fn many_duplicate_directives_in_one_location() { 258 | use crate::validation::test_utils::*; 259 | 260 | let mut plan = create_plan_from_rule(Box::new(UniqueDirectivesPerLocation::new())); 261 | let errors = test_operation_with_schema( 262 | "fragment Test on Type { 263 | field @onField @onField @onField 264 | }", 265 | TEST_SCHEMA, 266 | &mut plan, 267 | ); 268 | let messages = get_messages(&errors); 269 | assert_eq!(messages.len(), 2); 270 | assert_eq!( 271 | messages, 272 | vec![ 273 | "Duplicate directive \"onField\"", 274 | "Duplicate directive \"onField\"" 275 | ] 276 | ) 277 | } 278 | 279 | #[test] 280 | fn different_duplicate_directives_in_one_location() { 281 | use crate::validation::test_utils::*; 282 | 283 | let mut plan = create_plan_from_rule(Box::new(UniqueDirectivesPerLocation::new())); 284 | let errors = test_operation_with_schema( 285 | "fragment Test on Type { 286 | field @onField @testDirective @onField @testDirective 287 | }", 288 | TEST_SCHEMA, 289 | &mut plan, 290 | ); 291 | let messages = get_messages(&errors); 292 | assert_eq!(messages.len(), 2); 293 | assert_eq!( 294 | messages, 295 | vec![ 296 | "Duplicate directive \"onField\"", 297 | "Duplicate directive \"testDirective\"" 298 | ] 299 | ) 300 | } 301 | 302 | #[test] 303 | fn duplicate_directives_in_many_location() { 304 | use crate::validation::test_utils::*; 305 | 306 | let mut plan = create_plan_from_rule(Box::new(UniqueDirectivesPerLocation::new())); 307 | let errors = test_operation_with_schema( 308 | "fragment Test on Type @onFragmentDefinition @onFragmentDefinition { 309 | field @onField @onField 310 | }", 311 | TEST_SCHEMA, 312 | &mut plan, 313 | ); 314 | let messages = get_messages(&errors); 315 | assert_eq!(messages.len(), 2); 316 | assert_eq!( 317 | messages, 318 | vec![ 319 | "Duplicate directive \"onFragmentDefinition\"", 320 | "Duplicate directive \"onField\"" 321 | ] 322 | ) 323 | } 324 | -------------------------------------------------------------------------------- /src/validation/rules/unique_fragment_names.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use super::ValidationRule; 4 | use crate::ast::{visit_document, AstNodeWithName, OperationVisitor, OperationVisitorContext}; 5 | use crate::static_graphql::query::*; 6 | use crate::validation::utils::{ValidationError, ValidationErrorContext}; 7 | 8 | /// Unique fragment names 9 | /// 10 | /// A GraphQL document is only valid if all defined fragments have unique names. 11 | /// 12 | /// See https://spec.graphql.org/draft/#sec-Fragment-Name-Uniqueness 13 | pub struct UniqueFragmentNames<'a> { 14 | findings_counter: HashMap<&'a str, i32>, 15 | } 16 | 17 | impl<'a> OperationVisitor<'a, ValidationErrorContext> for UniqueFragmentNames<'a> { 18 | fn enter_fragment_definition( 19 | &mut self, 20 | _: &mut OperationVisitorContext, 21 | _: &mut ValidationErrorContext, 22 | fragment: &'a FragmentDefinition, 23 | ) { 24 | if let Some(name) = fragment.node_name() { 25 | self.store_finding(name); 26 | } 27 | } 28 | } 29 | 30 | impl<'a> Default for UniqueFragmentNames<'a> { 31 | fn default() -> Self { 32 | Self::new() 33 | } 34 | } 35 | 36 | impl<'a> UniqueFragmentNames<'a> { 37 | pub fn new() -> Self { 38 | Self { 39 | findings_counter: HashMap::new(), 40 | } 41 | } 42 | 43 | fn store_finding(&mut self, name: &'a str) { 44 | let value = *self.findings_counter.entry(name).or_insert(0); 45 | self.findings_counter.insert(name, value + 1); 46 | } 47 | } 48 | 49 | impl<'u> ValidationRule for UniqueFragmentNames<'u> { 50 | fn error_code<'a>(&self) -> &'a str { 51 | "UniqueFragmentNames" 52 | } 53 | 54 | fn validate( 55 | &self, 56 | ctx: &mut OperationVisitorContext, 57 | error_collector: &mut ValidationErrorContext, 58 | ) { 59 | let mut rule = UniqueFragmentNames::new(); 60 | 61 | visit_document(&mut rule, ctx.operation, ctx, error_collector); 62 | 63 | rule.findings_counter 64 | .into_iter() 65 | .filter(|(_key, value)| *value > 1) 66 | .for_each(|(key, _value)| { 67 | error_collector.report_error(ValidationError { 68 | error_code: self.error_code(), 69 | message: format!("There can be only one fragment named \"{}\".", key), 70 | locations: vec![], 71 | }) 72 | }) 73 | } 74 | } 75 | 76 | #[test] 77 | fn no_fragments() { 78 | use crate::validation::test_utils::*; 79 | 80 | let mut plan = create_plan_from_rule(Box::new(UniqueFragmentNames::new())); 81 | let errors = test_operation_with_schema( 82 | "{ 83 | field 84 | }", 85 | TEST_SCHEMA, 86 | &mut plan, 87 | ); 88 | 89 | assert_eq!(get_messages(&errors).len(), 0); 90 | } 91 | 92 | #[test] 93 | fn one_fragment() { 94 | use crate::validation::test_utils::*; 95 | 96 | let mut plan = create_plan_from_rule(Box::new(UniqueFragmentNames::new())); 97 | let errors = test_operation_with_schema( 98 | "{ 99 | ...fragA 100 | } 101 | fragment fragA on Type { 102 | field 103 | }", 104 | TEST_SCHEMA, 105 | &mut plan, 106 | ); 107 | 108 | assert_eq!(get_messages(&errors).len(), 0); 109 | } 110 | 111 | #[test] 112 | fn many_fragment() { 113 | use crate::validation::test_utils::*; 114 | 115 | let mut plan = create_plan_from_rule(Box::new(UniqueFragmentNames::new())); 116 | let errors = test_operation_with_schema( 117 | "{ 118 | ...fragA 119 | ...fragB 120 | ...fragC 121 | } 122 | fragment fragA on Type { 123 | fieldA 124 | } 125 | fragment fragB on Type { 126 | fieldB 127 | } 128 | fragment fragC on Type { 129 | fieldC 130 | }", 131 | TEST_SCHEMA, 132 | &mut plan, 133 | ); 134 | 135 | assert_eq!(get_messages(&errors).len(), 0); 136 | } 137 | 138 | #[test] 139 | fn inline_fragments_are_always_unique() { 140 | use crate::validation::test_utils::*; 141 | 142 | let mut plan = create_plan_from_rule(Box::new(UniqueFragmentNames::new())); 143 | let errors = test_operation_with_schema( 144 | "{ 145 | ...on Type { 146 | fieldA 147 | } 148 | ...on Type { 149 | fieldB 150 | } 151 | }", 152 | TEST_SCHEMA, 153 | &mut plan, 154 | ); 155 | 156 | assert_eq!(get_messages(&errors).len(), 0); 157 | } 158 | 159 | #[test] 160 | fn fragment_and_operation_named_the_same() { 161 | use crate::validation::test_utils::*; 162 | 163 | let mut plan = create_plan_from_rule(Box::new(UniqueFragmentNames::new())); 164 | let errors = test_operation_with_schema( 165 | "query Foo { 166 | ...Foo 167 | } 168 | fragment Foo on Type { 169 | field 170 | }", 171 | TEST_SCHEMA, 172 | &mut plan, 173 | ); 174 | 175 | assert_eq!(get_messages(&errors).len(), 0); 176 | } 177 | 178 | #[test] 179 | fn fragments_named_the_same() { 180 | use crate::validation::test_utils::*; 181 | 182 | let mut plan = create_plan_from_rule(Box::new(UniqueFragmentNames::new())); 183 | let errors = test_operation_with_schema( 184 | "{ 185 | ...fragA 186 | } 187 | fragment fragA on Type { 188 | fieldA 189 | } 190 | fragment fragA on Type { 191 | fieldB 192 | }", 193 | TEST_SCHEMA, 194 | &mut plan, 195 | ); 196 | 197 | let messages = get_messages(&errors); 198 | assert_eq!(messages.len(), 1); 199 | assert_eq!( 200 | messages, 201 | vec!["There can be only one fragment named \"fragA\"."] 202 | ); 203 | } 204 | 205 | #[test] 206 | fn fragments_named_the_same_without_being_referenced() { 207 | use crate::validation::test_utils::*; 208 | 209 | let mut plan = create_plan_from_rule(Box::new(UniqueFragmentNames::new())); 210 | let errors = test_operation_with_schema( 211 | "fragment fragA on Type { 212 | fieldA 213 | } 214 | fragment fragA on Type { 215 | fieldB 216 | }", 217 | TEST_SCHEMA, 218 | &mut plan, 219 | ); 220 | 221 | let messages = get_messages(&errors); 222 | assert_eq!(messages.len(), 1); 223 | assert_eq!( 224 | messages, 225 | vec!["There can be only one fragment named \"fragA\"."] 226 | ); 227 | } 228 | -------------------------------------------------------------------------------- /src/validation/rules/unique_operation_names.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use super::ValidationRule; 4 | use crate::ast::{visit_document, AstNodeWithName, OperationVisitor, OperationVisitorContext}; 5 | use crate::static_graphql::query::*; 6 | use crate::validation::utils::{ValidationError, ValidationErrorContext}; 7 | 8 | /// Unique operation names 9 | /// 10 | /// A GraphQL document is only valid if all defined operations have unique names. 11 | /// 12 | /// See https://spec.graphql.org/draft/#sec-Operation-Name-Uniqueness 13 | pub struct UniqueOperationNames<'a> { 14 | findings_counter: HashMap<&'a str, i32>, 15 | } 16 | 17 | impl<'a> OperationVisitor<'a, ValidationErrorContext> for UniqueOperationNames<'a> { 18 | fn enter_operation_definition( 19 | &mut self, 20 | _: &mut OperationVisitorContext, 21 | _: &mut ValidationErrorContext, 22 | operation_definition: &'a OperationDefinition, 23 | ) { 24 | if let Some(name) = operation_definition.node_name() { 25 | self.store_finding(name); 26 | } 27 | } 28 | } 29 | 30 | impl<'a> Default for UniqueOperationNames<'a> { 31 | fn default() -> Self { 32 | Self::new() 33 | } 34 | } 35 | 36 | impl<'a> UniqueOperationNames<'a> { 37 | pub fn new() -> Self { 38 | Self { 39 | findings_counter: HashMap::new(), 40 | } 41 | } 42 | 43 | fn store_finding(&mut self, name: &'a str) { 44 | let value = *self.findings_counter.entry(name).or_insert(0); 45 | self.findings_counter.insert(name, value + 1); 46 | } 47 | } 48 | 49 | impl<'u> ValidationRule for UniqueOperationNames<'u> { 50 | fn error_code<'a>(&self) -> &'a str { 51 | "UniqueOperationNames" 52 | } 53 | 54 | fn validate( 55 | &self, 56 | ctx: &mut OperationVisitorContext, 57 | error_collector: &mut ValidationErrorContext, 58 | ) { 59 | let mut rule = UniqueOperationNames::new(); 60 | 61 | visit_document(&mut rule, ctx.operation, ctx, error_collector); 62 | 63 | rule.findings_counter 64 | .into_iter() 65 | .filter(|(_key, value)| *value > 1) 66 | .for_each(|(key, _value)| { 67 | error_collector.report_error(ValidationError { 68 | error_code: self.error_code(), 69 | message: format!("There can be only one operation named \"{}\".", key), 70 | locations: vec![], 71 | }) 72 | }) 73 | } 74 | } 75 | 76 | #[test] 77 | fn no_operations() { 78 | use crate::validation::test_utils::*; 79 | 80 | let mut plan = create_plan_from_rule(Box::new(UniqueOperationNames::new())); 81 | let errors = test_operation_with_schema( 82 | "fragment fragA on Type { 83 | field 84 | }", 85 | TEST_SCHEMA, 86 | &mut plan, 87 | ); 88 | 89 | assert_eq!(get_messages(&errors).len(), 0); 90 | } 91 | 92 | #[test] 93 | fn one_anon_operation() { 94 | use crate::validation::test_utils::*; 95 | 96 | let mut plan = create_plan_from_rule(Box::new(UniqueOperationNames::new())); 97 | let errors = test_operation_with_schema( 98 | "{ 99 | field 100 | }", 101 | TEST_SCHEMA, 102 | &mut plan, 103 | ); 104 | 105 | let messages = get_messages(&errors); 106 | assert_eq!(messages.len(), 0); 107 | } 108 | 109 | #[test] 110 | fn one_named_operation() { 111 | use crate::validation::test_utils::*; 112 | 113 | let mut plan = create_plan_from_rule(Box::new(UniqueOperationNames::new())); 114 | let errors = test_operation_with_schema( 115 | "query Foo { 116 | field 117 | }", 118 | TEST_SCHEMA, 119 | &mut plan, 120 | ); 121 | 122 | let messages = get_messages(&errors); 123 | assert_eq!(messages.len(), 0); 124 | } 125 | 126 | #[test] 127 | fn multiple_operations() { 128 | use crate::validation::test_utils::*; 129 | 130 | let mut plan = create_plan_from_rule(Box::new(UniqueOperationNames::new())); 131 | let errors = test_operation_with_schema( 132 | "query Foo { 133 | field 134 | } 135 | query Bar { 136 | field 137 | }", 138 | TEST_SCHEMA, 139 | &mut plan, 140 | ); 141 | 142 | let messages = get_messages(&errors); 143 | assert_eq!(messages.len(), 0); 144 | } 145 | 146 | #[test] 147 | fn multiple_operations_of_different_types() { 148 | use crate::validation::test_utils::*; 149 | 150 | let mut plan = create_plan_from_rule(Box::new(UniqueOperationNames::new())); 151 | let errors = test_operation_with_schema( 152 | "query Foo { 153 | field 154 | } 155 | mutation Bar { 156 | field 157 | } 158 | subscription Baz { 159 | field 160 | }", 161 | TEST_SCHEMA, 162 | &mut plan, 163 | ); 164 | 165 | let messages = get_messages(&errors); 166 | assert_eq!(messages.len(), 0); 167 | } 168 | 169 | #[test] 170 | fn fragment_and_operation_named_the_same() { 171 | use crate::validation::test_utils::*; 172 | 173 | let mut plan = create_plan_from_rule(Box::new(UniqueOperationNames::new())); 174 | let errors = test_operation_with_schema( 175 | "query Foo { 176 | ...Foo 177 | } 178 | fragment Foo on Type { 179 | field 180 | }", 181 | TEST_SCHEMA, 182 | &mut plan, 183 | ); 184 | 185 | let messages = get_messages(&errors); 186 | assert_eq!(messages.len(), 0); 187 | } 188 | 189 | #[test] 190 | fn multiple_operations_of_same_name() { 191 | use crate::validation::test_utils::*; 192 | 193 | let mut plan = create_plan_from_rule(Box::new(UniqueOperationNames::new())); 194 | let errors = test_operation_with_schema( 195 | "query Foo { 196 | fieldA 197 | } 198 | query Foo { 199 | fieldB 200 | }", 201 | TEST_SCHEMA, 202 | &mut plan, 203 | ); 204 | 205 | let messages = get_messages(&errors); 206 | assert_eq!(messages.len(), 1); 207 | assert_eq!( 208 | messages, 209 | vec!["There can be only one operation named \"Foo\".",] 210 | ); 211 | } 212 | 213 | #[test] 214 | fn multiple_ops_of_same_name_of_different_types_mutation() { 215 | use crate::validation::test_utils::*; 216 | 217 | let mut plan = create_plan_from_rule(Box::new(UniqueOperationNames::new())); 218 | let errors = test_operation_with_schema( 219 | "query Foo { 220 | fieldA 221 | } 222 | mutation Foo { 223 | fieldB 224 | }", 225 | TEST_SCHEMA, 226 | &mut plan, 227 | ); 228 | 229 | let messages = get_messages(&errors); 230 | assert_eq!(messages.len(), 1); 231 | assert_eq!( 232 | messages, 233 | vec!["There can be only one operation named \"Foo\".",] 234 | ); 235 | } 236 | 237 | #[test] 238 | fn multiple_ops_of_same_name_of_different_types_subscription() { 239 | use crate::validation::test_utils::*; 240 | 241 | let mut plan = create_plan_from_rule(Box::new(UniqueOperationNames::new())); 242 | let errors = test_operation_with_schema( 243 | "query Foo { 244 | fieldA 245 | } 246 | subscription Foo { 247 | fieldB 248 | }", 249 | TEST_SCHEMA, 250 | &mut plan, 251 | ); 252 | 253 | let messages = get_messages(&errors); 254 | assert_eq!(messages.len(), 1); 255 | assert_eq!( 256 | messages, 257 | vec!["There can be only one operation named \"Foo\".",] 258 | ); 259 | } 260 | -------------------------------------------------------------------------------- /src/validation/rules/unique_variable_names.rs: -------------------------------------------------------------------------------- 1 | use std::collections::hash_map::Entry; 2 | use std::collections::HashMap; 3 | 4 | use crate::parser::Pos; 5 | 6 | use super::ValidationRule; 7 | use crate::ast::{visit_document, OperationVisitor, OperationVisitorContext}; 8 | use crate::static_graphql::query::*; 9 | use crate::validation::utils::{ValidationError, ValidationErrorContext}; 10 | 11 | /// Unique variable names 12 | /// 13 | /// A GraphQL operation is only valid if all its variables are uniquely named. 14 | /// 15 | /// See https://spec.graphql.org/draft/#sec-Variable-Uniqueness 16 | #[derive(Default)] 17 | pub struct UniqueVariableNames<'a> { 18 | found_records: HashMap<&'a str, Pos>, 19 | } 20 | 21 | impl<'a> UniqueVariableNames<'a> { 22 | pub fn new() -> Self { 23 | UniqueVariableNames { 24 | found_records: HashMap::new(), 25 | } 26 | } 27 | } 28 | 29 | impl<'a> OperationVisitor<'a, ValidationErrorContext> for UniqueVariableNames<'a> { 30 | fn enter_operation_definition( 31 | &mut self, 32 | _: &mut OperationVisitorContext, 33 | _: &mut ValidationErrorContext, 34 | _operation_definition: &OperationDefinition, 35 | ) { 36 | self.found_records.clear(); 37 | } 38 | 39 | fn enter_variable_definition( 40 | &mut self, 41 | _: &mut OperationVisitorContext, 42 | user_context: &mut ValidationErrorContext, 43 | variable_definition: &'a VariableDefinition, 44 | ) { 45 | let error_code = self.error_code(); 46 | match self.found_records.entry(&variable_definition.name) { 47 | Entry::Occupied(entry) => user_context.report_error(ValidationError { 48 | error_code, 49 | locations: vec![*entry.get(), variable_definition.position], 50 | message: format!( 51 | "There can only be one variable named \"${}\".", 52 | variable_definition.name 53 | ), 54 | }), 55 | Entry::Vacant(entry) => { 56 | entry.insert(variable_definition.position); 57 | } 58 | }; 59 | } 60 | } 61 | 62 | impl<'v> ValidationRule for UniqueVariableNames<'v> { 63 | fn error_code<'a>(&self) -> &'a str { 64 | "UniqueVariableNames" 65 | } 66 | 67 | fn validate( 68 | &self, 69 | ctx: &mut OperationVisitorContext, 70 | error_collector: &mut ValidationErrorContext, 71 | ) { 72 | visit_document( 73 | &mut UniqueVariableNames::new(), 74 | ctx.operation, 75 | ctx, 76 | error_collector, 77 | ); 78 | } 79 | } 80 | 81 | #[test] 82 | fn unique_variable_names() { 83 | use crate::validation::test_utils::*; 84 | 85 | let mut plan = create_plan_from_rule(Box::new(UniqueVariableNames::new())); 86 | let errors = test_operation_with_schema( 87 | "query A($x: Int, $y: String) { __typename } 88 | query B($x: String, $y: Int) { __typename }", 89 | TEST_SCHEMA, 90 | &mut plan, 91 | ); 92 | 93 | assert_eq!(get_messages(&errors).len(), 0); 94 | } 95 | 96 | #[test] 97 | fn duplicate_variable_names() { 98 | use crate::validation::test_utils::*; 99 | 100 | let mut plan = create_plan_from_rule(Box::new(UniqueVariableNames::new())); 101 | let errors = test_operation_with_schema( 102 | "query A($x: Int, $x: Int, $x: String) { __typename } 103 | query B($y: String, $y: Int) { __typename } 104 | query C($z: Int, $z: Int) { __typename }", 105 | TEST_SCHEMA, 106 | &mut plan, 107 | ); 108 | 109 | let messages = get_messages(&errors); 110 | 111 | assert_eq!(messages.len(), 4); 112 | assert!(messages.contains(&&"There can only be one variable named \"$x\".".to_owned())); 113 | assert!(messages.contains(&&"There can only be one variable named \"$y\".".to_owned())); 114 | assert!(messages.contains(&&"There can only be one variable named \"$z\".".to_owned())); 115 | } 116 | -------------------------------------------------------------------------------- /src/validation/rules/variables_are_input_types.rs: -------------------------------------------------------------------------------- 1 | use super::ValidationRule; 2 | use crate::ast::{ 3 | visit_document, OperationVisitor, OperationVisitorContext, SchemaDocumentExtension, 4 | TypeDefinitionExtension, TypeExtension, 5 | }; 6 | use crate::validation::utils::ValidationError; 7 | use crate::validation::utils::ValidationErrorContext; 8 | 9 | /// Variables are input types 10 | /// 11 | /// A GraphQL operation is only valid if all the variables it defines are of 12 | /// input types (scalar, enum, or input object). 13 | /// 14 | /// See https://spec.graphql.org/draft/#sec-Variables-Are-Input-Types 15 | #[derive(Default)] 16 | pub struct VariablesAreInputTypes; 17 | 18 | impl VariablesAreInputTypes { 19 | pub fn new() -> Self { 20 | VariablesAreInputTypes 21 | } 22 | } 23 | 24 | impl<'a> OperationVisitor<'a, ValidationErrorContext> for VariablesAreInputTypes { 25 | fn enter_variable_definition( 26 | &mut self, 27 | context: &mut OperationVisitorContext, 28 | user_context: &mut ValidationErrorContext, 29 | variable_definition: &crate::static_graphql::query::VariableDefinition, 30 | ) { 31 | if let Some(var_schema_type) = context 32 | .schema 33 | .type_by_name(variable_definition.var_type.inner_type()) 34 | { 35 | if !var_schema_type.is_input_type() { 36 | user_context.report_error(ValidationError { 37 | error_code: self.error_code(), 38 | message: format!( 39 | "Variable \"${}\" cannot be non-input type \"{}\".", 40 | variable_definition.name, variable_definition.var_type 41 | ), 42 | locations: vec![variable_definition.position], 43 | }) 44 | } 45 | } 46 | } 47 | } 48 | 49 | impl ValidationRule for VariablesAreInputTypes { 50 | fn error_code<'a>(&self) -> &'a str { 51 | "VariablesAreInputTypes" 52 | } 53 | 54 | fn validate( 55 | &self, 56 | ctx: &mut OperationVisitorContext, 57 | error_collector: &mut ValidationErrorContext, 58 | ) { 59 | visit_document( 60 | &mut VariablesAreInputTypes::new(), 61 | ctx.operation, 62 | ctx, 63 | error_collector, 64 | ); 65 | } 66 | } 67 | 68 | #[test] 69 | fn unknown_types_are_ignored() { 70 | use crate::validation::test_utils::*; 71 | 72 | let mut plan = create_plan_from_rule(Box::new(VariablesAreInputTypes {})); 73 | let errors = test_operation_with_schema( 74 | " 75 | query Foo($a: Unknown, $b: [[Unknown!]]!) { 76 | field(a: $a, b: $b) 77 | }", 78 | TEST_SCHEMA, 79 | &mut plan, 80 | ); 81 | 82 | let messages = get_messages(&errors); 83 | assert_eq!(messages.len(), 0); 84 | } 85 | 86 | #[test] 87 | fn input_types_are_valid() { 88 | use crate::validation::test_utils::*; 89 | 90 | let mut plan = create_plan_from_rule(Box::new(VariablesAreInputTypes {})); 91 | let errors = test_operation_with_schema( 92 | " 93 | query Foo($a: String, $b: [Boolean!]!, $c: ComplexInput) { 94 | field(a: $a, b: $b, c: $c) 95 | }", 96 | TEST_SCHEMA, 97 | &mut plan, 98 | ); 99 | 100 | let messages = get_messages(&errors); 101 | assert_eq!(messages.len(), 0); 102 | } 103 | 104 | #[test] 105 | fn output_types_are_invalid() { 106 | use crate::validation::test_utils::*; 107 | 108 | let mut plan = create_plan_from_rule(Box::new(VariablesAreInputTypes {})); 109 | let errors = test_operation_with_schema( 110 | " 111 | query Foo($a: Dog, $b: [[CatOrDog!]]!, $c: Pet) { 112 | field(a: $a, b: $b, c: $c) 113 | }", 114 | TEST_SCHEMA, 115 | &mut plan, 116 | ); 117 | 118 | let messages = get_messages(&errors); 119 | assert_eq!(messages.len(), 3); 120 | assert_eq!( 121 | messages, 122 | vec![ 123 | "Variable \"$a\" cannot be non-input type \"Dog\".", 124 | "Variable \"$b\" cannot be non-input type \"[[CatOrDog!]]!\".", 125 | "Variable \"$c\" cannot be non-input type \"Pet\".", 126 | ] 127 | ); 128 | } 129 | -------------------------------------------------------------------------------- /src/validation/test_utils.rs: -------------------------------------------------------------------------------- 1 | use super::rules::ValidationRule; 2 | use super::utils::ValidationError; 3 | use super::validate::validate; 4 | use super::validate::ValidationPlan; 5 | 6 | #[cfg(test)] 7 | pub static INTROSPECTION_SCHEMA: &str = " 8 | directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT 9 | directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT 10 | 11 | scalar Boolean 12 | scalar Float 13 | scalar Int 14 | scalar ID 15 | scalar String 16 | 17 | type Query { 18 | __schema: __Schema! 19 | __type(name: String!): __Type 20 | } 21 | 22 | type __Schema { 23 | types: [__Type!]! 24 | queryType: __Type! 25 | mutationType: __Type 26 | subscriptionType: __Type 27 | directives: [__Directive!]! 28 | } 29 | 30 | type __Type { 31 | kind: __TypeKind! 32 | name: String 33 | description: String 34 | 35 | # OBJECT and INTERFACE only 36 | fields(includeDeprecated: Boolean = false): [__Field!] 37 | 38 | # OBJECT only 39 | interfaces: [__Type!] 40 | 41 | # INTERFACE and UNION only 42 | possibleTypes: [__Type!] 43 | 44 | # ENUM only 45 | enumValues(includeDeprecated: Boolean = false): [__EnumValue!] 46 | 47 | # INPUT_OBJECT only 48 | inputFields: [__InputValue!] 49 | 50 | # NON_NULL and LIST only 51 | ofType: __Type 52 | } 53 | 54 | type __Field { 55 | name: String! 56 | description: String 57 | args: [__InputValue!]! 58 | type: __Type! 59 | isDeprecated: Boolean! 60 | deprecationReason: String 61 | } 62 | 63 | type __InputValue { 64 | name: String! 65 | description: String 66 | type: __Type! 67 | defaultValue: String 68 | } 69 | 70 | type __EnumValue { 71 | name: String! 72 | description: String 73 | isDeprecated: Boolean! 74 | deprecationReason: String 75 | } 76 | 77 | enum __TypeKind { 78 | SCALAR 79 | OBJECT 80 | INTERFACE 81 | UNION 82 | ENUM 83 | INPUT_OBJECT 84 | LIST 85 | NON_NULL 86 | } 87 | 88 | type __Directive { 89 | name: String! 90 | description: String 91 | locations: [__DirectiveLocation!]! 92 | args: [__InputValue!]! 93 | } 94 | 95 | enum __DirectiveLocation { 96 | QUERY 97 | MUTATION 98 | SUBSCRIPTION 99 | FIELD 100 | FRAGMENT_DEFINITION 101 | FRAGMENT_SPREAD 102 | INLINE_FRAGMENT 103 | SCHEMA 104 | SCALAR 105 | OBJECT 106 | FIELD_DEFINITION 107 | ARGUMENT_DEFINITION 108 | INTERFACE 109 | UNION 110 | ENUM 111 | ENUM_VALUE 112 | INPUT_OBJECT 113 | INPUT_FIELD_DEFINITION 114 | }"; 115 | 116 | #[cfg(test)] 117 | pub static TEST_SCHEMA: &str = " 118 | interface Mammal { 119 | mother: Mammal 120 | father: Mammal 121 | } 122 | interface Pet { 123 | name(surname: Boolean): String 124 | } 125 | interface Canine implements Mammal { 126 | name(surname: Boolean): String 127 | mother: Canine 128 | father: Canine 129 | } 130 | enum DogCommand { 131 | SIT 132 | HEEL 133 | DOWN 134 | } 135 | type Dog implements Pet & Mammal & Canine { 136 | name(surname: Boolean): String 137 | nickname: String 138 | barkVolume: Int 139 | barks: Boolean 140 | doesKnowCommand(dogCommand: DogCommand): Boolean 141 | isHouseTrained(atOtherHomes: Boolean = true): Boolean 142 | isAtLocation(x: Int, y: Int): Boolean 143 | mother: Dog 144 | father: Dog 145 | } 146 | type Cat implements Pet { 147 | name(surname: Boolean): String 148 | nickname: String 149 | meows: Boolean 150 | meowsVolume: Int 151 | furColor: FurColor 152 | } 153 | union CatOrDog = Cat | Dog 154 | type Human { 155 | name(surname: Boolean): String 156 | pets: [Pet] 157 | relatives: [Human] 158 | } 159 | enum FurColor { 160 | BROWN 161 | BLACK 162 | TAN 163 | SPOTTED 164 | NO_FUR 165 | UNKNOWN 166 | } 167 | input ComplexInput { 168 | requiredField: Boolean! 169 | nonNullField: Boolean! = false 170 | intField: Int 171 | stringField: String 172 | booleanField: Boolean 173 | stringListField: [String] 174 | } 175 | type ComplicatedArgs { 176 | # TODO List 177 | # TODO Coercion 178 | # TODO NotNulls 179 | intArgField(intArg: Int): String 180 | nonNullIntArgField(nonNullIntArg: Int!): String 181 | stringArgField(stringArg: String): String 182 | booleanArgField(booleanArg: Boolean): String 183 | enumArgField(enumArg: FurColor): String 184 | floatArgField(floatArg: Float): String 185 | idArgField(idArg: ID): String 186 | stringListArgField(stringListArg: [String]): String 187 | stringListNonNullArgField(stringListNonNullArg: [String!]): String 188 | complexArgField(complexArg: ComplexInput): String 189 | multipleReqs(req1: Int!, req2: Int!): String 190 | nonNullFieldWithDefault(arg: Int! = 0): String 191 | multipleOpts(opt1: Int = 0, opt2: Int = 0): String 192 | multipleOptAndReq(req1: Int!, req2: Int!, opt1: Int = 0, opt2: Int = 0): String 193 | } 194 | type QueryRoot { 195 | human(id: ID): Human 196 | dog: Dog 197 | cat: Cat 198 | pet: Pet 199 | catOrDog: CatOrDog 200 | complicatedArgs: ComplicatedArgs 201 | } 202 | 203 | type SubscriptionRoot { 204 | fieldB: String 205 | } 206 | 207 | type MutationRoot { 208 | fieldB: String 209 | } 210 | 211 | schema { 212 | subscription: SubscriptionRoot 213 | mutation: MutationRoot 214 | query: QueryRoot 215 | } 216 | directive @onField on FIELD 217 | directive @onQuery on QUERY 218 | directive @onMutation on MUTATION 219 | directive @onSubscription on SUBSCRIPTION 220 | directive @onFragmentDefinition on FRAGMENT_DEFINITION 221 | directive @onFragmentSpread on FRAGMENT_SPREAD 222 | directive @onInlineFragment on INLINE_FRAGMENT 223 | directive @testDirective on FIELD | FRAGMENT_DEFINITION 224 | 225 | # doesn't work see https://github.com/graphql-rust/graphql-parser/issues/60 226 | # directive @onVariableDefinition on VARIABLE_DEFINITION 227 | 228 | directive @repeatable repeatable on FIELD | FRAGMENT_DEFINITION 229 | "; 230 | 231 | #[cfg(test)] 232 | pub fn create_plan_from_rule(rule: Box) -> ValidationPlan { 233 | let mut rules = Vec::new(); 234 | rules.push(rule); 235 | 236 | ValidationPlan { rules } 237 | } 238 | 239 | #[cfg(test)] 240 | pub fn get_messages(validation_errors: &[ValidationError]) -> Vec<&String> { 241 | validation_errors 242 | .iter() 243 | .map(|m| &m.message) 244 | .collect::>() 245 | } 246 | 247 | #[cfg(test)] 248 | pub fn test_operation_without_schema<'a>( 249 | operation: &'a str, 250 | plan: &'a mut ValidationPlan, 251 | ) -> Vec { 252 | let schema_ast = crate::parser::parse_schema( 253 | " 254 | type Query { 255 | dummy: String 256 | } 257 | ", 258 | ) 259 | .expect("Failed to parse schema"); 260 | 261 | let operation_ast = crate::parser::parse_query(operation).unwrap().into_static(); 262 | 263 | validate(&schema_ast, &operation_ast, plan) 264 | } 265 | 266 | #[cfg(test)] 267 | fn string_to_static_str(s: String) -> &'static str { 268 | Box::leak(s.into_boxed_str()) 269 | } 270 | 271 | #[cfg(test)] 272 | pub fn test_operation_with_schema<'a>( 273 | operation: &'a str, 274 | schema: &'a str, 275 | plan: &'a ValidationPlan, 276 | ) -> Vec { 277 | let schema_clone = string_to_static_str(schema.to_string() + INTROSPECTION_SCHEMA); 278 | let schema_ast = crate::parser::parse_schema(schema_clone).expect("Failed to parse schema"); 279 | 280 | let operation_ast = crate::parser::parse_query(operation).unwrap().into_static(); 281 | 282 | validate(&schema_ast, &operation_ast, plan) 283 | } 284 | -------------------------------------------------------------------------------- /src/validation/utils.rs: -------------------------------------------------------------------------------- 1 | use crate::parser::Pos; 2 | use serde::ser::*; 3 | use serde::{Serialize, Serializer}; 4 | use serde_with::{serde_as, SerializeAs}; 5 | use std::fmt::Debug; 6 | 7 | #[derive(Debug, Default)] 8 | pub struct ValidationErrorContext { 9 | pub errors: Vec, 10 | } 11 | 12 | impl ValidationErrorContext { 13 | pub fn new() -> ValidationErrorContext { 14 | ValidationErrorContext { errors: vec![] } 15 | } 16 | 17 | pub fn report_error(&mut self, error: ValidationError) { 18 | self.errors.push(error); 19 | } 20 | } 21 | 22 | struct PositionDef; 23 | 24 | impl SerializeAs for PositionDef { 25 | fn serialize_as(value: &Pos, serializer: S) -> Result 26 | where 27 | S: Serializer, 28 | { 29 | let mut s = serializer.serialize_map(Some(2))?; 30 | s.serialize_entry("line", &value.line)?; 31 | s.serialize_entry("column", &value.column)?; 32 | s.end() 33 | } 34 | } 35 | 36 | #[serde_as] 37 | #[derive(Serialize, Debug, Clone)] 38 | pub struct ValidationError { 39 | #[serde_as(as = "Vec")] 40 | pub locations: Vec, 41 | pub message: String, 42 | #[serde(skip_serializing)] 43 | pub error_code: &'static str, 44 | } 45 | 46 | #[test] 47 | fn serialization_test() { 48 | let error = ValidationError { 49 | locations: vec![Pos { line: 1, column: 2 }], 50 | message: "test".to_string(), 51 | error_code: "test", 52 | }; 53 | let serialized = serde_json::to_string(&error).unwrap(); 54 | assert_eq!( 55 | serialized, 56 | r#"{"locations":[{"line":1,"column":2}],"message":"test"}"# 57 | ); 58 | } 59 | 60 | #[test] 61 | fn serialization_test_vec() { 62 | let error = ValidationError { 63 | locations: vec![Pos { line: 1, column: 2 }], 64 | message: "test".to_string(), 65 | error_code: "test", 66 | }; 67 | let serialized = serde_json::to_string(&vec![error]).unwrap(); 68 | assert_eq!( 69 | serialized, 70 | r#"[{"locations":[{"line":1,"column":2}],"message":"test"}]"# 71 | ); 72 | } 73 | -------------------------------------------------------------------------------- /src/validation/validate.rs: -------------------------------------------------------------------------------- 1 | use super::{ 2 | rules::ValidationRule, 3 | utils::{ValidationError, ValidationErrorContext}, 4 | }; 5 | 6 | use crate::{ 7 | ast::OperationVisitorContext, 8 | static_graphql::{query, schema}, 9 | }; 10 | 11 | pub struct ValidationPlan { 12 | pub rules: Vec>, 13 | } 14 | 15 | impl ValidationPlan { 16 | pub fn new() -> Self { 17 | Self { rules: vec![] } 18 | } 19 | 20 | pub fn from(rules: Vec>) -> Self { 21 | Self { rules } 22 | } 23 | 24 | pub fn add_rule(&mut self, rule: Box) { 25 | self.rules.push(rule); 26 | } 27 | } 28 | 29 | impl Default for ValidationPlan { 30 | fn default() -> Self { 31 | Self::new() 32 | } 33 | } 34 | 35 | pub fn validate<'a>( 36 | schema: &'a schema::Document, 37 | operation: &'a query::Document, 38 | validation_plan: &'a ValidationPlan, 39 | ) -> Vec { 40 | let mut error_collector = ValidationErrorContext::new(); 41 | let mut validation_context = OperationVisitorContext::new(operation, schema); 42 | 43 | validation_plan 44 | .rules 45 | .iter() 46 | .for_each(|rule| rule.validate(&mut validation_context, &mut error_collector)); 47 | 48 | error_collector.errors 49 | } 50 | 51 | #[test] 52 | fn cyclic_fragment_should_never_loop() { 53 | use crate::validation::rules::default_rules_validation_plan; 54 | use crate::validation::test_utils::*; 55 | 56 | let mut default_plan = default_rules_validation_plan(); 57 | let errors = test_operation_with_schema( 58 | " 59 | { 60 | dog { 61 | nickname 62 | ...bark 63 | ...parents 64 | } 65 | } 66 | 67 | fragment bark on Dog { 68 | barkVolume 69 | ...parents 70 | } 71 | 72 | fragment parents on Dog { 73 | mother { 74 | ...bark 75 | } 76 | } 77 | 78 | ", 79 | TEST_SCHEMA, 80 | &mut default_plan, 81 | ); 82 | 83 | let messages = get_messages(&errors); 84 | assert_eq!(errors[0].error_code, "NoFragmentsCycle"); 85 | assert_eq!(messages.len(), 1); 86 | assert_eq!( 87 | messages, 88 | vec!["Cannot spread fragment \"bark\" within itself via \"parents\"."] 89 | ) 90 | } 91 | 92 | #[test] 93 | fn simple_self_reference_fragment_should_not_loop() { 94 | use crate::validation::rules::default_rules_validation_plan; 95 | use crate::validation::test_utils::*; 96 | 97 | let mut default_plan = default_rules_validation_plan(); 98 | let errors = test_operation_with_schema( 99 | " 100 | query dog { 101 | dog { 102 | ...DogFields 103 | } 104 | } 105 | 106 | fragment DogFields on Dog { 107 | mother { 108 | ...DogFields 109 | } 110 | father { 111 | ...DogFields 112 | } 113 | } 114 | ", 115 | TEST_SCHEMA, 116 | &mut default_plan, 117 | ); 118 | 119 | let messages = get_messages(&errors); 120 | assert_eq!(messages.len(), 2); 121 | assert_eq!( 122 | messages, 123 | vec![ 124 | "Cannot spread fragment \"DogFields\" within itself.", 125 | "Cannot spread fragment \"DogFields\" within itself." 126 | ] 127 | ) 128 | } 129 | 130 | #[test] 131 | fn fragment_loop_through_multiple_frags() { 132 | use crate::validation::rules::default_rules_validation_plan; 133 | use crate::validation::test_utils::*; 134 | 135 | let mut default_plan = default_rules_validation_plan(); 136 | let errors = test_operation_with_schema( 137 | " 138 | query dog { 139 | dog { 140 | ...DogFields1 141 | } 142 | } 143 | 144 | fragment DogFields1 on Dog { 145 | barks 146 | ...DogFields2 147 | } 148 | 149 | fragment DogFields2 on Dog { 150 | barkVolume 151 | ...DogFields3 152 | } 153 | 154 | fragment DogFields3 on Dog { 155 | name 156 | ...DogFields1 157 | } 158 | ", 159 | TEST_SCHEMA, 160 | &mut default_plan, 161 | ); 162 | 163 | let messages = get_messages(&errors); 164 | assert_eq!(messages.len(), 1); 165 | assert_eq!( 166 | messages, 167 | vec![ 168 | "Cannot spread fragment \"DogFields1\" within itself via \"DogFields2\", \"DogFields3\"." 169 | ] 170 | ) 171 | } 172 | --------------------------------------------------------------------------------