Skip to content

Commit e8f4816

Browse files
committed
feat: implement constraint directive
1 parent 2415dc7 commit e8f4816

File tree

12 files changed

+191
-14
lines changed

12 files changed

+191
-14
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,5 @@
99

1010
# rspec failure tracking
1111
.rspec_status
12+
13+
.byebug_history

.rubocop.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
AllCops:
22
TargetRubyVersion: 3.0.0
3+
NewCops: enable
4+
SuggestExtensions: false
35

46
Style/StringLiterals:
57
Enabled: true
@@ -11,3 +13,11 @@ Style/StringLiteralsInInterpolation:
1113

1214
Layout/LineLength:
1315
Max: 120
16+
17+
Metrics/BlockLength:
18+
Exclude:
19+
- "spec/**/*"
20+
21+
Style/Documentation:
22+
Enabled: false
23+

Gemfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ source "https://rubygems.org"
44

55
gemspec
66

7+
gem "byebug"
78
gem "rake", "~> 13.0"
8-
99
gem "rspec", "~> 3.0"
10-
10+
gem "rspec-json_matcher"
1111
gem "rubocop", "~> 1.21"

Gemfile.lock

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ PATH
22
remote: .
33
specs:
44
graphql-constraint-directive (0.1.0)
5-
graphql (~> 2.0, >= 2.0.16)
5+
graphql (~> 2.0, < 3)
66

77
GEM
88
remote: https://rubygems.org/
99
specs:
10+
amazing_print (1.4.0)
1011
ast (2.4.2)
12+
byebug (11.1.3)
1113
diff-lcs (1.5.0)
1214
graphql (2.0.16)
1315
json (2.6.2)
@@ -27,6 +29,9 @@ GEM
2729
rspec-expectations (3.12.0)
2830
diff-lcs (>= 1.2.0, < 2.0)
2931
rspec-support (~> 3.12.0)
32+
rspec-json_matcher (0.2.0)
33+
amazing_print
34+
json
3035
rspec-mocks (3.12.0)
3136
diff-lcs (>= 1.2.0, < 2.0)
3237
rspec-support (~> 3.12.0)
@@ -50,9 +55,11 @@ PLATFORMS
5055
arm64-darwin-21
5156

5257
DEPENDENCIES
58+
byebug
5359
graphql-constraint-directive!
5460
rake (~> 13.0)
5561
rspec (~> 3.0)
62+
rspec-json_matcher
5663
rubocop (~> 1.21)
5764

5865
BUNDLED WITH

graphql-constraint-directive.gemspec

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ require_relative "lib/graphql/constraint/directive/version"
44

55
Gem::Specification.new do |spec|
66
spec.name = "graphql-constraint-directive"
7-
spec.version = Graphql::Constraint::Directive::VERSION
7+
spec.version = GraphQL::Constraint::Directive::VERSION
88
spec.authors = ["MH4GF"]
99
spec.email = ["[email protected]"]
1010

@@ -31,5 +31,6 @@ Gem::Specification.new do |spec|
3131
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
3232
spec.require_paths = ["lib"]
3333

34-
spec.add_dependency "graphql", "~> 2.0", ">= 2.0.16"
34+
spec.add_runtime_dependency "graphql", "~> 2.0", "< 3"
35+
spec.metadata["rubygems_mfa_required"] = "true"
3536
end

lib/graphql/constraint/directive.rb

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
# frozen_string_literal: true
22

33
require_relative "directive/version"
4+
require_relative "directive/constraint"
45

5-
module Graphql
6+
module GraphQL
67
module Constraint
78
module Directive
89
class Error < StandardError; end
9-
# Your code goes here...
10+
11+
def self.use(schema_defn)
12+
schema_defn.directive(Constraint)
13+
end
1014
end
1115
end
1216
end
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# frozen_string_literal: true
2+
3+
require "graphql"
4+
require "byebug"
5+
6+
module GraphQL
7+
module Constraint
8+
module Directive
9+
class Constraint < GraphQL::Schema::Directive
10+
LENGTH = { min_length: :minimum, max_length: :maximum }.freeze
11+
12+
description "validate GraphQL fields"
13+
argument :max_length, Int, required: false, description: "validate max length"
14+
argument :min_length, Int, required: false, description: "validate min length"
15+
locations INPUT_FIELD_DEFINITION
16+
17+
def initialize(owner, **arguments)
18+
super
19+
validates = validation_config(@arguments.keyword_arguments)
20+
owner.validates(validates)
21+
end
22+
23+
def validation_config(arguments)
24+
{
25+
length: arguments.filter { |key, _| LENGTH.key?(key) }.transform_keys { |key| LENGTH[key] }
26+
}
27+
end
28+
end
29+
end
30+
end
31+
end

