yogurt 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. checksums.yaml +7 -0
  2. data/.DS_Store +0 -0
  3. data/.github/workflows/tests.yml +117 -0
  4. data/.gitignore +12 -0
  5. data/.rspec +3 -0
  6. data/.rubocop.yml +113 -0
  7. data/.ruby-gemset +1 -0
  8. data/.ruby-version +1 -0
  9. data/.travis.yml +7 -0
  10. data/CHANGELOG.md +0 -0
  11. data/Gemfile +6 -0
  12. data/Gemfile.lock +82 -0
  13. data/LICENSE +201 -0
  14. data/README.md +38 -0
  15. data/Rakefile +9 -0
  16. data/bin/bundle +105 -0
  17. data/bin/byebug +29 -0
  18. data/bin/coderay +29 -0
  19. data/bin/console +16 -0
  20. data/bin/htmldiff +29 -0
  21. data/bin/ldiff +29 -0
  22. data/bin/pry +29 -0
  23. data/bin/rake +29 -0
  24. data/bin/rspec +29 -0
  25. data/bin/rubocop +29 -0
  26. data/bin/ruby-parse +29 -0
  27. data/bin/ruby-rewrite +29 -0
  28. data/bin/setup +8 -0
  29. data/bin/srb +29 -0
  30. data/bin/srb-rbi +29 -0
  31. data/lib/yogurt.rb +95 -0
  32. data/lib/yogurt/code_generator.rb +706 -0
  33. data/lib/yogurt/code_generator/defined_class.rb +22 -0
  34. data/lib/yogurt/code_generator/defined_class_sorter.rb +44 -0
  35. data/lib/yogurt/code_generator/defined_method.rb +24 -0
  36. data/lib/yogurt/code_generator/enum_class.rb +56 -0
  37. data/lib/yogurt/code_generator/field_access_method.rb +273 -0
  38. data/lib/yogurt/code_generator/field_access_path.rb +56 -0
  39. data/lib/yogurt/code_generator/generated_file.rb +53 -0
  40. data/lib/yogurt/code_generator/input_class.rb +52 -0
  41. data/lib/yogurt/code_generator/leaf_class.rb +68 -0
  42. data/lib/yogurt/code_generator/operation_declaration.rb +12 -0
  43. data/lib/yogurt/code_generator/root_class.rb +130 -0
  44. data/lib/yogurt/code_generator/type_wrapper.rb +13 -0
  45. data/lib/yogurt/code_generator/typed_input.rb +12 -0
  46. data/lib/yogurt/code_generator/typed_output.rb +16 -0
  47. data/lib/yogurt/code_generator/utils.rb +140 -0
  48. data/lib/yogurt/code_generator/variable_definition.rb +33 -0
  49. data/lib/yogurt/converters.rb +50 -0
  50. data/lib/yogurt/error_result.rb +30 -0
  51. data/lib/yogurt/http.rb +80 -0
  52. data/lib/yogurt/inspectable.rb +22 -0
  53. data/lib/yogurt/memoize.rb +33 -0
  54. data/lib/yogurt/query.rb +23 -0
  55. data/lib/yogurt/query_container.rb +89 -0
  56. data/lib/yogurt/query_container/interfaces_and_unions_have_typename.rb +107 -0
  57. data/lib/yogurt/query_declaration.rb +11 -0
  58. data/lib/yogurt/query_executor.rb +23 -0
  59. data/lib/yogurt/query_result.rb +25 -0
  60. data/lib/yogurt/scalar_converter.rb +19 -0
  61. data/lib/yogurt/unexpected_object_type.rb +30 -0
  62. data/lib/yogurt/validation_error.rb +6 -0
  63. data/lib/yogurt/version.rb +6 -0
  64. data/sorbet/config +10 -0
  65. data/sorbet/rbi/fake_schema.rbi +14 -0
  66. data/sorbet/rbi/graphql.rbi +11 -0
  67. data/sorbet/rbi/hidden-definitions/errors.txt +20513 -0
  68. data/sorbet/rbi/hidden-definitions/hidden.rbi +42882 -0
  69. data/sorbet/rbi/sorbet-typed/lib/graphql/all/graphql.rbi +48 -0
  70. data/sorbet/rbi/sorbet-typed/lib/rainbow/all/rainbow.rbi +276 -0
  71. data/sorbet/rbi/sorbet-typed/lib/rubocop/~>0.85/rubocop.rbi +2072 -0
  72. data/sorbet/rbi/todo.rbi +6 -0
  73. data/yogurt.gemspec +54 -0
  74. metadata +286 -0
