ar_serializer 1.0.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/.gitignore +10 -0
- data/.travis.yml +5 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +59 -0
- data/LICENSE.txt +21 -0
- data/README.md +160 -0
- data/Rakefile +10 -0
- data/ar_serializer.gemspec +29 -0
- data/bin/console +8 -0
- data/bin/setup +8 -0
- data/lib/ar_serializer.rb +77 -0
- data/lib/ar_serializer/error.rb +3 -0
- data/lib/ar_serializer/field.rb +234 -0
- data/lib/ar_serializer/graphql.rb +41 -0
- data/lib/ar_serializer/graphql/parser.rb +269 -0
- data/lib/ar_serializer/graphql/types.rb +442 -0
- data/lib/ar_serializer/serializer.rb +184 -0
- data/lib/ar_serializer/type_script.rb +58 -0
- data/lib/ar_serializer/version.rb +3 -0
- metadata +160 -0
@@ -0,0 +1,41 @@
|
|
1
|
+
require_relative 'graphql/types'
|
2
|
+
require_relative 'graphql/parser'
|
3
|
+
|
4
|
+
module ArSerializer::GraphQL
|
5
|
+
def self.definition(klass, use: nil)
|
6
|
+
ArSerializer::Serializer.with_namespaces(use) { _definition klass }
|
7
|
+
end
|
8
|
+
|
9
|
+
def self._definition(klass)
|
10
|
+
schema = SchemaClass.new(klass)
|
11
|
+
definitions = schema.types.map do |type|
|
12
|
+
next "scalar #{type.name}" if type.is_a? ScalarTypeClass
|
13
|
+
fields = type.fields.map do |field|
|
14
|
+
args = field.args.map { |arg| "#{arg.name}: #{arg.type.gql_type}" }
|
15
|
+
args_exp = "(#{args.join(', ')})" unless args.empty?
|
16
|
+
" #{field.name}#{args_exp}: #{field.type.gql_type}"
|
17
|
+
end
|
18
|
+
<<~TYPE
|
19
|
+
type #{type.name} {
|
20
|
+
#{fields.join("\n")}
|
21
|
+
}
|
22
|
+
TYPE
|
23
|
+
end
|
24
|
+
<<~SCHEMA
|
25
|
+
schema {
|
26
|
+
query: #{schema.query_type.name}
|
27
|
+
}
|
28
|
+
|
29
|
+
#{definitions.map(&:strip).join("\n\n")}
|
30
|
+
SCHEMA
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.serialize(schema, gql_query, operation_name: nil, variables: {}, **args)
|
34
|
+
query = ArSerializer::GraphQL::Parser.parse(
|
35
|
+
gql_query,
|
36
|
+
operation_name: operation_name,
|
37
|
+
variables: variables
|
38
|
+
)
|
39
|
+
{ data: ArSerializer::Serializer.serialize(schema, query, **args) }
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,269 @@
|
|
1
|
+
class ArSerializer::GraphQL::Parser
|
2
|
+
class ParseError < StandardError; end
|
3
|
+
|
4
|
+
attr_reader :query, :operation_name, :variables, :chars
|
5
|
+
def initialize(query, operation_name: nil, variables: {})
|
6
|
+
@query = query
|
7
|
+
@operation_name = operation_name
|
8
|
+
@variables = variables
|
9
|
+
@chars = query.chars
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.parse(*args)
|
13
|
+
new(*args).parse
|
14
|
+
end
|
15
|
+
|
16
|
+
def parse
|
17
|
+
definitions = []
|
18
|
+
consume_blank
|
19
|
+
loop do
|
20
|
+
definition = parse_definition
|
21
|
+
consume_blank
|
22
|
+
consume_text ','
|
23
|
+
consume_blank
|
24
|
+
break unless definition
|
25
|
+
definitions << definition
|
26
|
+
end
|
27
|
+
raise_expected_not_found 'definition or EOF' unless chars.empty?
|
28
|
+
query = definitions.find do |definition|
|
29
|
+
next unless definition[:type] == 'query'
|
30
|
+
operation_name.nil? || operation_name == definition[:args].first
|
31
|
+
end
|
32
|
+
raise ParseError, 'empty query' unless query
|
33
|
+
fragments = definitions.select { |definition| definition[:type] == 'fragment' }
|
34
|
+
fragments_by_name = fragments.index_by { |frag| frag[:args].first }
|
35
|
+
embed_fragment query[:fields], fragments_by_name
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def consume_comment
|
41
|
+
return false if chars.first != '#'
|
42
|
+
until chars.blank?
|
43
|
+
c = chars.first
|
44
|
+
break if c == "\n"
|
45
|
+
chars.shift
|
46
|
+
end
|
47
|
+
true
|
48
|
+
end
|
49
|
+
|
50
|
+
def consume_blank
|
51
|
+
loop do
|
52
|
+
chars.shift while chars.first&.match?(/\s/)
|
53
|
+
return unless consume_comment
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def consume_text(s)
|
58
|
+
return false unless chars.take(s.size).join == s
|
59
|
+
chars.shift s.size
|
60
|
+
true
|
61
|
+
end
|
62
|
+
|
63
|
+
def consume_text!(s)
|
64
|
+
return if consume_text s
|
65
|
+
raise_expected_not_found s.inspect
|
66
|
+
end
|
67
|
+
|
68
|
+
def raise_expected_not_found(expected, found = nil)
|
69
|
+
raise(
|
70
|
+
ParseError,
|
71
|
+
"expected #{expected} but found #{found || chars.first.inspect} #{current_position_message}"
|
72
|
+
)
|
73
|
+
end
|
74
|
+
|
75
|
+
def parse_name
|
76
|
+
name = ''
|
77
|
+
name << chars.shift while chars.first && chars.first =~ /[a-zA-Z0-9_]/
|
78
|
+
name unless name.empty?
|
79
|
+
end
|
80
|
+
|
81
|
+
def parse_name_alias
|
82
|
+
name = parse_name
|
83
|
+
return unless name
|
84
|
+
consume_blank
|
85
|
+
if consume_text ':'
|
86
|
+
consume_blank
|
87
|
+
[parse_name, name]
|
88
|
+
else
|
89
|
+
name
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def parse_arg_value
|
94
|
+
case chars.first
|
95
|
+
when '"'
|
96
|
+
chars.shift
|
97
|
+
s = ''
|
98
|
+
loop do
|
99
|
+
if chars.first == '\\'
|
100
|
+
s << chars.shift
|
101
|
+
s << chars.shift
|
102
|
+
elsif chars.first == '"'
|
103
|
+
break
|
104
|
+
else
|
105
|
+
s << chars.shift
|
106
|
+
end
|
107
|
+
end
|
108
|
+
chars.shift
|
109
|
+
unescape_string s
|
110
|
+
when '['
|
111
|
+
chars.shift
|
112
|
+
result = []
|
113
|
+
loop do
|
114
|
+
consume_blank
|
115
|
+
value = parse_arg_value
|
116
|
+
consume_blank
|
117
|
+
consume_text ','
|
118
|
+
break if value == :none
|
119
|
+
result << value
|
120
|
+
end
|
121
|
+
consume_text! ']'
|
122
|
+
result
|
123
|
+
when '{'
|
124
|
+
chars.shift
|
125
|
+
consume_blank
|
126
|
+
result = parse_arg_fields
|
127
|
+
consume_blank
|
128
|
+
consume_text! '}'
|
129
|
+
result
|
130
|
+
when '$'
|
131
|
+
chars.shift
|
132
|
+
name = parse_name
|
133
|
+
variables[name]
|
134
|
+
when /[0-9+\-]/
|
135
|
+
s = ''
|
136
|
+
s << chars.shift while chars.first.match?(/[0-9.e+\-]/)
|
137
|
+
s.match?(/\.|e/) ? s.to_f : s.to_i
|
138
|
+
when /[a-zA-Z]/
|
139
|
+
s = parse_name
|
140
|
+
converts = { 'true' => true, 'false' => false, 'null' => nil }
|
141
|
+
converts.key?(s) ? converts[s] : s
|
142
|
+
else
|
143
|
+
:none
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def unescape_string(s)
|
148
|
+
JSON.parse %("#{s}")
|
149
|
+
rescue JSON::ParserError # for old json gem
|
150
|
+
JSON.parse(%(["#{s}"])).first
|
151
|
+
end
|
152
|
+
|
153
|
+
def parse_arg_fields
|
154
|
+
result = {}
|
155
|
+
loop do
|
156
|
+
name = parse_name
|
157
|
+
break unless name
|
158
|
+
consume_blank
|
159
|
+
consume_text! ':'
|
160
|
+
consume_blank
|
161
|
+
value = parse_arg_value
|
162
|
+
if value == :none
|
163
|
+
raise(
|
164
|
+
ParseError,
|
165
|
+
"expected hash value but nothing found #{current_position_message}"
|
166
|
+
)
|
167
|
+
end
|
168
|
+
result[name] = value
|
169
|
+
consume_blank
|
170
|
+
consume_text ','
|
171
|
+
consume_blank
|
172
|
+
end
|
173
|
+
result
|
174
|
+
end
|
175
|
+
|
176
|
+
def parse_args
|
177
|
+
return unless consume_text '('
|
178
|
+
consume_blank
|
179
|
+
args = parse_arg_fields
|
180
|
+
consume_blank
|
181
|
+
consume_text! ')'
|
182
|
+
args
|
183
|
+
end
|
184
|
+
|
185
|
+
def parse_field
|
186
|
+
if chars[0, 3].join == '...'
|
187
|
+
3.times { chars.shift }
|
188
|
+
name = parse_name
|
189
|
+
return ['...' + name, { fragment: name }]
|
190
|
+
end
|
191
|
+
name, alias_name = parse_name_alias
|
192
|
+
return unless name
|
193
|
+
consume_blank
|
194
|
+
args = parse_args
|
195
|
+
consume_blank
|
196
|
+
fields = parse_fields
|
197
|
+
[name, { as: alias_name, params: args, attributes: fields }.compact]
|
198
|
+
end
|
199
|
+
|
200
|
+
def parse_fields
|
201
|
+
return unless consume_text '{'
|
202
|
+
consume_blank
|
203
|
+
fields = {}
|
204
|
+
loop do
|
205
|
+
name, field = parse_field
|
206
|
+
consume_blank
|
207
|
+
consume_text ','
|
208
|
+
consume_blank
|
209
|
+
break unless name
|
210
|
+
fields[name] = field
|
211
|
+
end
|
212
|
+
consume_text! '}'
|
213
|
+
fields
|
214
|
+
end
|
215
|
+
|
216
|
+
def parse_definition
|
217
|
+
type = parse_name
|
218
|
+
consume_blank
|
219
|
+
args_text = ''
|
220
|
+
if type
|
221
|
+
args_text << chars.shift while chars.first && chars.first != '{'
|
222
|
+
end
|
223
|
+
args = args_text.split(/[\s()]+/)
|
224
|
+
fields = parse_fields
|
225
|
+
return if type.nil? && fields.nil?
|
226
|
+
type ||= 'query'
|
227
|
+
raise_expected_not_found '{'.inspect if fields.nil?
|
228
|
+
{ type: type, args: args, fields: fields }
|
229
|
+
end
|
230
|
+
|
231
|
+
def current_position_message
|
232
|
+
pos = query.size - chars.size
|
233
|
+
code = query[[pos - 10, 0].max..pos + 10]
|
234
|
+
line_num = 0
|
235
|
+
query.each_line.with_index 1 do |l, i|
|
236
|
+
line_num = i
|
237
|
+
break if pos < l.size
|
238
|
+
pos -= l.size
|
239
|
+
end
|
240
|
+
"at #{line_num}:#{pos} near #{code.inspect}"
|
241
|
+
end
|
242
|
+
|
243
|
+
def embed_fragment(fields, fragments)
|
244
|
+
output = {}
|
245
|
+
fields.each do |key, value|
|
246
|
+
if value.is_a?(Hash) && (fragment_name = value[:fragment])
|
247
|
+
fragment = fragments[fragment_name]
|
248
|
+
extract_fragment fragment_name, fragments
|
249
|
+
output.update fragment[:fields]
|
250
|
+
else
|
251
|
+
output[key] = value
|
252
|
+
if (attrs = value[:attributes])
|
253
|
+
value[:attributes] = embed_fragment attrs, fragments
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
257
|
+
output
|
258
|
+
end
|
259
|
+
|
260
|
+
def extract_fragment(fragment_name, fragments)
|
261
|
+
fragment = fragments[fragment_name]
|
262
|
+
raise ParseError, "fragment named #{fragment_name.inspect} was not found" if fragment.nil?
|
263
|
+
raise ParseError, "fragment circular definition detected in #{fragment_name.inspect}" if fragment[:state] == :start
|
264
|
+
return if fragment[:state] == :done
|
265
|
+
fragment[:state] = :start
|
266
|
+
fragment[:fields] = embed_fragment fragment[:fields], fragments
|
267
|
+
fragment[:state] = :done
|
268
|
+
end
|
269
|
+
end
|
@@ -0,0 +1,442 @@
|
|
1
|
+
module ArSerializer::GraphQL
|
2
|
+
class ArgClass
|
3
|
+
include ::ArSerializer::Serializable
|
4
|
+
attr_reader :name, :type
|
5
|
+
def initialize(name, type)
|
6
|
+
@optional = name.to_s.end_with? '?' # TODO: refactor
|
7
|
+
@name = name.to_s.delete '?'
|
8
|
+
@type = TypeClass.from type
|
9
|
+
end
|
10
|
+
serializer_field :name
|
11
|
+
serializer_field :type, except: :fields
|
12
|
+
serializer_field(:defaultValue) { nil }
|
13
|
+
serializer_field(:description) { "#{'Optional: ' if @optional}#{type.description}" }
|
14
|
+
end
|
15
|
+
|
16
|
+
class FieldClass
|
17
|
+
include ::ArSerializer::Serializable
|
18
|
+
attr_reader :name, :field
|
19
|
+
def initialize(name, field)
|
20
|
+
@name = name
|
21
|
+
@field = field
|
22
|
+
end
|
23
|
+
|
24
|
+
def args
|
25
|
+
return [] if field.arguments == :any
|
26
|
+
field.arguments.map do |key, type|
|
27
|
+
ArgClass.new key, type
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def type
|
32
|
+
TypeClass.from field.type, field.only, field.except
|
33
|
+
end
|
34
|
+
|
35
|
+
def collect_types(types)
|
36
|
+
types[:any] = true if field.arguments == :any
|
37
|
+
args.each { |arg| arg.type.collect_types types }
|
38
|
+
type.collect_types types
|
39
|
+
end
|
40
|
+
|
41
|
+
def args_ts_type
|
42
|
+
arg_types = field.arguments.map do |key, type|
|
43
|
+
"#{key}: #{TypeClass.from(type).ts_type}"
|
44
|
+
end
|
45
|
+
"{ #{arg_types.join '; '} }"
|
46
|
+
end
|
47
|
+
|
48
|
+
serializer_field :name, :args
|
49
|
+
serializer_field :type, except: :fields
|
50
|
+
serializer_field(:isDeprecated) { false }
|
51
|
+
serializer_field(:description) { type.description }
|
52
|
+
serializer_field(:deprecationReason) { nil }
|
53
|
+
end
|
54
|
+
|
55
|
+
class SchemaClass
|
56
|
+
include ::ArSerializer::Serializable
|
57
|
+
attr_reader :klass, :query_type
|
58
|
+
def initialize(klass)
|
59
|
+
@klass = klass
|
60
|
+
@query_type = SerializableTypeClass.new klass
|
61
|
+
end
|
62
|
+
|
63
|
+
def collect_types
|
64
|
+
types = {}
|
65
|
+
klass._serializer_field_keys.each do |name|
|
66
|
+
fc = FieldClass.new name, klass._serializer_field_info(name)
|
67
|
+
fc.collect_types types
|
68
|
+
end
|
69
|
+
type_symbols, type_classes = types.keys.partition { |t| t.is_a? Symbol }
|
70
|
+
type_classes << TypeClass.from(klass)
|
71
|
+
[type_symbols.sort, type_classes.sort_by(&:name)]
|
72
|
+
end
|
73
|
+
|
74
|
+
def types
|
75
|
+
types_symbols, klass_types = collect_types
|
76
|
+
types_symbols.map { |t| ScalarTypeClass.new t } + klass_types
|
77
|
+
end
|
78
|
+
|
79
|
+
serializer_field(:mutationType) { nil }
|
80
|
+
serializer_field(:subscriptionType) { nil }
|
81
|
+
serializer_field(:directives) { [] }
|
82
|
+
serializer_field :types, :queryType
|
83
|
+
end
|
84
|
+
|
85
|
+
class TypeClass
|
86
|
+
include ::ArSerializer::Serializable
|
87
|
+
attr_reader :type, :only, :except
|
88
|
+
def initialize(type, only = nil, except = nil)
|
89
|
+
@type = type
|
90
|
+
@only = only
|
91
|
+
@except = except
|
92
|
+
validate!
|
93
|
+
end
|
94
|
+
|
95
|
+
class InvalidType < StandardError; end
|
96
|
+
|
97
|
+
def validate!
|
98
|
+
valid_symbols = %i[number int float string boolean any]
|
99
|
+
invalids = []
|
100
|
+
recursive_validate = lambda do |t|
|
101
|
+
case t
|
102
|
+
when Array
|
103
|
+
t.each { |v| recursive_validate.call v }
|
104
|
+
when Hash
|
105
|
+
t.each_value { |v| recursive_validate.call v }
|
106
|
+
when String, Numeric, true, false, nil
|
107
|
+
return
|
108
|
+
when Class
|
109
|
+
invalids << t unless t.ancestors.include? ArSerializer::Serializable
|
110
|
+
when Symbol
|
111
|
+
invalids << t unless valid_symbols.include? t.to_s.gsub(/\?$/, '').to_sym
|
112
|
+
else
|
113
|
+
invalids << t
|
114
|
+
end
|
115
|
+
end
|
116
|
+
recursive_validate.call type
|
117
|
+
return if invalids.empty?
|
118
|
+
message = "Valid types are String, Numeric, Hash, Array, ArSerializer::Serializable, true, false, nil and Symbol#{valid_symbols}"
|
119
|
+
raise InvalidType, "Invalid type: #{invalids.map(&:inspect).join(', ')}. #{message}"
|
120
|
+
end
|
121
|
+
|
122
|
+
def collect_types(types); end
|
123
|
+
|
124
|
+
def description
|
125
|
+
ts_type
|
126
|
+
end
|
127
|
+
|
128
|
+
def name; end
|
129
|
+
|
130
|
+
def of_type; end
|
131
|
+
|
132
|
+
def fields; end
|
133
|
+
|
134
|
+
def sample; end
|
135
|
+
|
136
|
+
def ts_type; end
|
137
|
+
|
138
|
+
def association_type; end
|
139
|
+
|
140
|
+
serializer_field :kind, :name, :description, :fields
|
141
|
+
serializer_field :ofType, except: :fields
|
142
|
+
serializer_field(:interfaces) { [] }
|
143
|
+
%i[inputFields enumValues possibleTypes].each do |name|
|
144
|
+
serializer_field(name) { nil }
|
145
|
+
end
|
146
|
+
|
147
|
+
def self.from(type, only = nil, except = nil)
|
148
|
+
type = [type[0...-1].to_sym, nil] if type.is_a?(Symbol) && type.to_s.ends_with?('?')
|
149
|
+
type = [type[0...-1], nil] if type.is_a?(String) && type.ends_with?('?')
|
150
|
+
case type
|
151
|
+
when Class
|
152
|
+
SerializableTypeClass.new type, only, except
|
153
|
+
when Symbol, String, Numeric, true, false, nil
|
154
|
+
ScalarTypeClass.new type
|
155
|
+
when Array
|
156
|
+
if type.size == 1
|
157
|
+
ListTypeClass.new type.first, only, except
|
158
|
+
elsif type.size == 2 && type.last.nil?
|
159
|
+
OptionalTypeClass.new type
|
160
|
+
else
|
161
|
+
OrTypeClass.new type, only, except
|
162
|
+
end
|
163
|
+
when Hash
|
164
|
+
HashTypeClass.new type, only, except
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
class ScalarTypeClass < TypeClass
|
170
|
+
def initialize(type)
|
171
|
+
@type = type
|
172
|
+
end
|
173
|
+
|
174
|
+
def kind
|
175
|
+
'SCALAR'
|
176
|
+
end
|
177
|
+
|
178
|
+
def name
|
179
|
+
case type
|
180
|
+
when String, :string
|
181
|
+
:string
|
182
|
+
when Integer, :int
|
183
|
+
:int
|
184
|
+
when Float, :float
|
185
|
+
:float
|
186
|
+
when true, false, :boolean
|
187
|
+
:boolean
|
188
|
+
when :other
|
189
|
+
:other
|
190
|
+
else
|
191
|
+
:any
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def collect_types(types)
|
196
|
+
types[name] = true
|
197
|
+
end
|
198
|
+
|
199
|
+
def gql_type
|
200
|
+
type
|
201
|
+
end
|
202
|
+
|
203
|
+
def sample
|
204
|
+
case ts_type
|
205
|
+
when 'number'
|
206
|
+
0
|
207
|
+
when 'string'
|
208
|
+
''
|
209
|
+
when 'boolean'
|
210
|
+
true
|
211
|
+
when 'any'
|
212
|
+
nil
|
213
|
+
else
|
214
|
+
type
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
def ts_type
|
219
|
+
case type
|
220
|
+
when :int, :float
|
221
|
+
'number'
|
222
|
+
when :string, :number, :boolean
|
223
|
+
type.to_s
|
224
|
+
when Symbol
|
225
|
+
'any'
|
226
|
+
else
|
227
|
+
type.to_json
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
class HashTypeClass < TypeClass
|
233
|
+
def kind
|
234
|
+
'SCALAR'
|
235
|
+
end
|
236
|
+
|
237
|
+
def name
|
238
|
+
:other
|
239
|
+
end
|
240
|
+
|
241
|
+
def collect_types(types)
|
242
|
+
types[:other] = true
|
243
|
+
type.values.map do |v|
|
244
|
+
TypeClass.from(v, only, except).collect_types(types)
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
def association_type
|
249
|
+
type.values.each do |v|
|
250
|
+
t = TypeClass.from(v, only, except).association_type
|
251
|
+
return t if t
|
252
|
+
end
|
253
|
+
nil
|
254
|
+
end
|
255
|
+
|
256
|
+
def gql_type
|
257
|
+
'OBJECT'
|
258
|
+
end
|
259
|
+
|
260
|
+
def sample
|
261
|
+
type.reject { |k| k.to_s.ends_with? '?' }.transform_values do |v|
|
262
|
+
TypeClass.from(v).sample
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
def ts_type
|
267
|
+
fields = type.map do |key, value|
|
268
|
+
k = key.to_s == '*' ? '[key: string]' : key
|
269
|
+
"#{k}: #{TypeClass.from(value, only, except).ts_type}"
|
270
|
+
end
|
271
|
+
"{ #{fields.join('; ')} }"
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
class SerializableTypeClass < TypeClass
|
276
|
+
def field_only
|
277
|
+
[*only].map(&:to_s)
|
278
|
+
end
|
279
|
+
|
280
|
+
def field_except
|
281
|
+
[*except].map(&:to_s)
|
282
|
+
end
|
283
|
+
|
284
|
+
def kind
|
285
|
+
'OBJECT'
|
286
|
+
end
|
287
|
+
|
288
|
+
def name
|
289
|
+
name_segments = [type.name.delete(':')]
|
290
|
+
unless field_only.empty?
|
291
|
+
name_segments << 'Only'
|
292
|
+
name_segments << field_only.map(&:camelize)
|
293
|
+
end
|
294
|
+
unless field_except.empty?
|
295
|
+
name_segments << 'Except'
|
296
|
+
name_segments << field_except.map(&:camelize)
|
297
|
+
end
|
298
|
+
name_segments.join
|
299
|
+
end
|
300
|
+
|
301
|
+
def fields
|
302
|
+
keys = type._serializer_field_keys - ['__schema'] - field_except
|
303
|
+
keys = field_only & keys unless field_only.empty?
|
304
|
+
keys.map do |name|
|
305
|
+
FieldClass.new name, type._serializer_field_info(name)
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
def collect_types(types)
|
310
|
+
return if types[self]
|
311
|
+
types[self] = true
|
312
|
+
fields.each { |field| field.collect_types types }
|
313
|
+
end
|
314
|
+
|
315
|
+
def association_type
|
316
|
+
self
|
317
|
+
end
|
318
|
+
|
319
|
+
def gql_type
|
320
|
+
name
|
321
|
+
end
|
322
|
+
|
323
|
+
def ts_type
|
324
|
+
"Type#{name}"
|
325
|
+
end
|
326
|
+
|
327
|
+
def eql?(t)
|
328
|
+
self.class == t.class && self.compare_elements == t.compare_elements
|
329
|
+
end
|
330
|
+
|
331
|
+
def == t
|
332
|
+
eql? t
|
333
|
+
end
|
334
|
+
|
335
|
+
def compare_elements
|
336
|
+
[type, field_only, field_except]
|
337
|
+
end
|
338
|
+
|
339
|
+
def hash
|
340
|
+
compare_elements.hash
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
class OptionalTypeClass < TypeClass
|
345
|
+
def kind
|
346
|
+
of_type.kind
|
347
|
+
end
|
348
|
+
|
349
|
+
def name
|
350
|
+
of_type.name
|
351
|
+
end
|
352
|
+
|
353
|
+
def of_type
|
354
|
+
TypeClass.from type.first, only, except
|
355
|
+
end
|
356
|
+
|
357
|
+
def association_type
|
358
|
+
of_type.association_type
|
359
|
+
end
|
360
|
+
|
361
|
+
def collect_types(types)
|
362
|
+
of_type.collect_types types
|
363
|
+
end
|
364
|
+
|
365
|
+
def gql_type
|
366
|
+
of_type.gql_type
|
367
|
+
end
|
368
|
+
|
369
|
+
def sample
|
370
|
+
nil
|
371
|
+
end
|
372
|
+
|
373
|
+
def ts_type
|
374
|
+
"(#{of_type.ts_type} | null)"
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
class OrTypeClass < TypeClass
|
379
|
+
def kind
|
380
|
+
'OBJECT'
|
381
|
+
end
|
382
|
+
|
383
|
+
def name
|
384
|
+
:other
|
385
|
+
end
|
386
|
+
|
387
|
+
def of_types
|
388
|
+
type.map { |t| TypeClass.from t, only, except }
|
389
|
+
end
|
390
|
+
|
391
|
+
def collect_types(types)
|
392
|
+
types[:other] = true
|
393
|
+
of_types.map { |t| t.collect_types types }
|
394
|
+
end
|
395
|
+
|
396
|
+
def gql_type
|
397
|
+
kind
|
398
|
+
end
|
399
|
+
|
400
|
+
def sample
|
401
|
+
of_types.first.sample
|
402
|
+
end
|
403
|
+
|
404
|
+
def ts_type
|
405
|
+
'(' + of_types.map(&:ts_type).join(' | ') + ')'
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
409
|
+
class ListTypeClass < TypeClass
|
410
|
+
def kind
|
411
|
+
'LIST'
|
412
|
+
end
|
413
|
+
|
414
|
+
def name
|
415
|
+
'LIST'
|
416
|
+
end
|
417
|
+
|
418
|
+
def of_type
|
419
|
+
TypeClass.from type, only, except
|
420
|
+
end
|
421
|
+
|
422
|
+
def collect_types(types)
|
423
|
+
of_type.collect_types types
|
424
|
+
end
|
425
|
+
|
426
|
+
def association_type
|
427
|
+
of_type.association_type
|
428
|
+
end
|
429
|
+
|
430
|
+
def gql_type
|
431
|
+
"[#{of_type.gql_type}]"
|
432
|
+
end
|
433
|
+
|
434
|
+
def sample
|
435
|
+
[]
|
436
|
+
end
|
437
|
+
|
438
|
+
def ts_type
|
439
|
+
"(#{of_type.ts_type} [])"
|
440
|
+
end
|
441
|
+
end
|
442
|
+
end
|