graphql-dsl 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/main.yml +23 -0
  3. data/.gitignore +12 -0
  4. data/.rspec +2 -0
  5. data/.rubocop.yml +44 -0
  6. data/.ruby-version +1 -0
  7. data/.yardopts +1 -0
  8. data/CODE_OF_CONDUCT.md +84 -0
  9. data/Gemfile +6 -0
  10. data/Gemfile.lock +64 -0
  11. data/LICENSE.txt +21 -0
  12. data/README.md +899 -0
  13. data/Rakefile +6 -0
  14. data/bin/console +15 -0
  15. data/bin/setup +7 -0
  16. data/graphql-dsl.gemspec +38 -0
  17. data/lib/graphql/dsl/constants.rb +9 -0
  18. data/lib/graphql/dsl/error.rb +32 -0
  19. data/lib/graphql/dsl/formatter/arguments.rb +26 -0
  20. data/lib/graphql/dsl/formatter/directives.rb +52 -0
  21. data/lib/graphql/dsl/formatter/executable_document.rb +22 -0
  22. data/lib/graphql/dsl/formatter/field.rb +49 -0
  23. data/lib/graphql/dsl/formatter/formatter.rb +41 -0
  24. data/lib/graphql/dsl/formatter/fragment_operation.rb +41 -0
  25. data/lib/graphql/dsl/formatter/fragment_spread.rb +25 -0
  26. data/lib/graphql/dsl/formatter/inline_fragment.rb +43 -0
  27. data/lib/graphql/dsl/formatter/operation.rb +60 -0
  28. data/lib/graphql/dsl/formatter/values.rb +146 -0
  29. data/lib/graphql/dsl/formatter/variable_definitions.rb +43 -0
  30. data/lib/graphql/dsl/nodes/containers/directive.rb +46 -0
  31. data/lib/graphql/dsl/nodes/containers/variable_definition.rb +52 -0
  32. data/lib/graphql/dsl/nodes/executable_document.rb +69 -0
  33. data/lib/graphql/dsl/nodes/field.rb +39 -0
  34. data/lib/graphql/dsl/nodes/fragment_operation.rb +36 -0
  35. data/lib/graphql/dsl/nodes/fragment_spread.rb +24 -0
  36. data/lib/graphql/dsl/nodes/inline_fragment.rb +34 -0
  37. data/lib/graphql/dsl/nodes/mixins/selection_set.rb +106 -0
  38. data/lib/graphql/dsl/nodes/node.rb +39 -0
  39. data/lib/graphql/dsl/nodes/operation.rb +61 -0
  40. data/lib/graphql/dsl/version.rb +9 -0
  41. data/lib/graphql/dsl.rb +230 -0
  42. data/lib/graphql-dsl.rb +3 -0
  43. data/lib/graphql_dsl.rb +36 -0
  44. data/tasks/readme/update.rake +143 -0
  45. metadata +173 -0
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'graphql_dsl'
4
+ require 'bundler/gem_tasks'
5
+
6
+ Rake.add_rakelib 'tasks/readme'
data/bin/console ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'graphql_dsl'
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require 'irb'
15
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle config set --local path 'vendor/bundle'
7
+ bundle install
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/graphql/dsl/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'graphql-dsl'
7
+ spec.version = GraphQL::DSL::VERSION
8
+ spec.authors = ['Maxim Dobryakov']
9
+ spec.email = ['maxim.dobryakov@gmail.com']
10
+
11
+ spec.summary = 'GraphQL DSL'
12
+ spec.description = 'Ruby DSL for GraphQL'
13
+ spec.homepage = 'https://github.com/maxd/graphql-dsl'
14
+ spec.license = 'MIT'
15
+ spec.required_ruby_version = Gem::Requirement.new('>= 2.6.0')
16
+
17
+ # spec.metadata['allowed_push_host'] = "Set to 'http://mygemserver.com'"
18
+
19
+ spec.metadata['homepage_uri'] = spec.homepage
20
+ spec.metadata['source_code_uri'] = 'https://github.com/maxd/graphql-dsl'
21
+ spec.metadata['changelog_uri'] = 'https://github.com/maxd/graphql-dsl/releases'
22
+
23
+ # Specify which files should be added to the gem when it is released.
24
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
25
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
26
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
27
+ end
28
+ spec.bindir = 'exe'
29
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
30
+ spec.require_paths = ['lib']
31
+
32
+ spec.add_development_dependency 'rake', '~> 13.0'
33
+ spec.add_development_dependency 'rspec', '~> 3.10'
34
+ spec.add_development_dependency 'rubocop', '~> 1.18'
35
+ spec.add_development_dependency 'rubocop-rake', '~> 0.6'
36
+ spec.add_development_dependency 'rubocop-rspec', '~> 2.4'
37
+ spec.add_development_dependency 'yard', '~> 0.9'
38
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module DSL
5
+ ##
6
+ # Help to mark default parameter as undefined
7
+ UNDEFINED = Object.new
8
+ end
9
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module DSL
5
+ ##
6
+ # GraphQL DSL error
7
+ class Error < StandardError
8
+ ##
9
+ # @return [Hash] additional error arguments
10
+ attr_reader :arguments
11
+
12
+ ##
13
+ # Create GraphQL error
14
+ #
15
+ # @param msg [String] error message
16
+ # @param arguments [Hash] additional error arguments
17
+ def initialize(msg = nil, **arguments)
18
+ super(msg)
19
+
20
+ @arguments = arguments
21
+ end
22
+
23
+ ##
24
+ # Error message
25
+ #
26
+ # @return [String] error message
27
+ def message
28
+ super + (arguments ? "\nArguments: #{arguments}" : '')
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module DSL
5
+ class Formatter # rubocop:disable Style/Documentation
6
+ private
7
+
8
+ ##
9
+ # Format arguments as string
10
+ #
11
+ # @param arguments [Hash] arguments
12
+ # @param is_const [Boolean] allow to use variables or not
13
+ #
14
+ # @return [String] representation of arguments as string
15
+ def format_arguments(arguments, is_const)
16
+ return nil if arguments.empty?
17
+
18
+ result = arguments.map do |name, value|
19
+ "#{name}: #{format_value(value, is_const)}"
20
+ end
21
+
22
+ "(#{result.join(', ')})"
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module DSL
5
+ class Formatter # rubocop:disable Style/Documentation
6
+ private
7
+
8
+ ##
9
+ # Format directives to string
10
+ #
11
+ # @param directives [Array] directives
12
+ # @param is_const [Boolean] allow to use variables or not
13
+ #
14
+ # @return [String] representation of directives as string
15
+ def format_directives(directives, is_const)
16
+ return nil if directives.empty?
17
+
18
+ result = directives.map do |directive|
19
+ format_directive(directive, is_const)
20
+ end
21
+
22
+ result.join(' ')
23
+ end
24
+
25
+ ##
26
+ # Format directive to string
27
+ #
28
+ # @param directive [] directive
29
+ # @param is_const [Boolean] allow to use variables or not
30
+ #
31
+ # @return [String] representation of directive as string
32
+ def format_directive(directive, is_const)
33
+ result = []
34
+
35
+ result << format_directive_name(directive.name)
36
+ result << format_arguments(directive.arguments, is_const) unless directive.arguments.empty?
37
+
38
+ result.compact.join
39
+ end
40
+
41
+ ##
42
+ # Format directive name to string
43
+ #
44
+ # @param name [String] directive name
45
+ #
46
+ # @return [String] representation of directive name as string
47
+ def format_directive_name(name)
48
+ name.start_with?('@') ? name.to_s : "@#{name}"
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module DSL
5
+ class Formatter # rubocop:disable Style/Documentation
6
+ private
7
+
8
+ ##
9
+ # Format executable document as string
10
+ #
11
+ # @param executable_document [ExecutableDocument] executable document node
12
+ # @param level [Integer] indent level
13
+ #
14
+ # @return [String] representation of executable document as string
15
+ def format_executable_document(executable_document, level)
16
+ result = []
17
+ result += executable_document.__nodes.map { |node| format_node(node, level) }
18
+ result.join("\n\n")
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module DSL
5
+ class Formatter # rubocop:disable Style/Documentation
6
+ private
7
+
8
+ ##
9
+ # Format field as string
10
+ #
11
+ # @param field [Field] field node
12
+ # @param level [Integer] indent level
13
+ #
14
+ # @return [String] representation of field as string
15
+ def format_field(field, level)
16
+ result = []
17
+ result << indent(level) + format_field_signature(field)
18
+
19
+ unless field.__nodes.empty?
20
+ result << "#{indent(level)}{"
21
+ result += field.__nodes.map { |node| format_node(node, level + 1) }
22
+ result << "#{indent(level)}}"
23
+ end
24
+
25
+ result.join("\n")
26
+ end
27
+
28
+ ##
29
+ # Format field signature as string
30
+ #
31
+ # @param field [Field] field node
32
+ #
33
+ # @return [String] representation of field signature as string
34
+ def format_field_signature(field)
35
+ field_alias = field.__alias ? "#{field.__alias}: " : ''
36
+ field_name = field.__name.to_s
37
+ field_arguments = field.__arguments.empty? ? '' : format_arguments(field.__arguments, false)
38
+ field_directives = field.__directives.empty? ? '' : " #{format_directives(field.__directives, false)}"
39
+
40
+ [
41
+ field_alias,
42
+ field_name,
43
+ field_arguments,
44
+ field_directives,
45
+ ].join
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module DSL
5
+ ##
6
+ # Format nodes to GraphQL
7
+ class Formatter
8
+ ##
9
+ # Format node to GraphQL
10
+ #
11
+ # @param node [Node] node
12
+ # @param level [Integer] indent level
13
+ #
14
+ # @return [String] representation of node as GraphQL string
15
+ def format_node(node, level)
16
+ case node
17
+ when ExecutableDocument then format_executable_document(node, level)
18
+ when Operation then format_operation(node, level)
19
+ when FragmentOperation then format_fragment_operation(node, level)
20
+ when Field then format_field(node, level)
21
+ when FragmentSpread then format_fragment_spread(node, level)
22
+ when InlineFragment then format_inline_fragment(node, level)
23
+ else
24
+ raise Error.new('Unknown node', class: node.class.name, node: node)
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ ##
31
+ # Generate indent for formatting
32
+ #
33
+ # @param level [Integer] indent level
34
+ #
35
+ # @return [String] string for indent
36
+ def indent(level)
37
+ ' ' * level
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module DSL
5
+ class Formatter # rubocop:disable Style/Documentation
6
+ private
7
+
8
+ ##
9
+ # Format fragment operation as string
10
+ #
11
+ # @param fragment_operation [FragmentOperation] fragment operation node
12
+ # @param level [Integer] indent level
13
+ #
14
+ # @return [String] representation of fragment operation as string
15
+ def format_fragment_operation(fragment_operation, level)
16
+ result = []
17
+
18
+ result << indent(level) + format_fragment_operation_signature(fragment_operation)
19
+ result << "#{indent(level)}{"
20
+ result += fragment_operation.__nodes.map { |node| format_node(node, level + 1) }
21
+ result << "#{indent(level)}}"
22
+
23
+ result.join("\n")
24
+ end
25
+
26
+ ##
27
+ # Format fragment operation signature as string
28
+ #
29
+ # @param fragment_operator [FragmentOperation] fragment operation node
30
+ #
31
+ # @return [String] representation of fragment operation signature as string
32
+ def format_fragment_operation_signature(fragment_operator)
33
+ [
34
+ "fragment #{fragment_operator.__name}",
35
+ "on #{fragment_operator.__type}",
36
+ format_directives(fragment_operator.__directives, false),
37
+ ].compact.join(' ')
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module DSL
5
+ class Formatter # rubocop:disable Style/Documentation
6
+ private
7
+
8
+ ##
9
+ # Format fragment spread as string
10
+ #
11
+ # @param fragment_spread [FragmentSpread] fragment spread node
12
+ # @param level [Integer] indent level
13
+ #
14
+ # @return [String] representation of fragment spread as string
15
+ def format_fragment_spread(fragment_spread, level)
16
+ fragment_spread_signature = [
17
+ fragment_spread.__name,
18
+ format_directives(fragment_spread.__directives, false),
19
+ ].compact.join(' ')
20
+
21
+ indent(level) + "...#{fragment_spread_signature}"
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module DSL
5
+ class Formatter # rubocop:disable Style/Documentation
6
+ private
7
+
8
+ ##
9
+ # Format inline fragment as string
10
+ #
11
+ # @param inline_fragment [InlineFragment] inline fragment node
12
+ # @param level [Integer] indent level
13
+ #
14
+ # @return [String] representation of inline fragment as string
15
+ def format_inline_fragment(inline_fragment, level)
16
+ result = []
17
+ result << indent(level) + format_inline_fragment_signature(inline_fragment)
18
+
19
+ unless inline_fragment.__nodes.empty?
20
+ result << "#{indent(level)}{"
21
+ result += inline_fragment.__nodes.map { |node| format_node(node, level + 1) }
22
+ result << "#{indent(level)}}"
23
+ end
24
+
25
+ result.join("\n")
26
+ end
27
+
28
+ ##
29
+ # Format inline fragment signature as string
30
+ #
31
+ # @param inline_fragment [InlineFragment] inline fragment node
32
+ #
33
+ # @return [String] representation of inline fragment signature as string
34
+ def format_inline_fragment_signature(inline_fragment)
35
+ [
36
+ '...',
37
+ (inline_fragment.__type ? "on #{inline_fragment.__type}" : nil),
38
+ format_directives(inline_fragment.__directives, false),
39
+ ].compact.join(' ')
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module DSL
5
+ class Formatter # rubocop:disable Style/Documentation
6
+ private
7
+
8
+ ##
9
+ # Format operation as string
10
+ #
11
+ # @param operation [FragmentOperation] operation node
12
+ # @param level [Integer] indent level
13
+ #
14
+ # @return [String] representation of operation as string
15
+ def format_operation(operation, level)
16
+ result = []
17
+
18
+ operation_signature = format_operation_signature(operation)
19
+
20
+ result << indent(level) + operation_signature if operation_signature
21
+ result << "#{indent(level)}{"
22
+ result += operation.__nodes.map { |node| format_node(node, level + 1) }
23
+ result << "#{indent(level)}}"
24
+
25
+ result.join("\n")
26
+ end
27
+
28
+ ##
29
+ # Format operation signature
30
+ #
31
+ # @return [String, nil] representation of operation signature as string
32
+ def format_operation_signature(operation)
33
+ operation_name_and_variables = [
34
+ operation.__name,
35
+ format_variable_definitions(operation.__variable_definitions),
36
+ ].compact
37
+
38
+ operation_signature = [
39
+ format_operation_type(operation),
40
+ (operation_name_and_variables.join unless operation_name_and_variables.empty?),
41
+ format_directives(operation.__directives, false),
42
+ ].compact
43
+
44
+ operation_signature.empty? ? nil : operation_signature.join(' ')
45
+ end
46
+
47
+ ##
48
+ # Format operation type
49
+ #
50
+ # @return [String, nil] representation of operation type as string
51
+ def format_operation_type(operation)
52
+ return unless operation.__operation_type != :query ||
53
+ operation.__name ||
54
+ !operation.__variable_definitions.empty?
55
+
56
+ operation.__operation_type.to_s
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,146 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module DSL
5
+ class Formatter # rubocop:disable Style/Documentation
6
+ private
7
+
8
+ ##
9
+ # Format value to string
10
+ #
11
+ # @param value [] value
12
+ # @param is_const [Boolean] allow to use variables or not i.e. value is constant or not
13
+ #
14
+ # @return [String] representation of value as string
15
+ def format_value(value, is_const) # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/AbcSize
16
+ case value
17
+ when Integer
18
+ format_value_to_integer(value)
19
+ when Float
20
+ format_value_to_float(value)
21
+ when String
22
+ format_value_to_string(value)
23
+ when TrueClass, FalseClass
24
+ format_value_to_boolean(value)
25
+ when NilClass
26
+ format_value_to_null(value)
27
+ when Symbol
28
+ if value.start_with?('$')
29
+ format_value_to_variable(value, is_const)
30
+ else
31
+ format_value_to_enum(value)
32
+ end
33
+ when Array
34
+ format_value_to_list(value, is_const)
35
+ when Hash
36
+ format_value_to_object(value, is_const)
37
+ else
38
+ raise Error.new('Unsupported value type', class: value.class.name, value: value)
39
+ end
40
+ end
41
+
42
+ ##
43
+ # Format value to integer value
44
+ #
45
+ # @param value [Integer] value
46
+ #
47
+ # @return [String] representation of value as integer value
48
+ def format_value_to_integer(value)
49
+ value.to_s
50
+ end
51
+
52
+ ##
53
+ # Format value to float value
54
+ #
55
+ # @param value [Float] value
56
+ #
57
+ # @return [String] representation of value as float value
58
+ def format_value_to_float(value)
59
+ value.to_s
60
+ end
61
+
62
+ ##
63
+ # Format value to string value
64
+ #
65
+ # @param value [String] value
66
+ #
67
+ # @return [String] representation of value as string value
68
+ def format_value_to_string(value)
69
+ value.dump
70
+ end
71
+
72
+ ##
73
+ # Format value to boolean value
74
+ #
75
+ # @param value [TrueClass, FalseClass] value
76
+ #
77
+ # @return [String] representation of value as boolean value
78
+ def format_value_to_boolean(value)
79
+ value.to_s
80
+ end
81
+
82
+ ##
83
+ # Format value to null value
84
+ #
85
+ # @param _value [NilClass] value
86
+ #
87
+ # @return [String] representation of value as null value
88
+ def format_value_to_null(_value)
89
+ 'null'
90
+ end
91
+
92
+ ##
93
+ # Format value to variable value
94
+ #
95
+ # @param value [Symbol] value
96
+ # @param is_const [Boolean] allow to use variables or not
97
+ #
98
+ # @return [String] representation of value as variable value
99
+ def format_value_to_variable(value, is_const)
100
+ raise Error.new('Value must be constant', value: value) if is_const
101
+
102
+ value.to_s
103
+ end
104
+
105
+ ##
106
+ # Format value to enum value
107
+ #
108
+ # @param value [Symbol] value
109
+ #
110
+ # @return [String] representation of value as enum value
111
+ def format_value_to_enum(value)
112
+ value.to_s
113
+ end
114
+
115
+ ##
116
+ # Format value to list value
117
+ #
118
+ # @param value [Array] value
119
+ # @param is_const [Boolean] allow to use variables or not
120
+ #
121
+ # @return [String] representation of value as list value
122
+ def format_value_to_list(value, is_const)
123
+ result = value.map do |element|
124
+ format_value(element, is_const)
125
+ end.join(', ')
126
+
127
+ "[#{result}]"
128
+ end
129
+
130
+ ##
131
+ # Format value to object value
132
+ #
133
+ # @param value [Hash] value
134
+ # @param is_const [Boolean] allow to use variables or not
135
+ #
136
+ # @return [String] representation of value as object value
137
+ def format_value_to_object(value, is_const)
138
+ result = value.map do |n, v|
139
+ "#{n}: #{format_value(v, is_const)}"
140
+ end.join(', ')
141
+
142
+ "{#{result}}"
143
+ end
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module DSL
5
+ class Formatter # rubocop:disable Style/Documentation
6
+ private
7
+
8
+ ##
9
+ # Format variable definitions to string
10
+ #
11
+ # @param variable_definitions [Hash] variable definitions
12
+ #
13
+ # @return [String] representation of variable definitions as string
14
+ def format_variable_definitions(variable_definitions)
15
+ return nil if variable_definitions.empty?
16
+
17
+ result = variable_definitions.map do |variable_name, variable_definition|
18
+ format_variable_definition(variable_name, variable_definition)
19
+ end
20
+
21
+ "(#{result.join(', ')})"
22
+ end
23
+
24
+ ##
25
+ # Format variable definition to string
26
+ #
27
+ # @param variable_name [Symbol, String] variable name
28
+ # @param variable_definition [] variable definition
29
+ #
30
+ # @return [String] representation of variable definition as string
31
+ def format_variable_definition(variable_name, variable_definition)
32
+ result = []
33
+
34
+ result << "$#{variable_name}:"
35
+ result << variable_definition.type
36
+ result << "= #{format_value(variable_definition.default, true)}" if variable_definition.default != UNDEFINED
37
+ result << format_directives(variable_definition.directives, true) unless variable_definition.directives.empty?
38
+
39
+ result.compact.join(' ')
40
+ end
41
+ end
42
+ end
43
+ end