lib/graphql/constraint/directive/version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# frozen_string_literal: true
22

3-
module Graphql
3+
module GraphQL
44
module Constraint
55
module Directive
66
VERSION = "0.1.0"
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# frozen_string_literal: true
2+
3+
RSpec.describe GraphQL::Constraint::Directive::Constraint do
4+
let(:query) do
5+
<<~GRAPHQL
6+
mutation($input: SampleMutationInput!) {
7+
sample(input: $input) {
8+
text
9+
extraArgs
10+
}
11+
}
12+
GRAPHQL
13+
end
14+
15+
context "with violated" do
16+
let(:expected) do
17+
{
18+
"data" => {
19+
"sample" => nil
20+
},
21+
"errors" => [
22+
{
23+
"message" => message,
24+
"locations" => [
25+
{
26+
"line" => 2,
27+
"column" => 3
28+
}
29+
],
30+
"path" => [
31+
"sample"
32+
]
33+
}
34+
]
35+
}
36+
end
37+
38+
context "when min is violated" do
39+
let(:variables) { { input: { text: "" } } }
40+
let(:message) { "text is too short (minimum is 1)" }
41+
42+
it "is returns error" do
43+
expect(Schema.execute(query: query, variables: variables).to_json).to be_json_as(expected)
44+
end
45+
end
46+
47+
context "when max is violated" do
48+
let(:variables) { { input: { text: "wow" } } }
49+
let(:message) { "text is too long (maximum is 2)" }
50+
51+
it "is returns error" do
52+
expect(Schema.execute(query: query, variables: variables).to_json).to be_json_as(expected)
53+
end
54+
end
55+
end
56+
57+
context "when valid argument" do
58+
let(:variables) { { input: { text: "yo" } } }
59+
let(:expected) do
60+
{
61+
"data" => {
62+
"sample" => {
63+
"text" => "yo",
64+
"extraArgs" => nil
65+
}
66+
}
67+
}
68+
end
69+
70+
it "returns data" do
71+
expect(Schema.execute(query: query, variables: variables).to_json).to be_json_as(expected)
72+
end
73+
end
74+
75+
context "with other arguments" do
76+
let(:variables) { { input: { extraArgs: "foo" } } }
77+
let(:expected) do
78+
{
79+
"data" => {
80+
"sample" => {
81+
"text" => nil,
82+
"extraArgs" => "foo"
83+
}
84+
}
85+
}
86+
end
87+
88+
it "returns data" do
89+
expect(Schema.execute(query: query, variables: variables).to_json).to be_json_as(expected)
90+
end
91+
end
92+
end
Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
11
# frozen_string_literal: true
22

3-
RSpec.describe Graphql::Constraint::Directive do
3+
RSpec.describe GraphQL::Constraint::Directive do
44
it "has a version number" do
5-
expect(Graphql::Constraint::Directive::VERSION).not_to be nil
6-
end
7-
8-
it "does something useful" do
9-
expect(false).to eq(true)
5+
expect(GraphQL::Constraint::Directive::VERSION).not_to be nil
106
end
117
end

spec/spec_helper.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# frozen_string_literal: true
22

33
require "graphql/constraint/directive"
4+
require "support/schema"
5+
require "rspec/json_matcher"
46

57
RSpec.configure do |config|
68
# Enable flags like --only-failures and --next-failure
@@ -12,4 +14,6 @@
1214
config.expect_with :rspec do |c|
1315
c.syntax = :expect
1416
end
17+
18+
config.include RSpec::JsonMatcher
1519
end

spec/support/schema.rb

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# frozen_string_literal: true
2+
3+
class BaseMutation < GraphQL::Schema::RelayClassicMutation
4+
end
5+
6+
class SampleMutation < BaseMutation
7+
argument :text, String, required: false,
8+
directives: { GraphQL::Constraint::Directive::Constraint => { min_length: 1, max_length: 2 } }
9+
argument :extra_args, String, required: false
10+
11+
field :text, String, null: true
12+
field :extra_args, String, null: true
13+
14+
def resolve(text: nil, extra_args: nil)
15+
{
16+
text: text,
17+
extra_args: extra_args
18+
}
19+
end
20+
end
21+
22+
class MutationType < GraphQL::Schema::Object
23+
field :sample, mutation: SampleMutation
24+
end
25+
26+
class Schema < GraphQL::Schema
27+
mutation MutationType
28+
29+
use GraphQL::Constraint::Directive
30+
end

0 commit comments

Comments
 (0)