graphqlite 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +46 -0
- data/LICENSE +21 -0
- data/README.md +339 -0
- data/lib/graphqlite/errors.rb +7 -0
- data/lib/graphqlite/executor.rb +380 -0
- data/lib/graphqlite/introspection.rb +222 -0
- data/lib/graphqlite/lexer.rb +266 -0
- data/lib/graphqlite/parser.rb +354 -0
- data/lib/graphqlite/schema.rb +238 -0
- data/lib/graphqlite/types.rb +336 -0
- data/lib/graphqlite/validator.rb +183 -0
- data/lib/graphqlite/version.rb +3 -0
- data/lib/graphqlite.rb +18 -0
- metadata +85 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
module GraphQLite
|
|
2
|
+
# Validator validates GraphQL documents against the schema
|
|
3
|
+
class Validator
|
|
4
|
+
attr_reader :schema, :errors
|
|
5
|
+
|
|
6
|
+
def initialize(schema)
|
|
7
|
+
@schema = schema
|
|
8
|
+
@errors = []
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def validate(document)
|
|
12
|
+
@errors = []
|
|
13
|
+
|
|
14
|
+
document.definitions.each do |definition|
|
|
15
|
+
case definition
|
|
16
|
+
when Parser::OperationDefinition
|
|
17
|
+
validate_operation(definition)
|
|
18
|
+
when Parser::FragmentDefinition
|
|
19
|
+
validate_fragment(definition)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
@errors
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def validate_operation(operation)
|
|
29
|
+
# Get root type
|
|
30
|
+
root_type = case operation.operation_type
|
|
31
|
+
when 'query'
|
|
32
|
+
@schema.query_type
|
|
33
|
+
when 'mutation'
|
|
34
|
+
@schema.mutation_type
|
|
35
|
+
when 'subscription'
|
|
36
|
+
@schema.subscription_type
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
unless root_type
|
|
40
|
+
@errors << ValidationError.new("Schema does not support #{operation.operation_type}")
|
|
41
|
+
return
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Validate variable definitions
|
|
45
|
+
validate_variable_definitions(operation.variable_definitions)
|
|
46
|
+
|
|
47
|
+
# Validate selection set
|
|
48
|
+
validate_selection_set(operation.selection_set, root_type)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def validate_fragment(fragment)
|
|
52
|
+
# Validate type condition exists
|
|
53
|
+
type = @schema.get_type(fragment.type_condition.name)
|
|
54
|
+
unless type
|
|
55
|
+
@errors << ValidationError.new("Unknown type: #{fragment.type_condition.name}")
|
|
56
|
+
return
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Validate selection set
|
|
60
|
+
validate_selection_set(fragment.selection_set, type)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def validate_variable_definitions(variable_definitions)
|
|
64
|
+
return if variable_definitions.empty?
|
|
65
|
+
|
|
66
|
+
variable_definitions.each do |var_def|
|
|
67
|
+
# Validate variable type exists
|
|
68
|
+
type = resolve_type_ref(var_def.type)
|
|
69
|
+
unless type
|
|
70
|
+
@errors << ValidationError.new("Unknown type for variable $#{var_def.variable.name}")
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def validate_selection_set(selection_set, parent_type)
|
|
76
|
+
return unless selection_set
|
|
77
|
+
|
|
78
|
+
selection_set.selections.each do |selection|
|
|
79
|
+
case selection
|
|
80
|
+
when Parser::Field
|
|
81
|
+
validate_field(selection, parent_type)
|
|
82
|
+
when Parser::FragmentSpread
|
|
83
|
+
# Fragment validation would require storing fragments
|
|
84
|
+
# Skip for minimal implementation
|
|
85
|
+
when Parser::InlineFragment
|
|
86
|
+
if selection.type_condition
|
|
87
|
+
type = @schema.get_type(selection.type_condition.name)
|
|
88
|
+
unless type
|
|
89
|
+
@errors << ValidationError.new("Unknown type: #{selection.type_condition.name}")
|
|
90
|
+
next
|
|
91
|
+
end
|
|
92
|
+
validate_selection_set(selection.selection_set, type)
|
|
93
|
+
else
|
|
94
|
+
validate_selection_set(selection.selection_set, parent_type)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def validate_field(field, parent_type)
|
|
101
|
+
field_name = field.name
|
|
102
|
+
|
|
103
|
+
# Introspection fields are always valid
|
|
104
|
+
return if %w[__typename __schema __type].include?(field_name)
|
|
105
|
+
|
|
106
|
+
# Check if field exists on parent type
|
|
107
|
+
unless parent_type.respond_to?(:fields) && parent_type.fields[field_name]
|
|
108
|
+
@errors << ValidationError.new("Field '#{field_name}' does not exist on type '#{parent_type.name}'")
|
|
109
|
+
return
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
field_def = parent_type.fields[field_name]
|
|
113
|
+
|
|
114
|
+
# Validate arguments
|
|
115
|
+
validate_arguments(field.arguments, field_def)
|
|
116
|
+
|
|
117
|
+
# Validate nested selection set
|
|
118
|
+
if field.selection_set
|
|
119
|
+
field_type = unwrap_type(field_def.type)
|
|
120
|
+
|
|
121
|
+
unless field_type.respond_to?(:fields)
|
|
122
|
+
@errors << ValidationError.new("Field '#{field_name}' is a scalar and cannot have a selection set")
|
|
123
|
+
return
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
validate_selection_set(field.selection_set, field_type)
|
|
127
|
+
elsif requires_selection_set?(field_def.type)
|
|
128
|
+
@errors << ValidationError.new("Field '#{field_name}' requires a selection set")
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def validate_arguments(arguments, field_def)
|
|
133
|
+
return if arguments.empty?
|
|
134
|
+
|
|
135
|
+
arguments.each do |arg|
|
|
136
|
+
arg_def = field_def.arguments[arg.name]
|
|
137
|
+
unless arg_def
|
|
138
|
+
@errors << ValidationError.new("Unknown argument '#{arg.name}' on field '#{field_def.name}'")
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Check for required arguments
|
|
143
|
+
field_def.arguments.each do |arg_name, arg_def|
|
|
144
|
+
if arg_def.type.is_a?(Types::NonNullType)
|
|
145
|
+
unless arguments.any? { |arg| arg.name == arg_name }
|
|
146
|
+
@errors << ValidationError.new("Required argument '#{arg_name}' missing on field '#{field_def.name}'")
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def unwrap_type(type)
|
|
153
|
+
case type
|
|
154
|
+
when Types::NonNullType, Types::ListType
|
|
155
|
+
unwrap_type(type.of_type)
|
|
156
|
+
else
|
|
157
|
+
type
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def requires_selection_set?(type)
|
|
162
|
+
unwrapped = unwrap_type(type)
|
|
163
|
+
unwrapped.is_a?(Types::ObjectType) ||
|
|
164
|
+
unwrapped.is_a?(Types::InterfaceType) ||
|
|
165
|
+
unwrapped.is_a?(Types::UnionType)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def resolve_type_ref(type_ref)
|
|
169
|
+
case type_ref
|
|
170
|
+
when Parser::NamedType
|
|
171
|
+
@schema.get_type(type_ref.name)
|
|
172
|
+
when Parser::ListType
|
|
173
|
+
inner = resolve_type_ref(type_ref.type)
|
|
174
|
+
inner ? Types::ListType.new(inner) : nil
|
|
175
|
+
when Parser::NonNullType
|
|
176
|
+
inner = resolve_type_ref(type_ref.type)
|
|
177
|
+
inner ? Types::NonNullType.new(inner) : nil
|
|
178
|
+
else
|
|
179
|
+
type_ref
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
data/lib/graphqlite.rb
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
require_relative "graphqlite/version"
|
|
2
|
+
require_relative "graphqlite/lexer"
|
|
3
|
+
require_relative "graphqlite/parser"
|
|
4
|
+
require_relative "graphqlite/types"
|
|
5
|
+
require_relative "graphqlite/schema"
|
|
6
|
+
require_relative "graphqlite/executor"
|
|
7
|
+
require_relative "graphqlite/validator"
|
|
8
|
+
require_relative "graphqlite/introspection"
|
|
9
|
+
require_relative "graphqlite/errors"
|
|
10
|
+
|
|
11
|
+
module GraphQLite
|
|
12
|
+
class << self
|
|
13
|
+
# Create a new schema
|
|
14
|
+
def schema(&block)
|
|
15
|
+
Schema.new(&block)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: graphqlite
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- zyxzen
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: minitest
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '5.0'
|
|
19
|
+
type: :development
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '5.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: rake
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '13.0'
|
|
33
|
+
type: :development
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '13.0'
|
|
40
|
+
description: GraphQLite is a simple, minimal, and clean GraphQL implementation with
|
|
41
|
+
zero dependencies. It's designed to be easier to use and faster than existing solutions
|
|
42
|
+
while maintaining full GraphQL spec compliance.
|
|
43
|
+
email:
|
|
44
|
+
- dev@zyxzen.com
|
|
45
|
+
executables: []
|
|
46
|
+
extensions: []
|
|
47
|
+
extra_rdoc_files: []
|
|
48
|
+
files:
|
|
49
|
+
- CHANGELOG.md
|
|
50
|
+
- LICENSE
|
|
51
|
+
- README.md
|
|
52
|
+
- lib/graphqlite.rb
|
|
53
|
+
- lib/graphqlite/errors.rb
|
|
54
|
+
- lib/graphqlite/executor.rb
|
|
55
|
+
- lib/graphqlite/introspection.rb
|
|
56
|
+
- lib/graphqlite/lexer.rb
|
|
57
|
+
- lib/graphqlite/parser.rb
|
|
58
|
+
- lib/graphqlite/schema.rb
|
|
59
|
+
- lib/graphqlite/types.rb
|
|
60
|
+
- lib/graphqlite/validator.rb
|
|
61
|
+
- lib/graphqlite/version.rb
|
|
62
|
+
homepage: https://github.com/zyxzen/graphqlite
|
|
63
|
+
licenses:
|
|
64
|
+
- MIT
|
|
65
|
+
metadata:
|
|
66
|
+
homepage_uri: https://github.com/zyxzen/graphqlite
|
|
67
|
+
changelog_uri: https://github.com/zyxzen/graphqlite/blob/main/CHANGELOG.md
|
|
68
|
+
rdoc_options: []
|
|
69
|
+
require_paths:
|
|
70
|
+
- lib
|
|
71
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - ">="
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: 2.7.0
|
|
76
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
77
|
+
requirements:
|
|
78
|
+
- - ">="
|
|
79
|
+
- !ruby/object:Gem::Version
|
|
80
|
+
version: '0'
|
|
81
|
+
requirements: []
|
|
82
|
+
rubygems_version: 3.6.9
|
|
83
|
+
specification_version: 4
|
|
84
|
+
summary: A lightweight, production-ready GraphQL implementation for Ruby
|
|
85
|
+
test_files: []
|