@@ -0,0 +1,53 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Yogurt
5
+ class CodeGenerator
6
+ class GeneratedFile < T::Struct
7
+ extend T::Sig
8
+
9
+ class FileType < T::Enum
10
+ enums do
11
+ # File contains the definition of a GraphQL Enum
12
+ ENUM = new('enum')
13
+
14
+ # File contains the definition of a GraphQL operation (ie, a root query result)
15
+ OPERATION = new('operation')
16
+
17
+ # File contains the definition of a GraphQL object result (ie, a non-root query result)
18
+ OBJECT_RESULT = new('object_result')
19
+
20
+ # File contains the definition of a GraphQL input object
21
+ INPUT_OBJECT = new('input_object')
22
+ end
23
+ end
24
+
25
+ # The name of the constant that is stored in this file
26
+ const :constant_name, String
27
+
28
+ # Type of constant that is stored in this file
29
+ const :type, FileType
30
+
31
+ # Names of the constants that this file references. If you are not using an
32
+ # autoloading tool, the files containing these constants need to be `require`'d
33
+ # at the start of the file.
34
+ const :dependencies, T::Array[String]
35
+
36
+ # The code that defines the constant that is stored in this file
37
+ const :code, String
38
+
39
+ # Full contents of the file
40
+ sig {returns(String)}
41
+ def contents
42
+ <<~STRING
43
+ # typed: strict
44
+ # frozen_string_literal: true
45
+
46
+ require 'pp'
47
+
48
+ #{code}
49
+ STRING
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,52 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Yogurt
5
+ class CodeGenerator
6
+ # Input classes are used for input objects
7
+ class InputClass < T::Struct
8
+ extend T::Sig
9
+ include Utils
10
+ include DefinedClass
11
+
12
+ const :name, String
13
+ const :arguments, T::Array[VariableDefinition]
14
+
15
+ sig {override.returns(T::Array[String])}
16
+ def dependencies
17
+ arguments.map(&:dependency).compact
18
+ end
19
+
20
+ sig {override.returns(String)}
21
+ def to_ruby
22
+ extract = []
23
+ props = []
24
+ serializers = []
25
+ arguments.sort.each do |definition|
26
+ props << "prop #{definition.name.inspect}, #{definition.signature}"
27
+ extract << "#{definition.name} = self.#{definition.name}"
28
+ serializers.push(<<~STRING.strip)
29
+ #{definition.graphql_name.inspect} => #{definition.serializer.strip},
30
+ STRING
31
+ end
32
+
33
+ <<~STRING
34
+ class #{name} < T::Struct
35
+ extend T::Sig
36
+
37
+ #{indent(props.join("\n"), 1).strip}
38
+
39
+ sig {returns(T::Hash[String, T.untyped])}
40
+ def serialize
41
+ #{indent(extract.join("\n"), 2).strip}
42
+
43
+ {
44
+ #{indent(serializers.join("\n"), 3).strip}
45
+ }
46
+ end
47
+ end
48
+ STRING
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,68 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Yogurt
5
+ class CodeGenerator
6
+ # Leaf classes are generated for the inner types of query results.
7
+ class LeafClass < T::Struct
8
+ include DefinedClass
9
+ extend T::Sig
10
+ include Utils
11
+
12
+ const :name, String
13
+ const :schema, GRAPHQL_SCHEMA
14
+ const :graphql_type, T.untyped # rubocop:disable Sorbet/ForbidUntypedStructProps
15
+ prop :defined_methods, T::Array[DefinedMethod]
16
+ prop :dependencies, T::Array[String]
17
+
18
+ # Adds the defined methods to the ones that are already defined in the class
19
+ sig {params(extra_methods: T::Array[DefinedMethod]).void}
20
+ def merge_defined_methods(extra_methods)
21
+ own_methods = defined_methods.map {|dm| [dm.name, dm]}.to_h
22
+ extra_methods.each do |extra|
23
+ own = own_methods[extra.name]
24
+ if own.nil?
25
+ own_methods[extra.name] = extra
26
+ elsif !own.merge?(extra)
27
+ raise "Cannot merge method #{extra.inspect} into #{own.inspect}"
28
+ end
29
+ end
30
+
31
+ self.defined_methods = own_methods.values
32
+ end
33
+
34
+ sig {override.returns(String)}
35
+ def to_ruby
36
+ pretty_print = generate_pretty_print(defined_methods)
37
+
38
+ dynamic_methods = <<~STRING.strip
39
+ #{defined_methods.map(&:to_ruby).join("\n")}
40
+ #{pretty_print}
41
+ STRING
42
+
43
+ <<~STRING
44
+ class #{name}
45
+ extend T::Sig
46
+ include Yogurt::QueryResult
47
+
48
+ #{indent(possible_types_constant(schema, graphql_type), 1).strip}
49
+
50
+ sig {params(result: Yogurt::OBJECT_TYPE).void}
51
+ def initialize(result)
52
+ @result = T.let(result, Yogurt::OBJECT_TYPE)
53
+ end
54
+
55
+ sig {override.returns(Yogurt::OBJECT_TYPE)}
56
+ def raw_result
57
+ @result
58
+ end
59
+
60
+ #{indent(typename_method(schema, graphql_type), 1).strip}
61
+
62
+ #{indent(dynamic_methods, 1).strip}
63
+ end
64
+ STRING
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,12 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Yogurt
5
+ class CodeGenerator
6
+ class OperationDeclaration < T::Struct
7
+ const :declaration, QueryDeclaration
8
+ const :operation_name, String
9
+ const :variables, T::Array[GraphQL::Language::Nodes::VariableDefinition]
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,130 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Yogurt
5
+ class CodeGenerator
6
+ # Root classes are generated for the root of a GraphQL query.
7
+ class RootClass < T::Struct
8
+ include DefinedClass
9
+ extend T::Sig
10
+ include Utils
11
+
12
+ const :name, String
13
+ const :schema, GRAPHQL_SCHEMA
14
+ const :operation_name, String
15
+ const :graphql_type, T.untyped # rubocop:disable Sorbet/ForbidUntypedStructProps
16
+ const :query_container, QueryContainer::CONTAINER
17
+ const :defined_methods, T::Array[DefinedMethod]
18
+ const :variables, T::Array[VariableDefinition]
19
+ const :dependencies, T::Array[String]
20
+
21
+ sig {override.returns(String)}
22
+ def to_ruby
23
+ pretty_print = generate_pretty_print(defined_methods)
24
+ declaration = query_container.fetch_query(operation_name)
25
+ original_query = declaration.query_text
26
+ parsed_query = GraphQL.parse(original_query)
27
+ reprinted_query = GraphQL::Language::Printer.new.print(parsed_query)
28
+
29
+ dynamic_methods = <<~STRING.strip
30
+ #{defined_methods.map(&:to_ruby).join("\n")}
31
+ #{pretty_print}
32
+ STRING
33
+
34
+ <<~STRING
35
+ class #{name} < Yogurt::Query
36
+ extend T::Sig
37
+ include Yogurt::QueryResult
38
+ include Yogurt::ErrorResult
39
+
40
+ SCHEMA = T.let(#{schema.name}, Yogurt::GRAPHQL_SCHEMA)
41
+ OPERATION_NAME = T.let(#{operation_name.inspect}, String)
42
+ QUERY_TEXT = T.let(<<~'GRAPHQL', String)
43
+ #{indent(reprinted_query, 2).strip}
44
+ GRAPHQL
45
+ #{indent(possible_types_constant(schema, graphql_type), 1).strip}
46
+
47
+ #{indent(execute_method, 1).strip}
48
+
49
+ sig {params(data: Yogurt::OBJECT_TYPE, errors: T.nilable(T::Array[Yogurt::OBJECT_TYPE])).void}
50
+ def initialize(data, errors)
51
+ @result = T.let(data, Yogurt::OBJECT_TYPE)
52
+ @errors = T.let(errors, T.nilable(T::Array[Yogurt::OBJECT_TYPE]))
53
+ end
54
+
55
+ sig {override.returns(Yogurt::OBJECT_TYPE)}
56
+ def raw_result
57
+ @result
58
+ end
59
+
60
+ #{indent(typename_method(schema, graphql_type), 1).strip}
61
+
62
+ sig {override.returns(T.nilable(T::Array[Yogurt::OBJECT_TYPE]))}
63
+ def errors
64
+ @errors
65
+ end
66
+
67
+ #{indent(dynamic_methods, 1).strip}
68
+ end
69
+ STRING
70
+ end
71
+
72
+ sig {returns(String)}
73
+ def execute_method
74
+ executor = Yogurt.registered_schemas.fetch(schema)
75
+ options_type = executor.options_type_alias.name
76
+ signature_params = ["options: #{options_type}"]
77
+
78
+ params = if options_type.start_with?("T.nilable", "T.untyped")
79
+ ["options=nil"]
80
+ else
81
+ ["options"]
82
+ end
83
+
84
+ variable_extraction = if variables.any?
85
+ serializers = []
86
+
87
+ variables.sort.each do |variable|
88
+ if variable.signature.start_with?("T.nilable")
89
+ params.push("#{variable.name}: nil")
90
+ else
91
+ params.push("#{variable.name}:")
92
+ end
93
+
94
+ signature_params.push("#{variable.name}: #{variable.signature}")
95
+ serializers.push(<<~STRING.strip)
96
+ #{variable.graphql_name.inspect} => #{variable.serializer.strip},
97
+ STRING
98
+ end
99
+
100
+ <<~STRING
101
+ {
102
+ #{indent(serializers.join("\n"), 1).strip}
103
+ }
104
+ STRING
105
+ else
106
+ "nil"
107
+ end
108
+
109
+ <<~STRING
110
+ sig do
111
+ params(
112
+ #{indent(signature_params.join(",\n"), 2).strip}
113
+ ).returns(T.any(T.attached_class, Yogurt::ErrorResult::OnlyErrors))
114
+ end
115
+ def self.execute(#{params.join(', ')})
116
+ raw_result = Yogurt.execute(
117
+ query: QUERY_TEXT,
118
+ schema: SCHEMA,
119
+ operation_name: OPERATION_NAME,
120
+ options: options,
121
+ variables: #{indent(variable_extraction, 2).strip}
122
+ )
123
+
124
+ from_result(raw_result)
125
+ end
126
+ STRING
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,13 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Yogurt
5
+ class CodeGenerator
6
+ class TypeWrapper < T::Enum
7
+ enums do
8
+ NILABLE = new
9
+ ARRAY = new
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,12 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Yogurt
5
+ class CodeGenerator
6
+ class TypedInput < T::Struct
7
+ const :signature, String
8
+ const :serializer, String
9
+ const :dependency, T.nilable(String)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,16 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Yogurt
5
+ class CodeGenerator
6
+ class TypedOutput < T::Struct
7
+ # The signature to put in the sorbet type
8
+ const :signature, String
9
+
10
+ # Converter function to use for the return result.
11
+ # This assumes that a local variable named `raw_value` has the
12
+ # value to be converted.
13
+ const :deserializer, String
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,140 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Yogurt
5
+ class CodeGenerator
6
+ module Utils
7
+ extend T::Sig
8
+ extend self
9
+
10
+ sig {params(schema: GRAPHQL_SCHEMA, graphql_type: T.untyped).returns(String)}
11
+ def typename_method(schema, graphql_type)
12
+ possible_types = schema.possible_types(graphql_type)
13
+
14
+ if possible_types.size == 1
15
+ <<~STRING
16
+ sig {override.returns(String)}
17
+ def __typename
18
+ #{possible_types.fetch(0).graphql_name.inspect}
19
+ end
20
+ STRING
21
+ else
22
+ <<~STRING
23
+ sig {override.returns(String)}
24
+ def __typename
25
+ raw_result["__typename"]
26
+ end
27
+ STRING
28
+ end
29
+ end
30
+
31
+ sig {params(schema: GRAPHQL_SCHEMA, graphql_type: T.untyped).returns(String)}
32
+ def possible_types_constant(schema, graphql_type)
33
+ possible_types = schema
34
+ .possible_types(graphql_type)
35
+ .map(&:graphql_name)
36
+ .sort
37
+ .map(&:inspect)
38
+
39
+ single_line = possible_types.join(', ')
40
+ if single_line.size <= 80
41
+ <<~STRING.strip
42
+ POSSIBLE_TYPES = T.let(
43
+ [#{single_line}],
44
+ T::Array[String]
45
+ )
46
+ STRING
47
+ else
48
+ multi_line = possible_types.join(",\n")
49
+ <<~STRING.strip
50
+ POSSIBLE_TYPES = T.let(
51
+ [
52
+ #{indent(multi_line, 2).strip}
53
+ ].freeze,
54
+ T::Array[String]
55
+ )
56
+ STRING
57
+ end
58
+ end
59
+
60
+ sig {params(camel_cased_word: String).returns(String)}
61
+ def underscore(camel_cased_word)
62
+ return camel_cased_word unless /[A-Z-]|::/.match?(camel_cased_word)
63
+
64
+ word = camel_cased_word.to_s.gsub("::", "/")
65
+ word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2')
66
+ word.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
67
+ word.tr!("-", "_")
68
+ word.downcase!
69
+ word
70
+ end
71
+
72
+ sig {params(term: String).returns(String)}
73
+ def camelize(term)
74
+ string = term.to_s
75
+ string = string.sub(/^[a-z\d]*/, &:capitalize)
76
+ string.gsub!(%r{(?:_|(/))([a-z\d]*)}i) {"#{Regexp.last_match(1)}#{T.must(Regexp.last_match(2)).capitalize}"}
77
+ string.gsub!("/", "::")
78
+ string
79
+ end
80
+
81
+ sig {params(string: String, amount: Integer).returns(String)}
82
+ def indent(string, amount)
83
+ return string if amount.zero?
84
+
85
+ padding = ' ' * amount
86
+
87
+ buffer = T.unsafe(String).new("", capacity: string.size)
88
+ string.each_line do |line|
89
+ buffer << padding if line.size > 1 || line != "\n"
90
+ buffer << line
91
+ end
92
+
93
+ buffer
94
+ end
95
+
96
+ sig {params(desired_name: String).returns(Symbol)}
97
+ def generate_method_name(desired_name)
98
+ base_desired_name = desired_name
99
+ escaping_level = 0
100
+
101
+ while PROTECTED_NAMES.include?(desired_name)
102
+ escaping_level += 1
103
+ desired_name = "#{base_desired_name}#{'_' * escaping_level}"
104
+ end
105
+
106
+ desired_name.to_sym
107
+ end
108
+
109
+ sig {params(methods: T::Array[DefinedMethod]).returns(String)}
110
+ def generate_pretty_print(methods)
111
+ inspect_lines = methods.map do |dm|
112
+ <<~STRING
113
+ p.comma_breakable
114
+ p.text(#{dm.name.to_s.inspect})
115
+ p.text(': ')
116
+ p.pp(#{dm.name})
117
+ STRING
118
+ end
119
+
120
+ object_group = <<~STRING.strip
121
+ p.breakable
122
+ p.text('__typename')
123
+ p.text(': ')
124
+ p.pp(__typename)
125
+
126
+ #{inspect_lines.join("\n\n")}
127
+ STRING
128
+
129
+ <<~STRING
130
+ sig {override.params(p: PP::PPMethods).void}
131
+ def pretty_print(p)
132
+ p.object_group(self) do
133
+ #{indent(object_group, 2).strip}
134
+ end
135
+ end
136
+ STRING
137
+ end
138
+ end
139
+ end
140
+ end