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,33 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Yogurt
5
+ class CodeGenerator
6
+ class VariableDefinition < T::Struct
7
+ extend T::Sig
8
+ include Comparable
9
+
10
+ const :name, Symbol
11
+ const :graphql_name, String
12
+ const :signature, String
13
+ const :serializer, String
14
+ const :dependency, T.nilable(String)
15
+
16
+ sig {returns(T::Boolean)}
17
+ def optional?
18
+ signature.start_with?("T.nilable")
19
+ end
20
+
21
+ sig {params(other: VariableDefinition).returns(T.nilable(Integer))}
22
+ def <=>(other)
23
+ if optional? && !other.optional?
24
+ 1
25
+ elsif other.optional? && !optional?
26
+ -1
27
+ else
28
+ name <=> other.name
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,50 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Yogurt
5
+ module Converters
6
+ class Date
7
+ extend T::Sig
8
+ extend ScalarConverter
9
+
10
+ sig {override.returns(T::Types::Base)}
11
+ def self.type_alias
12
+ T.type_alias {::Date}
13
+ end
14
+
15
+ sig {override.params(value: ::Date).returns(String)}
16
+ def self.serialize(value)
17
+ value.iso8601
18
+ end
19
+
20
+ sig {override.params(raw_value: SCALAR_TYPE).returns(::Date)}
21
+ def self.deserialize(raw_value)
22
+ raise "Unexpected value returned for Date: #{raw_value.inspect}" if !raw_value.is_a?(String)
23
+
24
+ ::Date.iso8601(raw_value)
25
+ end
26
+ end
27
+
28
+ class Time
29
+ extend T::Sig
30
+ extend ScalarConverter
31
+
32
+ sig {override.returns(T::Types::Base)}
33
+ def self.type_alias
34
+ T.type_alias {::Time}
35
+ end
36
+
37
+ sig {override.params(value: ::Time).returns(String)}
38
+ def self.serialize(value)
39
+ value.iso8601
40
+ end
41
+
42
+ sig {override.params(raw_value: SCALAR_TYPE).returns(::Time)}
43
+ def self.deserialize(raw_value)
44
+ raise "Unexpected value returned for Time: #{raw_value.inspect}" if !raw_value.is_a?(String)
45
+
46
+ ::Time.iso8601(raw_value)
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,30 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Yogurt
5
+ module ErrorResult
6
+ include Kernel
7
+ extend T::Sig
8
+ extend T::Helpers
9
+
10
+ interface!
11
+
12
+ sig {abstract.returns(T.nilable(T::Array[OBJECT_TYPE]))}
13
+ def errors; end
14
+
15
+ class OnlyErrors
16
+ extend T::Sig
17
+ include ErrorResult
18
+
19
+ sig {params(errors: T::Array[OBJECT_TYPE]).void}
20
+ def initialize(errors)
21
+ @errors = T.let(errors, T::Array[OBJECT_TYPE])
22
+ end
23
+
24
+ sig {override.returns(T.nilable(T::Array[OBJECT_TYPE]))}
25
+ def errors
26
+ @errors
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,80 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require 'uri'
5
+ require 'net/http'
6
+ require 'json'
7
+
8
+ module Yogurt
9
+ class Http
10
+ extend T::Sig
11
+ include QueryExecutor
12
+
13
+ sig {params(uri: String, headers: T::Hash[String, String]).void}
14
+ def initialize(uri, headers: {})
15
+ parsed = URI.parse(uri)
16
+ if parsed.is_a?(URI::HTTP) || parsed.is_a?(URI::HTTPS)
17
+ @uri = T.let(parsed, T.any(URI::HTTP, URI::HTTPS))
18
+ else
19
+ raise ArgumentError, "Invalid URI: #{uri} (must be HTTP or HTTPS)"
20
+ end
21
+
22
+ @headers = T.let(headers, T::Hash[String, String])
23
+ end
24
+
25
+ # You can override this method in a subclass to set options on the HTTP request
26
+ sig {overridable.params(options: T.untyped).returns(T::Hash[String, String])}
27
+ def headers(options)
28
+ @headers
29
+ end
30
+
31
+ # You can override this in a subclass to get strongly typed options on auto-generated code
32
+ sig {override.returns(T::Types::Base)}
33
+ def options_type_alias
34
+ T.type_alias {T.untyped}
35
+ end
36
+
37
+ sig do
38
+ override.params(
39
+ query: String,
40
+ operation_name: String,
41
+ variables: T.nilable(T::Hash[String, T.untyped]),
42
+ options: T.untyped,
43
+ ).returns(T::Hash[String, T.untyped])
44
+ end
45
+ def execute(query, operation_name:, variables: nil, options: nil)
46
+ request = Net::HTTP::Post.new(@uri.request_uri)
47
+ request.basic_auth(@uri.user, @uri.password) if @uri.user || @uri.password
48
+
49
+ request["Accept"] = "application/json"
50
+ request["Content-Type"] = "application/json"
51
+
52
+ headers(options).each do |name, value|
53
+ request[name] = value
54
+ end
55
+
56
+ body = T.let({}, T::Hash[String, T.untyped])
57
+ body["query"] = query
58
+ body["variables"] = variables if variables&.any?
59
+ body["operationName"] = operation_name
60
+ request.body = JSON.generate(body)
61
+
62
+ response = connection.request(request)
63
+ case response
64
+ when Net::HTTPOK, Net::HTTPBadRequest
65
+ JSON.parse(response.body)
66
+ else
67
+ { "errors" => [{ "message" => "#{response.code} #{response.message}" }] }
68
+ end
69
+ end
70
+
71
+ # Returns an HTTP connection. You can override in subclasses to customize the conection.
72
+ sig {overridable.returns(Net::HTTP)}
73
+ def connection
74
+ client = Net::HTTP.new(@uri.host, @uri.port)
75
+ client.use_ssl = @uri.scheme == "https"
76
+
77
+ client
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,22 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require 'pp'
5
+
6
+ module Yogurt
7
+ module Inspectable
8
+ extend T::Sig
9
+ extend T::Helpers
10
+ include PP::ObjectMixin
11
+
12
+ abstract!
13
+
14
+ sig {abstract.params(p: PP::PPMethods).void}
15
+ def pretty_print(p); end
16
+
17
+ sig {returns(String)}
18
+ def inspect
19
+ pretty_print_inspect
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,33 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Yogurt
5
+ module Memoize
6
+ extend T::Sig
7
+
8
+ sig do
9
+ type_parameters(:ReturnType)
10
+ .params(
11
+ name: Symbol,
12
+ block: T.proc.returns(T.type_parameter(:ReturnType)),
13
+ )
14
+ .returns(T.type_parameter(:ReturnType))
15
+ end
16
+ def memoize_as(name, &block)
17
+ memoized_values = @memoized_values
18
+ memoized_values = @memoized_values = {} if memoized_values.nil?
19
+
20
+ return memoized_values[name] if memoized_values.key?(name)
21
+
22
+ memoized_values[name] = yield
23
+ end
24
+
25
+ sig {returns(T.self_type)}
26
+ def freeze
27
+ @memoized_values = T.let(@memoized_values, T.nilable(T::Hash[Symbol, T.untyped]))
28
+ @memoized_values&.freeze
29
+ super
30
+ self
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,23 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Yogurt
5
+ class Query
6
+ extend T::Sig
7
+ extend T::Helpers
8
+ abstract!
9
+
10
+ sig {params(result: OBJECT_TYPE).returns(T.any(T.attached_class, Yogurt::ErrorResult::OnlyErrors))}
11
+ def self.from_result(result)
12
+ data = result['data']
13
+ if data
14
+ new(data, result['errors'])
15
+ else
16
+ Yogurt::ErrorResult::OnlyErrors.new(result['errors'])
17
+ end
18
+ end
19
+
20
+ sig {params(data: OBJECT_TYPE, errors: T.nilable(OBJECT_TYPE)).void}
21
+ def initialize(data, errors); end
22
+ end
23
+ end
@@ -0,0 +1,89 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Yogurt
5
+ module QueryContainer
6
+ extend T::Sig
7
+
8
+ CONTAINER = T.type_alias do
9
+ T.all(Module, QueryContainer)
10
+ end
11
+
12
+ VALIDATION_RULES = T.let(
13
+ [
14
+ *GraphQL::StaticValidation::ALL_RULES,
15
+ InterfacesAndUnionsHaveTypename
16
+ ].freeze,
17
+ T::Array[T.untyped],
18
+ )
19
+
20
+ sig {params(other: Module).void}
21
+ def self.included(other)
22
+ Kernel.raise(ValidationError, "You need to `extend Yogurt::QueryContainer`, you cannot use `include`.")
23
+ end
24
+
25
+ sig {params(other: Module).void}
26
+ def self.extended(other)
27
+ super
28
+ QueryContainer.containers << T.cast(other, CONTAINER)
29
+ end
30
+
31
+ sig {returns(T::Array[CONTAINER])}
32
+ def self.containers
33
+ @containers = T.let(@containers, T.nilable(T::Array[CONTAINER]))
34
+ @containers ||= []
35
+ end
36
+
37
+ sig {returns(T::Array[QueryDeclaration])}
38
+ def declared_queries
39
+ @declared_queries = T.let(@declared_queries, T.nilable(T::Array[QueryDeclaration]))
40
+ @declared_queries ||= []
41
+ end
42
+
43
+ sig {params(operation_name: String).returns(QueryDeclaration)}
44
+ def fetch_query(operation_name)
45
+ result = declared_queries.detect {|d| d.operations.include?(operation_name)}
46
+ T.must(result)
47
+ end
48
+
49
+ sig do
50
+ params(
51
+ query_text: String,
52
+ schema: T.nilable(T.class_of(GraphQL::Schema)),
53
+ ).void
54
+ end
55
+ def declare_query(query_text, schema: nil)
56
+ schema ||= Yogurt.default_schema
57
+ Kernel.raise(ValidationError, "You need to either provide a `schema:` to declare_query, or set Yogurt.default_schema") if schema.nil?
58
+
59
+ case (container = self)
60
+ when Module
61
+ # noop
62
+ else
63
+ Kernel.raise(ValidationError, "You need to `extend Yogurt::QueryContainer`, you cannot use `include`.")
64
+ end
65
+
66
+ Kernel.raise(ValidationError, "Query containers must be classes or modules that are assigned to constants.") if container.name.nil?
67
+
68
+ validator = GraphQL::StaticValidation::Validator.new(schema: schema, rules: VALIDATION_RULES)
69
+ query = GraphQL::Query.new(schema, query_text)
70
+ validation_result = validator.validate(query)
71
+
72
+ error = validation_result[:errors][0]
73
+ Kernel.raise(ValidationError, error.message) if error
74
+
75
+ if query.operations.key?(nil)
76
+ Kernel.raise(ValidationError, "You must provide a name for each of the operations in your GraphQL query.")
77
+ elsif query.operations.none?
78
+ Kernel.raise(ValidationError, "Your query did not define any operations.")
79
+ end
80
+
81
+ declared_queries << QueryDeclaration.new(
82
+ container: container,
83
+ operations: query.operations.keys.freeze,
84
+ query_text: query_text,
85
+ schema: schema,
86
+ )
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,107 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Yogurt
5
+ module QueryContainer
6
+ module InterfacesAndUnionsHaveTypename
7
+ include Kernel
8
+ extend T::Sig
9
+
10
+ class Error < GraphQL::StaticValidation::Error
11
+ extend T::Sig
12
+
13
+ sig {returns(T.nilable(String))}
14
+ attr_reader :type_name
15
+
16
+ sig {returns(String)}
17
+ attr_reader :node_name
18
+
19
+ sig do
20
+ params(
21
+ message: String,
22
+ node_name: String,
23
+ path: T.nilable(String),
24
+ nodes: T.untyped,
25
+ type: T.nilable(String),
26
+ ).void
27
+ end
28
+ def initialize(message, node_name:, path: nil, nodes: [], type: nil)
29
+ super(message, path: path, nodes: nodes)
30
+ @node_name = T.let(node_name, String)
31
+ @type_name = T.let(type, T.nilable(String))
32
+ end
33
+
34
+ # A hash representation of this Message
35
+ sig {returns(T::Hash[String, T.untyped])}
36
+ def to_h
37
+ extensions = {
38
+ "code" => code,
39
+ "nodeName" => @node_name
40
+ }
41
+
42
+ extensions['typeName'] = @type_name if @type_name
43
+ super.merge({ "extensions" => extensions })
44
+ end
45
+
46
+ sig {returns(String)}
47
+ def code
48
+ "interfaceOrUnionMissingTypename"
49
+ end
50
+ end
51
+
52
+ sig {params(node: GraphQL::Language::Nodes::Field, parent: T.untyped).void}
53
+ def on_field(node, parent)
54
+ super if validate_interface_union_includes_typename(node, T.unsafe(self).field_definition.type.unwrap)
55
+ end
56
+
57
+ sig {params(node: GraphQL::Language::Nodes::OperationDefinition, parent: T.untyped).void}
58
+ def on_operation_definition(node, parent)
59
+ super if validate_interface_union_includes_typename(node, T.unsafe(self).type_definition)
60
+ end
61
+
62
+ sig do
63
+ params(
64
+ node: T.any(GraphQL::Language::Nodes::Field, GraphQL::Language::Nodes::OperationDefinition),
65
+ type_definition: T.untyped,
66
+ ).returns(T::Boolean)
67
+ end
68
+ private def validate_interface_union_includes_typename(node, type_definition)
69
+ return true if node.selections.nil?
70
+ return true if node.selections.empty?
71
+
72
+ type_kind = type_definition.kind
73
+ return true unless type_kind.interface? || type_kind.union?
74
+ return true if node.selections.any? do |selection|
75
+ next false unless selection.is_a?(GraphQL::Language::Nodes::Field)
76
+ next false unless selection.name == '__typename'
77
+
78
+ selection.alias.nil?
79
+ end
80
+
81
+ msg = "Interfaces and unions must include the __typename field (%{node_name} returns #{type_definition.graphql_name} but doesn't select __typename)"
82
+ node_name = case node
83
+ when GraphQL::Language::Nodes::Field
84
+ "field '#{node.name}'"
85
+ when GraphQL::Language::Nodes::OperationDefinition
86
+ if node.name.nil?
87
+ "anonymous query"
88
+ else
89
+ "#{node.operation_type} '#{node.name}'"
90
+ end
91
+ else
92
+ T.absurd(node)
93
+ end
94
+
95
+ error = Error.new(
96
+ format(msg, node_name: node_name),
97
+ nodes: node,
98
+ node_name: node_name,
99
+ type: type_definition.graphql_name,
100
+ )
101
+
102
+ send(:add_error, error)
103
+ false
104
+ end
105
+ end
106
+ end
107
+ end