yogurt 0.1.1

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.
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