Skip to content

Commit

Permalink
Merge pull request #19 from baransu/feature/better-args-validation
Browse files Browse the repository at this point in the history
Validate required arguments on fields
  • Loading branch information
baransu authored Oct 7, 2019
2 parents e32de52 + 4a68335 commit 0590a64
Show file tree
Hide file tree
Showing 8 changed files with 142 additions and 54 deletions.
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ some unsupported areas:
- Interfaces are also converted into polymorphic variants. Overlapping interface
selections and other more uncommon use cases are not yet supported.
- Basic fragment support
- Required arguments validation - you're not going to miss required arguments on any field.

## Extra features

Expand Down Expand Up @@ -268,8 +269,11 @@ By default graphql_ppx uses `graphql_schema.json` filed from your root directory

```
npm install -g esy@latest
esy @402 install
esy @402 dune build -p graphql_ppx
# or
esy install
esy build
esy dune build -p graphql_ppx
```

## Running tests
Expand Down
115 changes: 64 additions & 51 deletions graphql_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -517,7 +517,7 @@
{
"kind": "SCALAR",
"name": "Int",
"description": "The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1. ",
"description": "The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1.",
"fields": null,
"inputFields": null,
"interfaces": null,
Expand All @@ -527,7 +527,7 @@
{
"kind": "SCALAR",
"name": "Float",
"description": "The `Float` scalar type represents signed double-precision fractional values as specified by [IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point). ",
"description": "The `Float` scalar type represents signed double-precision fractional values as specified by [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point).",
"fields": null,
"inputFields": null,
"interfaces": null,
Expand Down Expand Up @@ -1202,6 +1202,61 @@
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "optionalInputArgs",
"description": null,
"args": [
{
"name": "required",
"description": null,
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "optional",
"description": null,
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "anotherRequired",
"description": null,
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"defaultValue": null
}
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
Expand Down Expand Up @@ -2013,54 +2068,6 @@
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "onOperation",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
}
},
"isDeprecated": true,
"deprecationReason": "Use `locations`."
},
{
"name": "onFragment",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
}
},
"isDeprecated": true,
"deprecationReason": "Use `locations`."
},
{
"name": "onField",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
}
},
"isDeprecated": true,
"deprecationReason": "Use `locations`."
}
],
"inputFields": null,
Expand Down Expand Up @@ -2118,6 +2125,12 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "VARIABLE_DEFINITION",
"description": "Location adjacent to a variable definition.",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "SCHEMA",
"description": "Location adjacent to a schema definition.",
Expand Down Expand Up @@ -2489,7 +2502,7 @@
"args": [
{
"name": "reason",
"description": "Explains why this element was deprecated, usually also including a suggestion for how to access supported similar data. Formatted in [Markdown](https://daringfireball.net/projects/markdown/).",
"description": "Explains why this element was deprecated, usually also including a suggestion for how to access supported similar data. Formatted using the Markdown syntax (as specified by [CommonMark](https://commonmark.org/).",
"type": {
"kind": "SCALAR",
"name": "String",
Expand Down
5 changes: 5 additions & 0 deletions schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ type Query {

type Mutation {
mutationWithError: MutationWithErrorResult!
optionalInputArgs(
required: String!
optional: String
anotherRequired: String!
): String!
}

type Subscription {
Expand Down
5 changes: 5 additions & 0 deletions src/base/option.re
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,8 @@ let unsafe_unwrap =
fun
| None => raise(Option_unwrap_error)
| Some(v) => v;

let get_or_else = default =>
fun
| None => default
| Some(v) => v;
44 changes: 44 additions & 0 deletions src/base/rule_required_arguments.re
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
module Visitor: Traversal_utils.VisitorSig = {
open Traversal_utils;
open Source_pos;
open Graphql_ast;
open Schema;

include AbstractVisitor;

let enter_field = (_self, ctx, def) => {
let field_meta =
Context.parent_type(ctx)
|> Option.flat_map(t => Schema.lookup_field(t, def.item.fd_name.item));

let provided_args =
def.item.fd_arguments
|> Option.map(span => span.item)
|> Option.get_or_else([])
|> List.map(arg => (arg |> fst).item);

let expected_args =
field_meta
|> Option.map(fm => fm.fm_arguments)
|> Option.get_or_else([])
|> List.filter(arg =>
switch(arg.am_arg_type) {
| NonNull(_) => true
| _ => false
});

expected_args
|> List.iter(arg => {
let provided = provided_args |> List.exists(arg_name => arg_name == arg.am_name);
if(!provided) {
let message =
Printf.sprintf("Argument \"%s\" on field \"%s\" not provided", arg.am_name, def.item.fd_name.item);

Context.push_error(ctx, def.span, message);
}
});
};

type t = unit;
let make_self = () => ();
};
5 changes: 4 additions & 1 deletion src/base/validations.re
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ module AllRulesImpl =
(
Multi_visitor.Visitor(
Rule_no_unused_variables.Visitor,
Multi_visitor.NullVisitor,
Multi_visitor.Visitor(
Rule_required_arguments.Visitor,
Multi_visitor.NullVisitor,
)
)
),
);
Expand Down
14 changes: 14 additions & 0 deletions tests_bucklescript/__tests__/mutationWithArgs.re
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
open Jest;
open Expect;

module MyQuery = [%graphql {|
mutation MyMutation($required: String!) {
optionalInputArgs(required: $required, anotherRequired: "val")
}
|}];

describe("Mutation with args", () =>
test("Printed query is a mutation", () =>
MyQuery.query |> Js.String.indexOf("mutation") |> expect |> toBe(0)
)
);
2 changes: 1 addition & 1 deletion tests_bucklescript/run.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ async function test(folder) {
await command(`cp ./${folder}/* .`);
await command("npm install");
await command("npm run test");
await cleanup();
// await cleanup();
}

async function run() {
Expand Down

0 comments on commit 0590a64

Please sign in to comment.