├── lib ├── tinygql │ ├── version.rb │ ├── visitors.rb.erb │ ├── nodes.rb.erb │ ├── nodes.yml │ ├── lexer.rb │ └── parser.rb └── tinygql.rb ├── .gitignore ├── Gemfile ├── bin ├── profile.rb ├── make_hash.rb └── bench.rb ├── test ├── helper.rb ├── schema-extensions.graphql ├── kitchen-sink.graphql ├── schema-kitchen-sink.graphql ├── lexer_test.rb └── parser_test.rb ├── tinygql.gemspec ├── .github └── workflows │ └── ci.yml ├── README.md ├── Rakefile ├── CODE_OF_CONDUCT.md ├── LICENSE └── benchmark └── fixtures └── negotiate.gql /lib/tinygql/version.rb: -------------------------------------------------------------------------------- 1 | module TinyGQL 2 | VERSION = '0.3.1' 3 | end 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib/tinygql/nodes.rb 2 | lib/tinygql/visitors.rb 3 | Gemfile.lock 4 | pkg 5 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "benchmark-ips" 4 | 5 | gemspec 6 | -------------------------------------------------------------------------------- /bin/profile.rb: -------------------------------------------------------------------------------- 1 | require "vernier" 2 | 3 | Vernier.trace(out: "time_profile.json") { 4 | require_relative "bench" 5 | } 6 | -------------------------------------------------------------------------------- /test/helper.rb: -------------------------------------------------------------------------------- 1 | ENV["MT_NO_PLUGINS"] = "1" 2 | 3 | require "tinygql/parser" 4 | require "tldr" 5 | 6 | module TinyGQL 7 | class Test < TLDR 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/tinygql.rb: -------------------------------------------------------------------------------- 1 | require "tinygql/parser" 2 | require "tinygql/version" 3 | 4 | module TinyGQL 5 | autoload :Visitors, "tinygql/visitors" 6 | 7 | def self.parse doc 8 | Parser.new(doc).parse 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /tinygql.gemspec: -------------------------------------------------------------------------------- 1 | require_relative "lib/tinygql/version" 2 | 3 | Gem::Specification.new do |s| 4 | s.name = "tinygql" 5 | s.version = TinyGQL::VERSION 6 | s.summary = "A GraphQL parser" 7 | s.description = "Yet another GraphQL parser written in Ruby." 8 | s.authors = ["Aaron Patterson"] 9 | s.email = "tenderlove@ruby-lang.org" 10 | s.files = `git ls-files -z`.split("\x0") + [ 11 | "lib/tinygql/nodes.rb", 12 | "lib/tinygql/visitors.rb", 13 | ] 14 | s.test_files = s.files.grep(%r{^test/}) 15 | s.homepage = "https://github.com/tenderlove/tinygql" 16 | s.license = "Apache-2.0" 17 | 18 | s.add_development_dependency("rake", "~> 13.0") 19 | s.add_development_dependency("tldr", "~> 0.6.2") 20 | end 21 | -------------------------------------------------------------------------------- /test/schema-extensions.graphql: -------------------------------------------------------------------------------- 1 | extend scalar PositiveInt 2 | @serializationType(name: "global::System.Int32") 3 | @runtimeType(name: "global::System.Int32") 4 | 5 | extend scalar Aaron 6 | 7 | extend interface NamedEntity { 8 | nickname: String 9 | } 10 | 11 | extend type Person { 12 | nickname: String 13 | } 14 | 15 | extend type Business { 16 | nickname: String 17 | } 18 | 19 | extend interface NamedEntity @addedDirective 20 | 21 | extend union Cool @foo 22 | 23 | extend union Great @onUnion = A | B 24 | 25 | extend enum Direction { 26 | NORTH 27 | EAST 28 | SOUTH 29 | WEST 30 | } 31 | 32 | extend enum AnnotatedEnum @onEnum { 33 | ANNOTATED_VALUE @onEnumValue 34 | OTHER_VALUE 35 | } 36 | 37 | extend enum Neat @onEnum 38 | 39 | extend input InputType { 40 | key: String! 41 | answer: Int = 42 42 | } 43 | 44 | extend input AnnotatedInput @onInputObjectType { 45 | annotatedField: Type @onField 46 | } 47 | 48 | extend input NeatInput @onInputObjectType 49 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | runs-on: ${{ matrix.os }}-latest 8 | 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | os: [ubuntu, macos] 13 | ruby: [ head, 3.2, truffleruby, truffleruby-head, jruby ] 14 | 15 | steps: 16 | - uses: actions/checkout@v3 17 | - name: Set up Ruby 18 | uses: ruby/setup-ruby@v1 19 | with: 20 | ruby-version: ${{ matrix.ruby }} 21 | 22 | - name: Install dependencies 23 | run: bundle install 24 | - name: Run tests 25 | run: bundle exec rake 26 | 27 | gem-test: 28 | runs-on: ${{ matrix.os }}-latest 29 | 30 | strategy: 31 | fail-fast: false 32 | matrix: 33 | os: [ubuntu, macos] 34 | ruby: [ head, 3.2, truffleruby, truffleruby-head ] 35 | 36 | steps: 37 | - uses: actions/checkout@v3 38 | - name: Set up Ruby 39 | uses: ruby/setup-ruby@v1 40 | with: 41 | ruby-version: ${{ matrix.ruby }} 42 | 43 | - name: Install dependencies 44 | run: bundle install 45 | - name: Run tests 46 | run: bundle exec rake gem:test 47 | -------------------------------------------------------------------------------- /bin/make_hash.rb: -------------------------------------------------------------------------------- 1 | require "tinygql" 2 | 3 | # Calculate a perfect hash for GraphQL keywords 4 | 5 | def bits x 6 | count = 0 7 | while x > 0 8 | count += 1 9 | x >>= 1 10 | end 11 | count 12 | end 13 | 14 | # on is too short, and subscription is the longest. 15 | # The lexer can easily detect them by length, so lets calculate a perfect 16 | # hash for the rest. 17 | kws = TinyGQL::Lexer::KEYWORDS - ["on", "subscription"] 18 | MASK = (1 << bits(kws.length)) - 1 19 | 20 | prefixes = kws.map { |word| word[1, 2] } 21 | 22 | # make sure they're unique 23 | raise "Not unique" unless prefixes.uniq == prefixes 24 | 25 | keys = prefixes.map { |prefix| 26 | prefix.bytes.reverse.inject(0) { |c, byte| 27 | c << 8 | byte 28 | } 29 | } 30 | 31 | shift = 32 - bits(kws.length) # use the top bits 32 | 33 | c = 13 34 | loop do 35 | z = keys.map { |k| ((k * c) >> shift) & MASK } 36 | break if z.uniq.length == z.length 37 | c += 1 38 | end 39 | 40 | table = kws.zip(keys).each_with_object([]) { |(word, k), o| 41 | hash = ((k * c) >> shift) & MASK 42 | o[hash] = word.upcase.to_sym 43 | } 44 | 45 | print "KW_LUT =" 46 | pp table 47 | puts <<-eomethod 48 | def hash key 49 | (key * #{c}) >> #{shift} & #{sprintf("%#0x", MASK)} 50 | end 51 | eomethod 52 | -------------------------------------------------------------------------------- /test/kitchen-sink.graphql: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015-present, Facebook, Inc. 2 | # 3 | # This source code is licensed under the MIT license found in the 4 | # LICENSE file in the root directory of this source tree. 5 | 6 | query queryName($foo: ComplexType, $site: Site = MOBILE) { 7 | whoever123is: node(id: [123, 456]) { 8 | id , 9 | ... on User @defer { 10 | field2 { 11 | id , 12 | alias: field1(first:10, after:$foo,) @include(if: $foo) { 13 | id, 14 | ...frag 15 | } 16 | } 17 | } 18 | ... @skip(unless: $foo) { 19 | id 20 | } 21 | ... { 22 | id 23 | } 24 | } 25 | } 26 | 27 | mutation likeStory { 28 | like(story: 123) @defer { 29 | story { 30 | id 31 | } 32 | } 33 | } 34 | 35 | subscription StoryLikeSubscription($input: StoryLikeSubscribeInput) { 36 | storyLikeSubscribe(input: $input) { 37 | story { 38 | likers { 39 | count 40 | } 41 | likeSentence { 42 | text 43 | } 44 | } 45 | } 46 | } 47 | 48 | fragment frag on Friend { 49 | foo(size: $size, bar: $b, obj: {key: "value", block: """ 50 | 51 | block string uses \""" 52 | 53 | """}) 54 | } 55 | 56 | { 57 | unnamed(truthy: true, falsey: false, nullish: null), 58 | query 59 | } -------------------------------------------------------------------------------- /bin/bench.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "tinygql" 4 | require "benchmark/ips" 5 | 6 | source = File.read(File.expand_path("../test/kitchen-sink.graphql", __dir__)) 7 | 8 | files = Dir[File.join(File.expand_path("../benchmark", __dir__), "**/*")].select { |f| File.file? f } 9 | 10 | Benchmark.ips do |x| 11 | x.report "kitchen-sink" do 12 | TinyGQL.parse source 13 | end 14 | 15 | files.each do |file_name| 16 | data = File.read file_name 17 | name = File.basename(file_name, File.extname(file_name)) 18 | x.report name do 19 | TinyGQL.parse data 20 | end 21 | end 22 | end 23 | 24 | module Benchmark 25 | def self.allocs; yield Allocs; end 26 | end 27 | 28 | class Allocs 29 | def self.report name, &block 30 | allocs = nil 31 | 32 | 2.times do # 2 times to heat caches 33 | allocs = 10.times.map { 34 | x = GC.stat(:total_allocated_objects) 35 | yield 36 | GC.stat(:total_allocated_objects) - x 37 | }.inject(:+) / 10 38 | end 39 | 40 | puts name.rjust(20) + allocs.to_s.rjust(10) 41 | end 42 | end 43 | 44 | print "#" * 30 45 | print " ALLOCATIONS " 46 | puts "#" * 30 47 | 48 | Benchmark.allocs do |x| 49 | x.report "kitchen-sink" do 50 | TinyGQL.parse source 51 | end 52 | 53 | files.each do |file_name| 54 | data = File.read file_name 55 | name = File.basename(file_name, File.extname(file_name)) 56 | x.report name do 57 | TinyGQL.parse data 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /lib/tinygql/visitors.rb.erb: -------------------------------------------------------------------------------- 1 | module TinyGQL 2 | module Visitors 3 | module Visitor 4 | <% nodes.each do |node| %> 5 | def handle_<%= node.human_name %> obj 6 | <%- node.fields.find_all(&:visitable?).each do |field| -%> 7 | <%- if field.list? -%> 8 | obj.<%= field.name %>.each { |v| v.accept self }<% if field.nullable? %> if obj.<%= field.name %><% end %> 9 | <%- end -%> 10 | <%- if field.node? -%> 11 | obj.<%= field.name %>.accept(self)<% if field.nullable? %> if obj.<%= field.name %><% end %> 12 | <%- end -%> 13 | <%- end -%> 14 | end 15 | <% end %> 16 | end 17 | 18 | module Fold 19 | <% nodes.each do |node| %> 20 | def handle_<%= node.human_name %> obj, seed 21 | <%- node.fields.find_all(&:visitable?).each do |field| -%> 22 | <%- if field.list? -%> 23 | obj.<%= field.name %>.each { |v| seed = v.fold(self, seed) }<% if field.nullable? %> if obj.<%= field.name %><% end %> 24 | <%- end -%> 25 | <%- if field.node? -%> 26 | seed = obj.<%= field.name %>.fold(self, seed)<% if field.nullable? %> if obj.<%= field.name %><% end %> 27 | <%- end -%> 28 | <%- end -%> 29 | seed 30 | end 31 | <% end %> 32 | end 33 | 34 | module Null 35 | <% nodes.each do |node| %> 36 | def handle_<%= node.human_name %> obj 37 | end 38 | <% end %> 39 | end 40 | 41 | module NullFold 42 | <% nodes.each do |node| %> 43 | def handle_<%= node.human_name %> obj, _ 44 | end 45 | <% end %> 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /lib/tinygql/nodes.rb.erb: -------------------------------------------------------------------------------- 1 | module TinyGQL 2 | module Nodes 3 | class Node 4 | include Enumerable 5 | 6 | # `start` is the start position of this node in the source document 7 | attr_reader :start 8 | 9 | def initialize start 10 | @start = start 11 | end 12 | 13 | # Return the line of this node given `doc` 14 | def line doc 15 | doc[0, @start].count("\n") + 1 16 | end 17 | 18 | <%- nodes.each do |node| -%> 19 | def <%= node.human_name %>?; false; end 20 | <%- end -%> 21 | def each(&blk) 22 | yield self 23 | children.each { |v| v.each(&blk) } 24 | end 25 | end 26 | 27 | <%- nodes.each do |node| -%> 28 | class <%= node.name %> < <%= node.parent ? node.parent : "Node" %> 29 | <%- if node.fields.any? -%> 30 | attr_reader <%= node.fields.map { |v| ":" + v.name }.join(", ") %> 31 | 32 | def initialize <%= (["pos"] + node.fields.map(&:name)).join(", ") %> 33 | super(pos) 34 | <%- node.fields.each do |field| -%> 35 | @<%= field.name %> = <%= field.name %> 36 | <%- end -%> 37 | end 38 | <%- end -%> 39 | 40 | def accept viz 41 | viz.handle_<%= node.human_name %> self 42 | end 43 | 44 | def fold viz, seed 45 | viz.handle_<%= node.human_name %> self, seed 46 | end 47 | 48 | def <%= node.human_name %>?; true; end 49 | 50 | <%- if node.has_children? -%> 51 | def children 52 | <%= node.children %> 53 | end 54 | <%- end -%> 55 | end 56 | <%- end -%> 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /test/schema-kitchen-sink.graphql: -------------------------------------------------------------------------------- 1 | # Copyright 2019-present, GraphQL Foundation 2 | # 3 | # This source code is licensed under the MIT license found in the 4 | # LICENSE file in the root directory of this source tree. 5 | 6 | # (this line is padding to maintain test line numbers) 7 | 8 | schema { 9 | query: QueryType 10 | mutation: MutationType 11 | } 12 | 13 | type Foo implements Bar { 14 | one: Type 15 | two(argument: InputType!): Type 16 | three(argument: InputType, other: String): Int 17 | four(argument: String = "string"): String 18 | five(argument: [String] = ["string", "string"]): String 19 | six(argument: InputType = {key: "value"}): Type 20 | seven(argument: Int = null): Type 21 | } 22 | 23 | type AnnotatedObject @onObject(arg: "value") { 24 | annotatedField(arg: Type = "default" @onArg): Type @onField 25 | } 26 | 27 | interface Bar { 28 | one: Type 29 | four(argument: String = "string"): String 30 | } 31 | 32 | interface AnnotatedInterface @onInterface { 33 | annotatedField(arg: Type @onArg): Type @onField 34 | } 35 | 36 | union Feed = Story | Article | Advert 37 | 38 | union AnnotatedUnion @onUnion = A | B 39 | 40 | scalar CustomScalar 41 | 42 | scalar AnnotatedScalar @onScalar 43 | 44 | enum Site { 45 | DESKTOP 46 | MOBILE 47 | } 48 | 49 | enum AnnotatedEnum @onEnum { 50 | ANNOTATED_VALUE @onEnumValue 51 | OTHER_VALUE 52 | } 53 | 54 | input InputType { 55 | key: String! 56 | answer: Int = 42 57 | } 58 | 59 | input AnnotatedInput @onInputObjectType { 60 | annotatedField: Type @onField 61 | } 62 | 63 | extend type Foo { 64 | seven(argument: [String]): Type 65 | } 66 | 67 | # NOTE: out-of-spec test cases commented out until the spec is clarified; see 68 | # https://github.com/graphql/graphql-js/issues/650 . 69 | # extend type Foo @onType {} 70 | 71 | #type NoFields {} 72 | 73 | directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT 74 | 75 | directive @include(if: Boolean!) 76 | on FIELD 77 | | FRAGMENT_SPREAD 78 | | INLINE_FRAGMENT 79 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TinyGQL 2 | 3 | Very experimental GraphQL parser. It's mostly reusing the lexer from 4 | [GraphQL-Ruby](https://github.com/rmosolgo/graphql-ruby), but the parser is a 5 | hand-written recursive descent parser. 6 | 7 | I want to target this at server side applications, so the parser eliminates some nice stuff for humans (namely line / column information, and it throws away comments). 8 | 9 | Right now this code: 10 | 11 | 1. Doesn't know how to execute anything. It just gives you an AST 12 | 2. Isn't used anywhere (except in your heart, *but hopefully in production someday!) 13 | 14 | ## Usage 15 | 16 | You can get an AST like this: 17 | 18 | ```ruby 19 | ast = TinyGQL.parse "{ cool }" 20 | ``` 21 | 22 | The AST is iterable, so you can use the each method: 23 | 24 | ```ruby 25 | ast = TinyGQL.parse "{ cool }" 26 | ast.each do |node| 27 | p node.class 28 | end 29 | ``` 30 | 31 | Nodes have predicate methods, so if you want to find particular nodes just use a predicate: 32 | 33 | ```ruby 34 | ast = TinyGQL.parse "{ cool }" 35 | p ast.find_all(&:field?).map(&:name) # => ["cool"] 36 | ``` 37 | 38 | If you need a more advanced way to iterate nodes, you can use a visitor: 39 | 40 | ```ruby 41 | class Viz 42 | include TinyGQL::Visitors::Visitor 43 | 44 | def handle_field obj 45 | p obj.name # => cool 46 | super 47 | end 48 | end 49 | 50 | ast = TinyGQL.parse "{ cool }" 51 | ast.accept(Viz.new) 52 | ``` 53 | 54 | If you would like a functional way to collect data from the tree, use the `Fold` module: 55 | 56 | ```ruby 57 | module Fold 58 | extend TinyGQL::Visitors::Fold 59 | 60 | def self.handle_field obj, seed 61 | super(obj, seed + [obj.name]) 62 | end 63 | end 64 | 65 | ast = TinyGQL.parse "{ neat { cool } }" 66 | p ast.fold(Fold, []) # => ["neat", "cool"] 67 | ``` 68 | 69 | Nodes store their position in the source GraphQL document. 70 | If you'd like to extract the line number of the node, you'll need to keep a reference to the document and pass it to the `line` method on the node: 71 | 72 | ```ruby 73 | doc = <<-eod 74 | mutation { 75 | likeStory(sturyID: 12345) { 76 | story { 77 | likeCount 78 | } 79 | } 80 | } 81 | 82 | eod 83 | 84 | parser = TinyGQL::Parser.new doc 85 | ast = parser.parse 86 | 87 | ast.find_all(&:field?).each { |node| 88 | p node.name => node.line(doc) 89 | } 90 | ``` 91 | 92 | ## LICENSE: 93 | 94 | I've licensed this code as Apache 2.0, but the lexer is from [GraphQL-Ruby](https://github.com/rmosolgo/graphql-ruby/blob/772734dfcc7aa0513c867259912474ef0ba799c3/lib/graphql/language/lexer.rb) and is under the MIT license. 95 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "tldr/rake" 3 | require_relative "lib/tinygql/version" 4 | 5 | FILES = [ "lib/tinygql/nodes.rb", "lib/tinygql/visitors.rb" ] 6 | 7 | task :tldr => FILES 8 | 9 | task default: :tldr 10 | 11 | def extract_nodes source 12 | require "psych" 13 | require "erb" 14 | 15 | info = Psych.load_file source 16 | node = Struct.new(:name, :parent, :fields) do 17 | def has_children?; fields.length > 0; end 18 | 19 | def human_name 20 | name.gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2'). 21 | gsub(/([a-z\d])([A-Z])/,'\1_\2'). 22 | tr("-", "_"). 23 | downcase 24 | end 25 | 26 | def children 27 | buf = ["ary = []"] 28 | 29 | fields.each do |field| 30 | case field.type 31 | when "list" 32 | x = "ary.concat(#{field.name})" 33 | if field.nullable? 34 | x << " if #{field.name}" 35 | end 36 | buf << x 37 | when "node" 38 | x = "ary << #{field.name}" 39 | if field.nullable? 40 | x << " if #{field.name}" 41 | end 42 | buf << x 43 | end 44 | end 45 | 46 | buf << "ary" 47 | buf.join("; ") 48 | end 49 | end 50 | 51 | field = Struct.new :_name, :type do 52 | def name 53 | _name.sub(/\?$/, '') 54 | end 55 | 56 | def list?; type == "list"; end 57 | def node?; type == "node"; end 58 | def visitable?; list? || node?; end 59 | 60 | def nullable? 61 | _name.end_with?("?") 62 | end 63 | end 64 | info["nodes"].map { |n| 65 | node.new(n["name"], n["parent"] || "Node", (n["fields"] || []).map { |f| 66 | name = f 67 | type = "node" 68 | if Hash === f 69 | (name, type) = *f.to_a.first 70 | end 71 | field.new name, type 72 | }) 73 | } 74 | end 75 | 76 | file "lib/tinygql/nodes.rb" => "lib/tinygql/nodes.yml" do |t| 77 | nodes = extract_nodes t.source 78 | erb = ERB.new File.read("lib/tinygql/nodes.rb.erb"), trim_mode: "-" 79 | File.binwrite t.name, erb.result(binding) 80 | end 81 | 82 | file "lib/tinygql/visitors.rb" => "lib/tinygql/nodes.yml" do |t| 83 | nodes = extract_nodes t.source 84 | erb = ERB.new File.read("lib/tinygql/visitors.rb.erb"), trim_mode: "-" 85 | File.binwrite t.name, erb.result(binding) 86 | end 87 | 88 | require "rake/clean" 89 | CLOBBER.append(FILES) 90 | 91 | task :build => FILES 92 | 93 | namespace :gem do 94 | task :push => [:test, :build] do 95 | # check for a clean worktree 96 | sh "git update-index --really-refresh" 97 | sh "git diff-index --quiet HEAD" 98 | 99 | # tag it 100 | sh "git tag -m'tagging release' v#{TinyGQL::VERSION}" 101 | 102 | # push it 103 | ENV['GEM_HOST_OTP_CODE'] = `ykman oath accounts code -s rubygems.org`.chomp 104 | sh "gem push pkg/tinygql-#{TinyGQL::VERSION}.gem" 105 | sh "git push --tags" 106 | end 107 | 108 | task :test => :tldr 109 | 110 | task :install => :build do 111 | require 'tmpdir' 112 | 113 | Dir.mktmpdir do |d| 114 | ENV["GEM_HOME"] = d 115 | ENV["GEM_PATH"] = ([d] + Gem.path).join(":") 116 | end 117 | sh "gem install -l pkg/tinygql-#{TinyGQL::VERSION}.gem" 118 | end 119 | 120 | task :test => :install 121 | end 122 | 123 | desc "Run benchmarks" 124 | task :benchmark do 125 | ruby "-I lib bin/bench.rb" 126 | end 127 | 128 | desc "Profile with benchmarks" 129 | task :profile do 130 | ruby "-I lib bin/bench.rb" 131 | end 132 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to make participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies within all project spaces, and it also applies when 49 | an individual is representing the project or its community in public spaces. 50 | Examples of representing a project or community include using an official 51 | project e-mail address, posting via an official social media account, or acting 52 | as an appointed representative at an online or offline event. Representation of 53 | a project may be further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at aaron.patterson at gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | 78 | -------------------------------------------------------------------------------- /lib/tinygql/nodes.yml: -------------------------------------------------------------------------------- 1 | --- 2 | nodes: 3 | - name: Document 4 | fields: 5 | - definitions: list 6 | 7 | - name: ExecutableDefinition 8 | 9 | - name: OperationDefinition 10 | parent: ExecutableDefinition 11 | fields: 12 | - type: literal 13 | - name?: literal 14 | - variable_definitions?: list 15 | - directives?: list 16 | - selection_set: list 17 | 18 | - name: Variable 19 | fields: 20 | - name: literal 21 | 22 | - name: NamedType 23 | fields: 24 | - name: literal 25 | 26 | - name: NotNullType 27 | fields: 28 | - type 29 | 30 | - name: ListType 31 | fields: 32 | - type 33 | 34 | - name: VariableDefinition 35 | fields: 36 | - variable 37 | - type 38 | - default_value? 39 | 40 | - name: Value 41 | fields: 42 | - value: literal 43 | 44 | - name: Argument 45 | fields: 46 | - name: literal 47 | - value 48 | 49 | - name: Field 50 | fields: 51 | - aliaz?: literal 52 | - name: literal 53 | - arguments?: list 54 | - directives?: list 55 | - selection_set?: list 56 | 57 | - name: ObjectField 58 | fields: 59 | - name: literal 60 | - value 61 | 62 | - name: IntValue 63 | parent: Value 64 | 65 | - name: FloatValue 66 | parent: Value 67 | 68 | - name: StringValue 69 | parent: Value 70 | 71 | - name: BooleanValue 72 | parent: Value 73 | 74 | - name: "NullValue" 75 | parent: Value 76 | 77 | - name: EnumValue 78 | parent: Value 79 | 80 | - name: ListValue 81 | parent: Value 82 | 83 | - name: ObjectValue 84 | fields: 85 | - values: list 86 | 87 | - name: Directive 88 | fields: 89 | - name: literal 90 | - arguments?: list 91 | 92 | - name: TypeCondition 93 | fields: 94 | - named_type 95 | 96 | - name: InlineFragment 97 | fields: 98 | - type_condition? 99 | - directives?: list 100 | - selection_set: list 101 | 102 | - name: FragmentSpread 103 | fields: 104 | - fragment_name: literal 105 | - directives?: list 106 | 107 | - name: FragmentDefinition 108 | parent: ExecutableDefinition 109 | fields: 110 | - fragment_name: literal 111 | - type_condition 112 | - directives?: list 113 | - selection_set: list 114 | 115 | - name: RootOperationTypeDefinition 116 | fields: 117 | - operation_type: literal 118 | - named_type 119 | 120 | - name: SchemaDefinition 121 | fields: 122 | - description? 123 | - directives?: list 124 | - root_operation_definitions: list 125 | 126 | - name: FieldDefinition 127 | fields: 128 | - description? 129 | - name: literal 130 | - arguments_definition?: list 131 | - type 132 | - directives?: list 133 | 134 | - name: InputValueDefinition 135 | fields: 136 | - description? 137 | - name: literal 138 | - type 139 | - default_value? 140 | - directives?: list 141 | 142 | - name: ObjectTypeDefinition 143 | fields: 144 | - description? 145 | - name: literal 146 | - implements_interfaces?: list 147 | - directives?: list 148 | - fields_definition?: list 149 | 150 | - name: InterfaceTypeDefinition 151 | fields: 152 | - description? 153 | - name: literal 154 | - directives?: list 155 | - fields_definition?: list 156 | 157 | - name: UnionTypeDefinition 158 | fields: 159 | - description? 160 | - name: literal 161 | - directives?: list 162 | - union_member_types?: list 163 | 164 | - name: ScalarTypeDefinition 165 | fields: 166 | - description? 167 | - name: literal 168 | - directives?: list 169 | 170 | - name: EnumValueDefinition 171 | fields: 172 | - description? 173 | - enum_value 174 | - directives?: list 175 | 176 | - name: EnumTypeDefinition 177 | fields: 178 | - description? 179 | - name: literal 180 | - directives?: list 181 | - enum_value_definition?: list 182 | 183 | - name: InputObjectTypeDefinition 184 | fields: 185 | - description? 186 | - name: literal 187 | - directives?: list 188 | - input_fields_definition?: list 189 | 190 | - name: ObjectTypeExtension 191 | fields: 192 | - name: literal 193 | - implements_interfaces?: list 194 | - directives?: list 195 | - fields_definition?: list 196 | 197 | - name: ExecutableDirectiveLocation 198 | fields: 199 | - name: literal 200 | 201 | - name: TypeSystemDirectiveLocation 202 | fields: 203 | - name: literal 204 | 205 | - name: DirectiveDefinition 206 | fields: 207 | - description? 208 | - name: literal 209 | - arguments_definition?: list 210 | - directive_locations: list 211 | 212 | - name: ScalarTypeExtension 213 | fields: 214 | - name: literal 215 | - directives?: list 216 | 217 | - name: InterfaceTypeExtension 218 | fields: 219 | - name: literal 220 | - implements_interfaces?: list 221 | - directives?: list 222 | - fields_definition?: list 223 | 224 | - name: UnionTypeExtension 225 | fields: 226 | - name: literal 227 | - directives?: list 228 | - union_member_types?: list 229 | 230 | - name: EnumTypeExtension 231 | fields: 232 | - name: literal 233 | - directives?: list 234 | - enum_value_definition?: list 235 | 236 | - name: InputObjectTypeExtension 237 | fields: 238 | - name: literal 239 | - directives?: list 240 | - input_fields_definition?: list 241 | -------------------------------------------------------------------------------- /test/lexer_test.rb: -------------------------------------------------------------------------------- 1 | require "helper" 2 | 3 | module TinyGQL 4 | class LexerTest < Test 5 | PUNC_LUT = {"!"=>[:BANG, "!"], 6 | "$"=>[:VAR_SIGN, "$"], 7 | "("=>[:LPAREN, "("], 8 | ")"=>[:RPAREN, ")"], 9 | "..."=>[:ELLIPSIS, "..."], 10 | ":"=>[:COLON, ":"], 11 | "="=>[:EQUALS, "="], 12 | "@"=>[:DIR_SIGN, "@"], 13 | "["=>[:LBRACKET, "["], 14 | "]"=>[:RBRACKET, "]"], 15 | "{"=>[:LCURLY, "{"], 16 | "|"=>[:PIPE, "|"], 17 | "}"=>[:RCURLY, "}"]} 18 | 19 | def test_punc 20 | %w{ ! $ ( ) ... : = @ [ ] { | } }.each do |punc| 21 | lexer = Lexer.new punc 22 | token = lexer.next_token 23 | expected = PUNC_LUT[punc] 24 | assert_equal(expected, token) 25 | end 26 | end 27 | 28 | def test_regular_string 29 | str = "hello\n# foo\n\"world\"# lol \nlol" 30 | lexer = Lexer.new str 31 | assert_equal [:IDENTIFIER, "hello"], lexer.next_token 32 | assert_equal [:STRING, "world"], lexer.next_token 33 | assert_equal [:IDENTIFIER, "lol"], lexer.next_token 34 | end 35 | 36 | def test_multiline_comment 37 | str = "hello\n# foo\n# lol \nlol" 38 | lexer = Lexer.new str 39 | assert_equal [:IDENTIFIER, "hello"], lexer.next_token 40 | assert_equal [:IDENTIFIER, "lol"], lexer.next_token 41 | end 42 | 43 | def test_int 44 | str = "1" 45 | lexer = Lexer.new str 46 | assert_equal [:INT, "1"], lexer.next_token 47 | end 48 | 49 | def test_float 50 | str = "1.2" 51 | lexer = Lexer.new str 52 | assert_equal [:FLOAT, "1.2"], lexer.next_token 53 | end 54 | 55 | def test_block_string 56 | doc = <<-eos 57 | """ 58 | 59 | block string uses \\""" 60 | 61 | """ 62 | eos 63 | lexer = Lexer.new doc 64 | assert_equal :STRING, lexer.next_token.first 65 | end 66 | 67 | def test_tokenize 68 | lexer = Lexer.new "on" 69 | token = lexer.next_token 70 | assert_equal [:ON, "on"], token 71 | end 72 | 73 | def test_multi_tok 74 | doc = <<-eod 75 | mutation { 76 | likeStory(storyID: 12345) { 77 | story { 78 | likeCount 79 | } 80 | } 81 | } 82 | eod 83 | lexer = Lexer.new doc 84 | toks = [] 85 | while tok = lexer.next_token 86 | toks << tok 87 | end 88 | assert_equal [[:MUTATION, "mutation"], 89 | [:LCURLY, "{"], 90 | [:IDENTIFIER, "likeStory"], 91 | [:LPAREN, "("], 92 | [:IDENTIFIER, "storyID"], 93 | [:COLON, ":"], 94 | [:INT, "12345"], 95 | [:RPAREN, ")"], 96 | [:LCURLY, "{"], 97 | [:IDENTIFIER, "story"], 98 | [:LCURLY, "{"], 99 | [:IDENTIFIER, "likeCount"], 100 | [:RCURLY, "}"], 101 | [:RCURLY, "}"], 102 | [:RCURLY, "}"]], toks 103 | end 104 | 105 | def test_lex_4 106 | words = ["true", "null", "enum", "type"] 107 | doc = words.join(" ") 108 | 109 | lexer = Lexer.new doc 110 | toks = [] 111 | while tok = lexer.next_token 112 | toks << tok 113 | end 114 | 115 | assert_equal words.map { |x| [x.upcase.to_sym, x] }, toks 116 | end 117 | 118 | def test_lex_5 119 | words = ["input", "false", "query", "union"] 120 | doc = words.join(" ") 121 | 122 | lexer = Lexer.new doc 123 | toks = [] 124 | while tok = lexer.next_token 125 | toks << tok 126 | end 127 | 128 | assert_equal words.map { |x| [x.upcase.to_sym, x] }, toks 129 | end 130 | 131 | def test_lex_6 132 | words = ["extend", "scalar", "schema"] 133 | doc = words.join(" ") 134 | 135 | lexer = Lexer.new doc 136 | toks = [] 137 | while tok = lexer.next_token 138 | toks << tok 139 | end 140 | 141 | assert_equal words.map { |x| [x.upcase.to_sym, x] }, toks 142 | end 143 | 144 | def test_lex_8 145 | words = ["mutation", "fragment"] 146 | doc = words.join(" ") 147 | 148 | lexer = Lexer.new doc 149 | toks = [] 150 | while tok = lexer.next_token 151 | toks << tok 152 | end 153 | 154 | assert_equal words.map { |x| [x.upcase.to_sym, x] }, toks 155 | end 156 | 157 | def test_lex_9 158 | words = ["interface", "directive"] 159 | doc = words.join(" ") 160 | 161 | lexer = Lexer.new doc 162 | toks = [] 163 | while tok = lexer.next_token 164 | toks << tok 165 | end 166 | 167 | assert_equal words.map { |x| [x.upcase.to_sym, x] }, toks 168 | end 169 | 170 | def test_kw_lex 171 | words = ["on", "fragment", "true", "false", "null", "query", "mutation", "subscription", "schema", "scalar", "type", "extend", "implements", "interface", "union", "enum", "input", "directive", "repeatable"] 172 | doc = words.join(" ") 173 | 174 | lexer = Lexer.new doc 175 | toks = [] 176 | while tok = lexer.next_token 177 | toks << tok 178 | end 179 | 180 | assert_equal words.map { |x| [x.upcase.to_sym, x] }, toks 181 | end 182 | 183 | def test_looks_like_kw 184 | words = ["fragment", "fragments"] 185 | doc = words.join(" ") 186 | 187 | lexer = Lexer.new doc 188 | toks = [] 189 | while tok = lexer.advance 190 | toks << tok 191 | end 192 | 193 | assert_equal [:FRAGMENT, :IDENTIFIER], toks 194 | end 195 | 196 | def test_num_with_dots 197 | lexer = Lexer.new "1...2" 198 | toks = [] 199 | while tok = lexer.advance 200 | toks << tok 201 | end 202 | 203 | assert_equal [:INT, :ELLIPSIS, :INT], toks 204 | end 205 | end 206 | end 207 | -------------------------------------------------------------------------------- /test/parser_test.rb: -------------------------------------------------------------------------------- 1 | require "helper" 2 | require "tinygql" 3 | 4 | module TinyGQL 5 | class ParserTest < Test 6 | def test_homogeneous_ast 7 | %w{ kitchen-sink.graphql schema-extensions.graphql schema-kitchen-sink.graphql }.each do |f| 8 | ast = Parser.parse File.read(File.join(__dir__, f)) 9 | assert ast.all? { |x| x.is_a?(TinyGQL::Nodes::Node) } 10 | end 11 | end 12 | 13 | def test_multi_tok 14 | doc = <<-eod 15 | mutation aaron($neat: Int = 123) @foo(lol: { lon: 456 }) { 16 | } 17 | eod 18 | parser = Parser.new doc 19 | ast = parser.parse 20 | assert_equal "mutation", ast.children.first.type 21 | mutation = ast.children.first 22 | assert_equal "aaron", mutation.name 23 | assert_equal 1, mutation.variable_definitions.length 24 | 25 | var_def = mutation.variable_definitions.first 26 | variable = var_def.variable 27 | assert_equal "neat", variable.name 28 | assert_equal "Int", var_def.type.name 29 | assert_equal "123", var_def.default_value.value 30 | 31 | assert_equal(["123", "456"], ast.find_all { |node| node.int_value? }.map(&:value)) 32 | end 33 | 34 | def test_has_things 35 | doc = <<-eod 36 | mutation { 37 | likeStory(storyID: 12345) { 38 | story { 39 | likeCount 40 | } 41 | } 42 | } 43 | eod 44 | parser = Parser.new doc 45 | ast = parser.parse 46 | assert_equal ["likeStory", "story", "likeCount"], ast.find_all(&:field?).map(&:name) 47 | end 48 | 49 | def test_operation_definition_is_executable 50 | doc = <<-eod 51 | mutation { 52 | likeStory(storyID: 12345) { 53 | story { 54 | likeCount 55 | } 56 | } 57 | } 58 | eod 59 | ast = TinyGQL.parse doc 60 | od = ast.find_all(&:operation_definition?) 61 | refute_predicate od, :empty? 62 | assert od.all?(&:executable_definition?) 63 | end 64 | 65 | def test_fragments_are_executable 66 | doc = <<-eod 67 | query withFragments { 68 | user(id: 4) { 69 | friends(first: 10) { 70 | ...friendFields 71 | } 72 | mutualFriends(first: 10) { 73 | ...friendFields 74 | } 75 | } 76 | } 77 | 78 | fragment friendFields on User { 79 | id 80 | name 81 | profilePic(size: 50) 82 | } 83 | eod 84 | ast = TinyGQL.parse doc 85 | od = ast.find_all(&:fragment_definition?) 86 | refute_predicate od, :empty? 87 | assert od.all?(&:executable_definition?), "fragments should be executable" 88 | end 89 | 90 | def test_has_position_and_line 91 | doc = <<-eod 92 | mutation { 93 | likeStory(sturyID: 12345) { 94 | story { 95 | likeCount 96 | } 97 | } 98 | } 99 | eod 100 | parser = Parser.new doc 101 | ast = parser.parse 102 | expected = ["likeStory", "story", "likeCount"].map { |str| doc.index(str) } 103 | assert_equal expected, ast.find_all(&:field?).map(&:start) 104 | assert_equal [2, 3, 4], ast.find_all(&:field?).map { |n| n.line(doc) } 105 | end 106 | 107 | def test_field_alias 108 | doc = <<-eod 109 | mutation { 110 | a: likeStory(storyID: 12345) { 111 | b: story { 112 | c: likeCount 113 | } 114 | } 115 | } 116 | eod 117 | parser = Parser.new doc 118 | ast = parser.parse 119 | assert_equal ["likeStory", "story", "likeCount"], ast.find_all(&:field?).map(&:name) 120 | assert_equal ["a", "b", "c"], ast.find_all(&:field?).map(&:aliaz) 121 | end 122 | 123 | def test_shorthand 124 | doc = <<-eod 125 | { 126 | field 127 | } 128 | eod 129 | parser = Parser.new doc 130 | ast = parser.parse 131 | assert_predicate ast.children.first, :operation_definition? 132 | assert_equal ["field"], ast.find_all(&:field?).map(&:name) 133 | end 134 | 135 | def test_kitchen_sink 136 | parser = Parser.new File.read(File.join(__dir__, "kitchen-sink.graphql")) 137 | parser.parse 138 | end 139 | 140 | def test_schema_kitchen_sink 141 | parser = Parser.new File.read(File.join(__dir__, "schema-kitchen-sink.graphql")) 142 | parser.parse 143 | end 144 | 145 | def test_visitor 146 | doc = <<-eod 147 | mutation { 148 | a: likeStory(storyID: 12345) { 149 | b: story { 150 | c: likeCount 151 | } 152 | } 153 | } 154 | eod 155 | viz = Class.new do 156 | include TinyGQL::Visitors::Visitor 157 | 158 | attr_reader :nodes 159 | 160 | def initialize 161 | @nodes = [] 162 | end 163 | 164 | def handle_field obj 165 | nodes << obj if obj.name == "likeStory" 166 | super 167 | end 168 | end 169 | parser = Parser.new doc 170 | ast = parser.parse 171 | obj = viz.new 172 | ast.accept(obj) 173 | assert_equal 1, obj.nodes.length 174 | node = obj.nodes.first 175 | assert_equal "a", node.aliaz 176 | assert_equal 1, node.arguments.length 177 | end 178 | 179 | def test_fold 180 | doc = <<-eod 181 | mutation { 182 | a: likeStory(storyID: 12345) { 183 | b: story { 184 | c: likeCount 185 | } 186 | } 187 | } 188 | eod 189 | viz = Module.new do 190 | extend TinyGQL::Visitors::Fold 191 | 192 | def self.handle_field obj, nodes 193 | if obj.name == "likeStory" 194 | super(obj, nodes + [obj]) 195 | else 196 | super 197 | end 198 | end 199 | end 200 | parser = Parser.new doc 201 | ast = parser.parse 202 | fields = ast.fold(viz, []) 203 | assert_equal 1, fields.length 204 | node = fields.first 205 | assert_equal "a", node.aliaz 206 | assert_equal 1, node.arguments.length 207 | end 208 | 209 | def test_null 210 | doc = <<-eod 211 | mutation { 212 | a: likeStory(storyID: 12345) { 213 | b: story { 214 | c: likeCount 215 | } 216 | } 217 | } 218 | eod 219 | viz = Module.new do 220 | extend TinyGQL::Visitors::Null 221 | 222 | def self.handle_field obj 223 | true 224 | end 225 | end 226 | parser = Parser.new doc 227 | ast = parser.parse 228 | ast.each { |node| 229 | assert_equal(node.field?, !!node.accept(viz)) 230 | } 231 | end 232 | 233 | def test_null_fold 234 | doc = <<-eod 235 | mutation { 236 | a: likeStory(storyID: 12345) { 237 | b: story { 238 | c: likeCount 239 | } 240 | } 241 | } 242 | eod 243 | viz = Module.new do 244 | extend TinyGQL::Visitors::NullFold 245 | 246 | def self.handle_field obj, x 247 | x 248 | end 249 | end 250 | parser = Parser.new doc 251 | ast = parser.parse 252 | ast.each { |node| 253 | assert_equal(node.field?, !!node.fold(viz, true)) 254 | } 255 | end 256 | 257 | def test_multiple_implements 258 | doc = <<-eod 259 | type SomeType implements a, b, c { 260 | } 261 | eod 262 | parser = Parser.new doc 263 | ast = parser.parse 264 | node = ast.find(&:object_type_definition?).first 265 | assert_equal ["a", "b", "c"], node.implements_interfaces.map(&:name) 266 | end 267 | 268 | def test_multiple_implements_no_end 269 | doc = <<-eod 270 | type SomeType implements a, b, c 271 | eod 272 | parser = Parser.new doc 273 | ast = parser.parse 274 | node = ast.find(&:object_type_definition?).first 275 | assert_equal ["a", "b", "c"], node.implements_interfaces.map(&:name) 276 | end 277 | 278 | def test_schemas_have_descriptions 279 | doc = <<-eod 280 | "foo bar" 281 | schema { 282 | query: QueryType 283 | mutation: MutationType 284 | } 285 | eod 286 | ast = TinyGQL::Parser.parse doc 287 | node = ast.find(&:schema_definition?) 288 | assert node 289 | assert_equal "foo bar", node.description.value 290 | end 291 | 292 | def test_directives_have_descriptions 293 | doc = <<-eod 294 | """neat!""" 295 | directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT 296 | eod 297 | ast = TinyGQL::Parser.parse doc 298 | node = ast.find(&:directive_definition?) 299 | assert node 300 | assert_equal "neat!", node.description.value 301 | assert_equal ["FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT"], node.directive_locations.map(&:name) 302 | end 303 | 304 | def test_scalar_schema_extensions 305 | ast = Parser.parse File.read(File.join(__dir__, "schema-extensions.graphql")) 306 | node = ast.find { |x| x.scalar_type_extension? && x.name == "PositiveInt" } 307 | assert node 308 | assert_equal 2, node.directives.length 309 | end 310 | 311 | def test_scalar_schema_extensions_no_directives 312 | ast = Parser.parse File.read(File.join(__dir__, "schema-extensions.graphql")) 313 | node = ast.find { |x| x.scalar_type_extension? && x.name == "Aaron" } 314 | assert node 315 | assert_nil node.directives 316 | end 317 | 318 | def test_interface_extension 319 | ast = Parser.parse File.read(File.join(__dir__, "schema-extensions.graphql")) 320 | node = ast.find { |x| x.interface_type_extension? && x.name == "NamedEntity" } 321 | assert node 322 | assert_nil node.directives 323 | assert node.fields_definition 324 | end 325 | 326 | def test_union_extension 327 | ast = Parser.parse File.read(File.join(__dir__, "schema-extensions.graphql")) 328 | node = ast.find { |x| x.union_type_extension? && x.name == "Cool" } 329 | assert node 330 | assert_equal 1, node.directives.length 331 | assert_equal "foo", node.directives.first.name 332 | end 333 | 334 | def test_enum_extension 335 | ast = Parser.parse File.read(File.join(__dir__, "schema-extensions.graphql")) 336 | assert ast.find { |x| x.enum_type_extension? && x.name == "Direction" } 337 | assert ast.find { |x| x.enum_type_extension? && x.name == "AnnotatedEnum" } 338 | assert ast.find { |x| x.enum_type_extension? && x.name == "Neat" } 339 | end 340 | 341 | def test_input_extension 342 | ast = Parser.parse File.read(File.join(__dir__, "schema-extensions.graphql")) 343 | assert ast.find { |x| x.input_object_type_extension? && x.name == "InputType" } 344 | assert ast.find { |x| x.input_object_type_extension? && x.name == "AnnotatedInput" } 345 | assert ast.find { |x| x.input_object_type_extension? && x.name == "NeatInput" } 346 | end 347 | end 348 | end 349 | -------------------------------------------------------------------------------- /lib/tinygql/lexer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "strscan" 4 | 5 | module TinyGQL 6 | class Lexer 7 | IDENTIFIER = /[_A-Za-z][_0-9A-Za-z]*\b/ 8 | IGNORE = %r{ 9 | (?: 10 | [, \c\r\n\t]+ | 11 | \#.*$ 12 | )* 13 | }x 14 | INT = /[-]?(?:[0]|[1-9][0-9]*)/ 15 | FLOAT_DECIMAL = /[.][0-9]+/ 16 | FLOAT_EXP = /[eE][+-]?[0-9]+/ 17 | NUMERIC = /#{INT}(#{FLOAT_DECIMAL}#{FLOAT_EXP}|#{FLOAT_DECIMAL}|#{FLOAT_EXP})?/ 18 | 19 | KEYWORDS = [ 20 | "on", 21 | "fragment", 22 | "true", 23 | "false", 24 | "null", 25 | "query", 26 | "mutation", 27 | "subscription", 28 | "schema", 29 | "scalar", 30 | "type", 31 | "extend", 32 | "implements", 33 | "interface", 34 | "union", 35 | "enum", 36 | "input", 37 | "directive", 38 | "repeatable" 39 | ].freeze 40 | 41 | KW_RE = /#{Regexp.union(KEYWORDS.sort)}\b/ 42 | 43 | KW_FP3 = KEYWORDS.filter { |w| 44 | w.length >= 4 45 | }. map { |w| 46 | [ 47 | w.getbyte(1) * 169 + w.getbyte(2) * 13 + w.getbyte(3), 48 | w.upcase.to_sym 49 | ] 50 | }.to_h.freeze 51 | 52 | module Literals 53 | LCURLY = '{' 54 | RCURLY = '}' 55 | LPAREN = '(' 56 | RPAREN = ')' 57 | LBRACKET = '[' 58 | RBRACKET = ']' 59 | COLON = ':' 60 | VAR_SIGN = '$' 61 | DIR_SIGN = '@' 62 | EQUALS = '=' 63 | BANG = '!' 64 | PIPE = '|' 65 | AMP = '&' 66 | end 67 | 68 | ELLIPSIS = '...' 69 | 70 | include Literals 71 | 72 | QUOTE = '"' 73 | UNICODE_DIGIT = /[0-9A-Za-z]/ 74 | FOUR_DIGIT_UNICODE = /#{UNICODE_DIGIT}{4}/ 75 | N_DIGIT_UNICODE = %r{#{LCURLY}#{UNICODE_DIGIT}{4,}#{RCURLY}}x 76 | UNICODE_ESCAPE = %r{\\u(?:#{FOUR_DIGIT_UNICODE}|#{N_DIGIT_UNICODE})} 77 | # # https://graphql.github.io/graphql-spec/June2018/#sec-String-Value 78 | STRING_ESCAPE = %r{[\\][\\/bfnrt]} 79 | BLOCK_QUOTE = '"""' 80 | ESCAPED_QUOTE = /\\"/; 81 | STRING_CHAR = /#{ESCAPED_QUOTE}|[^"\\]|#{UNICODE_ESCAPE}|#{STRING_ESCAPE}/ 82 | 83 | PUNCT_LUT = Literals.constants.each_with_object([]) { |n, o| 84 | o[Literals.const_get(n).ord] = n 85 | } 86 | 87 | LEAD_BYTES = Array.new(255) { 0 } 88 | 89 | module LeadBytes 90 | INT = 1 << 0 91 | KW = 1 << 1 92 | ELLIPSIS = 1 << 2 93 | STRING = 1 << 3 94 | PUNCT = 1 << 4 95 | IDENT = 1 << 5 96 | end 97 | 98 | 10.times { |i| LEAD_BYTES[i.to_s.ord] |= LeadBytes::INT } 99 | 100 | ("A".."Z").each { |chr| LEAD_BYTES[chr.ord] |= LeadBytes::IDENT } 101 | ("a".."z").each { |chr| LEAD_BYTES[chr.ord] |= LeadBytes::IDENT } 102 | LEAD_BYTES['_'.ord] |= LeadBytes::IDENT 103 | 104 | KEYWORDS.each { |kw| LEAD_BYTES[kw.getbyte(0)] |= LeadBytes::KW } 105 | 106 | LEAD_BYTES['.'.ord] |= LeadBytes::ELLIPSIS 107 | 108 | LEAD_BYTES['"'.ord] |= LeadBytes::STRING 109 | 110 | Literals.constants.each_with_object([]) { |n, o| 111 | LEAD_BYTES[Literals.const_get(n).ord] |= LeadBytes::PUNCT 112 | } 113 | 114 | QUOTED_STRING = %r{#{QUOTE} ((?:#{STRING_CHAR})*) #{QUOTE}}x 115 | BLOCK_STRING = %r{ 116 | #{BLOCK_QUOTE} 117 | ((?: [^"\\] | # Any characters that aren't a quote or slash 118 | (?= 4 then 164 | fp = @string.getbyte(@start+1) * 169 + 165 | @string.getbyte(@start+2) * 13 + 166 | @string.getbyte(@start+3) 167 | tk = KW_FP3[fp] 168 | if tk then 169 | @scan.pos = @start 170 | return tk if @scan.skip(KW_RE) == @len 171 | @scan.pos = @start + @len 172 | end 173 | elsif @len == 2 then 174 | return :ON if lead_byte == 111 && @string.getbyte(@start+1) == 110 175 | end 176 | end 177 | :IDENTIFIER 178 | 179 | elsif lead_code == LeadBytes::INT 180 | @len = @scan.skip(NUMERIC) 181 | @scan[1] ? :FLOAT : :INT 182 | 183 | elsif lead_code == LeadBytes::ELLIPSIS 184 | 2.times do |i| 185 | raise unless @string.getbyte(@start + i + 1) == 46 186 | end 187 | @scan.pos += 3 188 | :ELLIPSIS 189 | 190 | elsif lead_code == LeadBytes::STRING 191 | @len = @scan.skip(BLOCK_STRING) || @scan.skip(QUOTED_STRING) 192 | raise unless @len 193 | :STRING 194 | 195 | else 196 | @scan.pos += 1 197 | :UNKNOWN_CHAR 198 | end 199 | end 200 | 201 | def token_value 202 | @string.byteslice(@start, @len) 203 | end 204 | 205 | def string_value 206 | str = token_value 207 | block = str.start_with?('"""') 208 | str.gsub!(/\A"*|"*\z/, '') 209 | 210 | if block 211 | emit_block str 212 | else 213 | emit_string str 214 | end 215 | end 216 | 217 | def next_token 218 | return unless tok = advance 219 | val = case tok 220 | when :STRING then string_value 221 | when :ELLIPSIS then 222 | @string.byteslice(@scan.pos - 3, 3) 223 | when *Literals.constants 224 | @string.byteslice(@scan.pos - 1, 1) 225 | else 226 | token_value 227 | end 228 | 229 | [tok, val] 230 | end 231 | 232 | # Replace any escaped unicode or whitespace with the _actual_ characters 233 | # To avoid allocating more strings, this modifies the string passed into it 234 | def replace_escaped_characters_in_place(raw_string) 235 | raw_string.gsub!(ESCAPES, ESCAPES_REPLACE) 236 | raw_string.gsub!(UTF_8) do |_matched_str| 237 | codepoint_1 = ($1 || $2).to_i(16) 238 | codepoint_2 = $3 239 | 240 | if codepoint_2 241 | codepoint_2 = codepoint_2.to_i(16) 242 | if (codepoint_1 >= 0xD800 && codepoint_1 <= 0xDBFF) && # leading surrogate 243 | (codepoint_2 >= 0xDC00 && codepoint_2 <= 0xDFFF) # trailing surrogate 244 | # A surrogate pair 245 | combined = ((codepoint_1 - 0xD800) * 0x400) + (codepoint_2 - 0xDC00) + 0x10000 246 | [combined].pack('U'.freeze) 247 | else 248 | # Two separate code points 249 | [codepoint_1].pack('U'.freeze) + [codepoint_2].pack('U'.freeze) 250 | end 251 | else 252 | [codepoint_1].pack('U'.freeze) 253 | end 254 | end 255 | nil 256 | end 257 | 258 | ESCAPES = /\\["\\\/bfnrt]/ 259 | ESCAPES_REPLACE = { 260 | '\\"' => '"', 261 | "\\\\" => "\\", 262 | "\\/" => '/', 263 | "\\b" => "\b", 264 | "\\f" => "\f", 265 | "\\n" => "\n", 266 | "\\r" => "\r", 267 | "\\t" => "\t", 268 | } 269 | UTF_8 = /\\u(?:([\dAa-f]{4})|\{([\da-f]{4,})\})(?:\\u([\dAa-f]{4}))?/i 270 | VALID_STRING = /\A(?:[^\\]|#{ESCAPES}|#{UTF_8})*\z/o 271 | 272 | def emit_block(value) 273 | value = trim_whitespace(value) 274 | emit_string(value) 275 | end 276 | 277 | def emit_string(value) 278 | if !value.valid_encoding? || !value.match?(VALID_STRING) 279 | emit(:BAD_UNICODE_ESCAPE, value) 280 | else 281 | replace_escaped_characters_in_place(value) 282 | 283 | if !value.valid_encoding? 284 | emit(:BAD_UNICODE_ESCAPE, value) 285 | else 286 | value 287 | end 288 | end 289 | end 290 | 291 | def trim_whitespace(str) 292 | # Early return for the most common cases: 293 | if str == "" 294 | return "".dup 295 | elsif !(has_newline = str.include?("\n")) && !(str.start_with?(" ")) 296 | return str 297 | end 298 | 299 | lines = has_newline ? str.split("\n") : [str] 300 | common_indent = nil 301 | 302 | # find the common whitespace 303 | lines.each_with_index do |line, idx| 304 | if idx == 0 305 | next 306 | end 307 | line_length = line.size 308 | line_indent = if line.match?(/\A [^ ]/) 309 | 2 310 | elsif line.match?(/\A [^ ]/) 311 | 4 312 | elsif line.match?(/\A[^ ]/) 313 | 0 314 | else 315 | line[/\A */].size 316 | end 317 | if line_indent < line_length && (common_indent.nil? || line_indent < common_indent) 318 | common_indent = line_indent 319 | end 320 | end 321 | 322 | # Remove the common whitespace 323 | if common_indent && common_indent > 0 324 | lines.each_with_index do |line, idx| 325 | if idx == 0 326 | next 327 | else 328 | line.slice!(0, common_indent) 329 | end 330 | end 331 | end 332 | 333 | # Remove leading & trailing blank lines 334 | while lines.size > 0 && lines[0].empty? 335 | lines.shift 336 | end 337 | while lines.size > 0 && lines[-1].empty? 338 | lines.pop 339 | end 340 | 341 | # Rebuild the string 342 | lines.size > 1 ? lines.join("\n") : (lines.first || "".dup) 343 | end 344 | end 345 | end 346 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2023 Aaron Patterson 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /lib/tinygql/parser.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "tinygql/lexer" 4 | require "tinygql/nodes" 5 | 6 | module TinyGQL 7 | class Parser 8 | class UnexpectedToken < StandardError; end 9 | 10 | def self.parse doc 11 | new(doc).parse 12 | end 13 | 14 | def initialize doc 15 | @lexer = Lexer.new doc 16 | accept_token 17 | end 18 | 19 | def parse 20 | document 21 | end 22 | 23 | private 24 | 25 | attr_reader :token_name 26 | 27 | def pos 28 | @lexer.start 29 | end 30 | 31 | def document 32 | loc = pos 33 | Nodes::Document.new loc, definition_list 34 | end 35 | 36 | def definition_list 37 | list = [] 38 | while !@lexer.done? 39 | list << definition 40 | end 41 | list 42 | end 43 | 44 | def definition 45 | case token_name 46 | when :FRAGMENT, :QUERY, :MUTATION, :SUBSCRIPTION, :LCURLY 47 | executable_definition 48 | when :EXTEND 49 | type_system_extension 50 | else 51 | desc = if at?(:STRING); string_value; end 52 | 53 | type_system_definition desc 54 | end 55 | end 56 | 57 | def type_system_extension 58 | expect_token :EXTEND 59 | case token_name 60 | when :SCALAR then scalar_type_extension 61 | when :TYPE then object_type_extension 62 | when :INTERFACE then interface_type_extension 63 | when :UNION then union_type_extension 64 | when :ENUM then enum_type_extension 65 | when :INPUT then input_object_type_extension 66 | else 67 | expect_token :SCALAR 68 | end 69 | end 70 | 71 | def input_object_type_extension 72 | loc = pos 73 | expect_token :INPUT 74 | name = self.name 75 | directives = if at?(:DIR_SIGN); self.directives; end 76 | input_fields_definition = if at?(:LCURLY); self.input_fields_definition; end 77 | Nodes::InputObjectTypeExtension.new(loc, name, directives, input_fields_definition) 78 | end 79 | 80 | def enum_type_extension 81 | loc = pos 82 | expect_token :ENUM 83 | name = self.name 84 | directives = if at?(:DIR_SIGN); self.directives; end 85 | enum_values_definition = if at?(:LCURLY); self.enum_values_definition; end 86 | Nodes::EnumTypeExtension.new(loc, name, directives, enum_values_definition) 87 | end 88 | 89 | def union_type_extension 90 | loc = pos 91 | expect_token :UNION 92 | name = self.name 93 | directives = if at?(:DIR_SIGN); self.directives; end 94 | union_member_types = if at?(:EQUALS); self.union_member_types; end 95 | Nodes::UnionTypeExtension.new(loc, name, directives, union_member_types) 96 | end 97 | 98 | def interface_type_extension 99 | loc = pos 100 | expect_token :INTERFACE 101 | name = self.name 102 | implements_interfaces = if at?(:IMPLEMENTS); self.implements_interfaces; end 103 | directives = if at?(:DIR_SIGN); self.directives; end 104 | fields_definition = if at?(:LCURLY); self.fields_definition; end 105 | Nodes::InterfaceTypeExtension.new(loc, name, implements_interfaces, directives, fields_definition) 106 | end 107 | 108 | def scalar_type_extension 109 | loc = pos 110 | expect_token :SCALAR 111 | name = self.name 112 | directives = if at?(:DIR_SIGN); self.directives; end 113 | Nodes::ScalarTypeExtension.new(loc, name, directives) 114 | end 115 | 116 | def object_type_extension 117 | loc = pos 118 | expect_token :TYPE 119 | name = self.name 120 | implements_interfaces = if at?(:IMPLEMENTS); self.implements_interfaces; end 121 | directives = if at?(:DIR_SIGN); self.directives; end 122 | fields_definition = if at?(:LCURLY); self.fields_definition; end 123 | Nodes::ObjectTypeExtension.new(loc, name, implements_interfaces, directives, fields_definition) 124 | end 125 | 126 | def type_system_definition desc 127 | case token_name 128 | when :SCHEMA then schema_definition(desc) 129 | when :DIRECTIVE then directive_defintion(desc) 130 | else 131 | type_definition(desc) 132 | end 133 | end 134 | 135 | def directive_defintion desc 136 | loc = pos 137 | expect_token :DIRECTIVE 138 | expect_token :DIR_SIGN 139 | name = self.name 140 | arguments_definition = if at?(:LPAREN); self.arguments_definition; end 141 | expect_token :ON 142 | directive_locations = self.directive_locations 143 | Nodes::DirectiveDefinition.new(loc, desc, name, arguments_definition, directive_locations) 144 | end 145 | 146 | def directive_locations 147 | list = [directive_location] 148 | while at?(:PIPE) 149 | accept_token 150 | list << directive_location 151 | end 152 | list 153 | end 154 | 155 | def directive_location 156 | loc = pos 157 | directive = expect_token_value :IDENTIFIER 158 | 159 | case directive 160 | when "QUERY", "MUTATION", "SUBSCRIPTION", "FIELD", "FRAGMENT_DEFINITION", "FRAGMENT_SPREAD", "INLINE_FRAGMENT" 161 | Nodes::ExecutableDirectiveLocation.new(loc, directive) 162 | when "SCHEMA", 163 | "SCALAR", 164 | "OBJECT", 165 | "FIELD_DEFINITION", 166 | "ARGUMENT_DEFINITION", 167 | "INTERFACE", 168 | "UNION", 169 | "ENUM", 170 | "ENUM_VALUE", 171 | "INPUT_OBJECT", 172 | "INPUT_FIELD_DEFINITION" 173 | Nodes::TypeSystemDirectiveLocation.new(loc, directive) 174 | else 175 | raise UnexpectedToken, "Expected directive #{directive}" 176 | end 177 | end 178 | 179 | def type_definition desc 180 | case token_name 181 | when :TYPE then object_type_definition(desc) 182 | when :INTERFACE then interface_type_definition(desc) 183 | when :UNION then union_type_definition(desc) 184 | when :SCALAR then scalar_type_definition(desc) 185 | when :ENUM then enum_type_definition(desc) 186 | when :INPUT then input_object_type_definition(desc) 187 | else 188 | expect_token :TYPE 189 | end 190 | end 191 | 192 | def input_object_type_definition desc 193 | loc = pos 194 | expect_token :INPUT 195 | name = self.name 196 | directives = if at?(:DIR_SIGN); self.directives; end 197 | input_fields_definition = if at?(:LCURLY); self.input_fields_definition; end 198 | Nodes::InputObjectTypeDefinition.new(loc, desc, name, directives, input_fields_definition) 199 | end 200 | 201 | def input_fields_definition 202 | expect_token :LCURLY 203 | list = [] 204 | while !at?(:RCURLY) 205 | list << input_value_definition 206 | end 207 | expect_token :RCURLY 208 | list 209 | end 210 | 211 | def enum_type_definition desc 212 | loc = pos 213 | expect_token :ENUM 214 | name = self.name 215 | directives = if at?(:DIR_SIGN); self.directives; end 216 | enum_values_definition = if at?(:LCURLY); self.enum_values_definition; end 217 | Nodes::EnumTypeDefinition.new(loc, desc, name, directives, enum_values_definition) 218 | end 219 | 220 | def enum_values_definition 221 | expect_token :LCURLY 222 | list = [] 223 | while !at?(:RCURLY) 224 | list << enum_value_definition 225 | end 226 | expect_token :RCURLY 227 | list 228 | end 229 | 230 | def enum_value_definition 231 | loc = pos 232 | description = if at?(:STRING); string_value; end 233 | enum_value = self.enum_value 234 | directives = if at?(:DIR_SIGN); self.directives; end 235 | Nodes::EnumValueDefinition.new(loc, description, enum_value, directives) 236 | end 237 | 238 | def scalar_type_definition desc 239 | loc = pos 240 | expect_token :SCALAR 241 | name = self.name 242 | directives = if at?(:DIR_SIGN); self.directives; end 243 | Nodes::ScalarTypeDefinition.new(loc, desc, name, directives) 244 | end 245 | 246 | def union_type_definition desc 247 | loc = pos 248 | expect_token :UNION 249 | name = self.name 250 | directives = if at?(:DIR_SIGN); self.directives; end 251 | union_member_types = if at?(:EQUALS); self.union_member_types; end 252 | Nodes::UnionTypeDefinition.new(loc, desc, name, directives, union_member_types) 253 | end 254 | 255 | def union_member_types 256 | expect_token :EQUALS 257 | list = [named_type] 258 | while at?(:PIPE) 259 | accept_token 260 | list << named_type 261 | end 262 | list 263 | end 264 | 265 | def interface_type_definition desc 266 | loc = pos 267 | expect_token :INTERFACE 268 | name = self.name 269 | directives = if at?(:DIR_SIGN); self.directives; end 270 | fields_definition = if at?(:LCURLY); self.fields_definition; end 271 | Nodes::InterfaceTypeDefinition.new(loc, desc, name, directives, fields_definition) 272 | end 273 | 274 | def object_type_definition desc 275 | loc = pos 276 | expect_token :TYPE 277 | name = self.name 278 | implements_interfaces = if at?(:IMPLEMENTS); self.implements_interfaces; end 279 | directives = if at?(:DIR_SIGN); self.directives; end 280 | fields_definition = if at?(:LCURLY); self.fields_definition; end 281 | 282 | Nodes::ObjectTypeDefinition.new(loc, desc, name, implements_interfaces, directives, fields_definition) 283 | end 284 | 285 | def fields_definition 286 | expect_token :LCURLY 287 | list = [] 288 | while !at?(:RCURLY) 289 | list << field_definition 290 | end 291 | expect_token :RCURLY 292 | list 293 | end 294 | 295 | def field_definition 296 | loc = pos 297 | description = if at?(:STRING); string_value; end 298 | name = self.name 299 | arguments_definition = if at?(:LPAREN); self.arguments_definition; end 300 | expect_token :COLON 301 | type = self.type 302 | directives = if at?(:DIR_SIGN); self.directives; end 303 | 304 | Nodes::FieldDefinition.new(loc, description, name, arguments_definition, type, directives) 305 | end 306 | 307 | def arguments_definition 308 | expect_token :LPAREN 309 | list = [] 310 | while !at?(:RPAREN) 311 | list << input_value_definition 312 | end 313 | expect_token :RPAREN 314 | list 315 | end 316 | 317 | def input_value_definition 318 | loc = pos 319 | description = if at?(:STRING); string_value; end 320 | name = self.name 321 | expect_token :COLON 322 | type = self.type 323 | default_value = if at?(:EQUALS); self.default_value; end 324 | directives = if at?(:DIR_SIGN); self.directives; end 325 | Nodes::InputValueDefinition.new(loc, description, name, type, default_value, directives) 326 | end 327 | 328 | def implements_interfaces 329 | expect_token :IMPLEMENTS 330 | list = [self.named_type] 331 | while true 332 | accept_token if at?(:AMP) 333 | break unless at?(:IDENTIFIER) 334 | list << self.named_type 335 | end 336 | list 337 | end 338 | 339 | def schema_definition desc 340 | loc = pos 341 | expect_token :SCHEMA 342 | 343 | directives = if at?(:DIR_SIGN); self.directives; end 344 | expect_token :LCURLY 345 | defs = root_operation_type_definition 346 | expect_token :RCURLY 347 | Nodes::SchemaDefinition.new(loc, desc, directives, defs) 348 | end 349 | 350 | def root_operation_type_definition 351 | list = [] 352 | while !at?(:RCURLY) 353 | loc = pos 354 | operation_type = self.operation_type 355 | expect_token :COLON 356 | list << Nodes::RootOperationTypeDefinition.new(loc, operation_type, named_type) 357 | end 358 | list 359 | end 360 | 361 | def executable_definition 362 | if at?(:FRAGMENT) 363 | fragment_definition 364 | else 365 | operation_definition 366 | end 367 | end 368 | 369 | def fragment_definition 370 | loc = pos 371 | expect_token :FRAGMENT 372 | expect_token(:IDENTIFIER) if at?(:ON) 373 | name = self.name 374 | tc = self.type_condition 375 | directives = if at?(:DIR_SIGN) 376 | self.directives 377 | end 378 | 379 | Nodes::FragmentDefinition.new(loc, name, tc, directives, selection_set) 380 | end 381 | 382 | def operation_definition 383 | loc = pos 384 | case token_name 385 | when :QUERY, :MUTATION, :SUBSCRIPTION 386 | type = self.operation_type 387 | ident = if at?(:IDENTIFIER); name; end 388 | variable_definitions = if at?(:LPAREN); self.variable_definitions; end 389 | directives = if at?(:DIR_SIGN); self.directives; end 390 | end 391 | 392 | Nodes::OperationDefinition.new( 393 | loc, 394 | type, 395 | ident, 396 | variable_definitions, 397 | directives, 398 | selection_set 399 | ) 400 | end 401 | 402 | def selection_set 403 | expect_token(:LCURLY) 404 | list = [] 405 | while !at?(:RCURLY) 406 | list << selection 407 | end 408 | expect_token(:RCURLY) 409 | list 410 | end 411 | 412 | def selection 413 | if at?(:ELLIPSIS) 414 | selection_fragment 415 | else 416 | field 417 | end 418 | end 419 | 420 | def selection_fragment 421 | expect_token :ELLIPSIS 422 | 423 | case token_name 424 | when :ON, :DIR_SIGN, :LCURLY then inline_fragment 425 | when :IDENTIFIER then fragment_spread 426 | else 427 | expect_token :IDENTIFIER 428 | end 429 | end 430 | 431 | def fragment_spread 432 | loc = pos 433 | name = self.name 434 | directives = if at?(:DIR_SIGN); self.directives; end 435 | 436 | expect_token(:IDENTIFIER) if at?(:ON) 437 | 438 | Nodes::FragmentSpread.new(loc, name, directives) 439 | end 440 | 441 | def inline_fragment 442 | loc = pos 443 | type_condition = if at?(:ON) 444 | self.type_condition 445 | end 446 | 447 | directives = if at?(:DIR_SIGN) 448 | self.directives 449 | end 450 | 451 | Nodes::InlineFragment.new(loc, type_condition, directives, selection_set) 452 | end 453 | 454 | def type_condition 455 | loc = pos 456 | expect_token :ON 457 | Nodes::TypeCondition.new(loc, named_type) 458 | end 459 | 460 | def field 461 | loc = pos 462 | name = self.name 463 | 464 | aliaz = nil 465 | 466 | if at?(:COLON) 467 | expect_token(:COLON) 468 | aliaz = name 469 | name = self.name 470 | end 471 | 472 | arguments = if at?(:LPAREN); self.arguments; end 473 | directives = if at?(:DIR_SIGN); self.directives; end 474 | selection_set = if at?(:LCURLY); self.selection_set; end 475 | 476 | Nodes::Field.new(loc, aliaz, name, arguments, directives, selection_set) 477 | end 478 | 479 | def operation_type 480 | val = if at?(:QUERY) 481 | "query" 482 | elsif at?(:MUTATION) 483 | "mutation" 484 | elsif at?(:SUBSCRIPTION) 485 | "subscription" 486 | else 487 | expect_token(:QUERY) 488 | end 489 | accept_token 490 | val 491 | end 492 | 493 | def directives 494 | list = [] 495 | while at?(:DIR_SIGN) 496 | list << directive 497 | end 498 | list 499 | end 500 | 501 | def directive 502 | loc = pos 503 | expect_token(:DIR_SIGN) 504 | name = self.name 505 | arguments = if at?(:LPAREN) 506 | self.arguments 507 | end 508 | 509 | Nodes::Directive.new(loc, name, arguments) 510 | end 511 | 512 | def arguments 513 | expect_token(:LPAREN) 514 | args = [] 515 | while !at?(:RPAREN) 516 | args << argument 517 | end 518 | expect_token(:RPAREN) 519 | args 520 | end 521 | 522 | def argument 523 | loc = pos 524 | name = self.name 525 | expect_token(:COLON) 526 | Nodes::Argument.new(loc, name, value) 527 | end 528 | 529 | def variable_definitions 530 | expect_token(:LPAREN) 531 | defs = [] 532 | while !at?(:RPAREN) 533 | defs << variable_definition 534 | end 535 | expect_token(:RPAREN) 536 | defs 537 | end 538 | 539 | def variable_definition 540 | loc = pos 541 | var = variable if at?(:VAR_SIGN) 542 | expect_token(:COLON) 543 | type = self.type 544 | default_value = if at?(:EQUALS) 545 | self.default_value 546 | end 547 | 548 | Nodes::VariableDefinition.new(loc, var, type, default_value) 549 | end 550 | 551 | def default_value 552 | expect_token(:EQUALS) 553 | value 554 | end 555 | 556 | def value 557 | case token_name 558 | when :INT then int_value 559 | when :FLOAT then float_value 560 | when :STRING then string_value 561 | when :TRUE, :FALSE then boolean_value 562 | when :NULL then null_value 563 | when :IDENTIFIER then enum_value 564 | when :LBRACKET then list_value 565 | when :LCURLY then object_value 566 | when :VAR_SIGN then variable 567 | else 568 | expect_token :INT 569 | end 570 | end 571 | 572 | def object_value 573 | start = pos 574 | expect_token(:LCURLY) 575 | list = [] 576 | while !at?(:RCURLY) 577 | loc = pos 578 | n = name 579 | expect_token(:COLON) 580 | list << Nodes::ObjectField.new(loc, n, value) 581 | end 582 | expect_token(:RCURLY) 583 | Nodes::ObjectValue.new(start, list) 584 | end 585 | 586 | def list_value 587 | loc = pos 588 | expect_token(:LBRACKET) 589 | list = [] 590 | while !at?(:RBRACKET) 591 | list << value 592 | end 593 | expect_token(:RBRACKET) 594 | Nodes::ListValue.new(loc, list) 595 | end 596 | 597 | def enum_value 598 | Nodes::EnumValue.new(pos, expect_token_value(:IDENTIFIER)) 599 | end 600 | 601 | def float_value 602 | Nodes::FloatValue.new(pos, expect_token_value(:FLOAT)) 603 | end 604 | 605 | def int_value 606 | Nodes::IntValue.new(pos, expect_token_value(:INT)) 607 | end 608 | 609 | def string_value 610 | Nodes::StringValue.new(pos, expect_string_value) 611 | end 612 | 613 | def boolean_value 614 | if at?(:TRUE) 615 | accept_token 616 | Nodes::BooleanValue.new(pos, "true") 617 | elsif at?(:FALSE) 618 | accept_token 619 | Nodes::BooleanValue.new(pos, "false") 620 | else 621 | expect_token(:TRUE) 622 | end 623 | end 624 | 625 | def null_value 626 | expect_token :NULL 627 | Nodes::NullValue.new(pos, "null") 628 | end 629 | 630 | def type 631 | type = case token_name 632 | when :IDENTIFIER then named_type 633 | when :LBRACKET then list_type 634 | end 635 | 636 | if at?(:BANG) 637 | Nodes::NotNullType.new pos, type 638 | expect_token(:BANG) 639 | end 640 | type 641 | end 642 | 643 | def list_type 644 | loc = pos 645 | expect_token(:LBRACKET) 646 | type = Nodes::ListType.new(loc, self.type) 647 | expect_token(:RBRACKET) 648 | type 649 | end 650 | 651 | def named_type 652 | Nodes::NamedType.new(pos, name) 653 | end 654 | 655 | def variable 656 | loc = pos 657 | expect_token(:VAR_SIGN) 658 | Nodes::Variable.new loc, name 659 | end 660 | 661 | def name 662 | case token_name 663 | when :IDENTIFIER then expect_token_value(:IDENTIFIER) 664 | when :TYPE then 665 | accept_token 666 | "type" 667 | when :QUERY then 668 | accept_token 669 | "query" 670 | when :INPUT then 671 | accept_token 672 | "input" 673 | else 674 | expect_token(:IDENTIFIER) 675 | end 676 | end 677 | 678 | def accept_token 679 | @token_name = @lexer.advance 680 | end 681 | 682 | def expect_token tok 683 | unless at?(tok) 684 | raise UnexpectedToken, "Expected token #{tok}, actual: #{token_name} #{@lexer.token_value} line: #{@lexer.line}" 685 | end 686 | accept_token 687 | end 688 | 689 | # Only use when we care about the expected token's value 690 | def expect_token_value tok 691 | token_value = @lexer.token_value 692 | expect_token tok 693 | token_value 694 | end 695 | 696 | def expect_string_value 697 | token_value = @lexer.string_value 698 | expect_token :STRING 699 | token_value 700 | end 701 | 702 | def at? tok 703 | token_name == tok 704 | end 705 | end 706 | end 707 | -------------------------------------------------------------------------------- /benchmark/fixtures/negotiate.gql: -------------------------------------------------------------------------------- 1 | query NegotiateFromSession( 2 | $sessionToken: String! 3 | $checkpointData: String 4 | $queueToken: String 5 | ) { 6 | session(sessionInput: { sessionToken: $sessionToken }) { 7 | context { 8 | session { 9 | ... on UnvalidatedParametersFact { 10 | email 11 | phone 12 | shippingAddress { 13 | address1 14 | address2 15 | city 16 | company 17 | countryCode 18 | firstName 19 | lastName 20 | phone 21 | zoneCode 22 | postalCode: zip 23 | __typename 24 | } 25 | billingAddress { 26 | address1 27 | address2 28 | city 29 | company 30 | countryCode 31 | firstName 32 | lastName 33 | phone 34 | zoneCode 35 | postalCode: zip 36 | __typename 37 | } 38 | __typename 39 | } 40 | ... on PreviousNegotiationFact { 41 | orderNumber 42 | requiresShipping 43 | __typename 44 | } 45 | __typename 46 | } 47 | policies { 48 | payment { 49 | ... on WalletsFact { 50 | walletsAlreadyOffered 51 | __typename 52 | } 53 | ... on PreviousPaymentsFact { 54 | previouslyPaidTotal { 55 | amount 56 | currencyCode 57 | __typename 58 | } 59 | updatedTotal { 60 | amount 61 | currencyCode 62 | __typename 63 | } 64 | billingAddress { 65 | ... on StreetAddress { 66 | name 67 | firstName 68 | lastName 69 | company 70 | address1 71 | address2 72 | city 73 | countryCode 74 | zoneCode 75 | postalCode 76 | coordinates { 77 | latitude 78 | longitude 79 | __typename 80 | } 81 | phone 82 | __typename 83 | } 84 | __typename 85 | } 86 | __typename 87 | } 88 | __typename 89 | } 90 | buyerIdentity { 91 | ... on PreviousBuyerIdentityFact { 92 | contactMethod { 93 | email 94 | phoneNumber 95 | __typename 96 | } 97 | __typename 98 | } 99 | __typename 100 | } 101 | tax { 102 | ... on PreviousTaxFact { 103 | total { 104 | amount 105 | currencyCode 106 | __typename 107 | } 108 | __typename 109 | } 110 | __typename 111 | } 112 | discount { 113 | ... on PreviousDiscountFact { 114 | orderLevelDiscounts { 115 | label 116 | amount { 117 | amount 118 | currencyCode 119 | __typename 120 | } 121 | __typename 122 | } 123 | __typename 124 | } 125 | __typename 126 | } 127 | merchandise { 128 | ... on PreviousMerchandiseFact { 129 | lines { 130 | title 131 | variantTitle 132 | quantity 133 | sku 134 | image { 135 | id 136 | altText 137 | url 138 | one: transformedSrc(maxWidth: 64, maxHeight: 64) 139 | two: transformedSrc(maxWidth: 128, maxHeight: 128) 140 | four: transformedSrc(maxWidth: 256, maxHeight: 256) 141 | __typename 142 | } 143 | properties { 144 | ...MerchandiseProperties 145 | __typename 146 | } 147 | price { 148 | amount 149 | currencyCode 150 | __typename 151 | } 152 | priceAfterDiscounts { 153 | amount 154 | currencyCode 155 | __typename 156 | } 157 | appliedDiscounts { 158 | label 159 | amountDiscounted { 160 | amount 161 | currencyCode 162 | __typename 163 | } 164 | __typename 165 | } 166 | quantityChange { 167 | delta 168 | type 169 | __typename 170 | } 171 | __typename 172 | } 173 | __typename 174 | } 175 | __typename 176 | } 177 | tip { 178 | ... on PreviousTipFact { 179 | total { 180 | amount 181 | currencyCode 182 | __typename 183 | } 184 | __typename 185 | } 186 | __typename 187 | } 188 | delivery { 189 | ... on PreviousDeliveryFact { 190 | total { 191 | amount 192 | currencyCode 193 | __typename 194 | } 195 | deliveryAddress { 196 | ... on StreetAddress { 197 | address1 198 | address2 199 | city 200 | company 201 | coordinates { 202 | latitude 203 | longitude 204 | __typename 205 | } 206 | countryCode 207 | firstName 208 | label 209 | lastName 210 | name 211 | phone 212 | postalCode 213 | zoneCode 214 | __typename 215 | } 216 | ... on Geolocation { 217 | coordinates { 218 | latitude 219 | longitude 220 | __typename 221 | } 222 | country { 223 | code 224 | name 225 | __typename 226 | } 227 | zone { 228 | code 229 | name 230 | __typename 231 | } 232 | __typename 233 | } 234 | ... on PartialStreetAddress { 235 | address1 236 | address2 237 | address3 238 | city 239 | company 240 | countryCode 241 | firstName 242 | lastName 243 | name 244 | phone 245 | postalCode 246 | zoneCode 247 | coordinates { 248 | latitude 249 | longitude 250 | __typename 251 | } 252 | __typename 253 | } 254 | __typename 255 | } 256 | pickupAddress { 257 | address1 258 | countryCode 259 | coordinates { 260 | latitude 261 | longitude 262 | __typename 263 | } 264 | address2 265 | city 266 | postalCode 267 | zoneCode 268 | phone 269 | __typename 270 | } 271 | pickupAddressName 272 | lines { 273 | title 274 | lineAmount { 275 | amount 276 | currencyCode 277 | __typename 278 | } 279 | appliedDiscounts { 280 | label 281 | allocationValue { 282 | ... on PercentageValue { 283 | percentage 284 | __typename 285 | } 286 | ... on FixedAmountValue { 287 | fixedAmount { 288 | ... on MoneyValueConstraint { 289 | value { 290 | amount 291 | currencyCode 292 | __typename 293 | } 294 | __typename 295 | } 296 | __typename 297 | } 298 | appliesOnEachItem 299 | __typename 300 | } 301 | __typename 302 | } 303 | amountDiscounted { 304 | amount 305 | currencyCode 306 | __typename 307 | } 308 | __typename 309 | } 310 | __typename 311 | } 312 | __typename 313 | } 314 | __typename 315 | } 316 | __typename 317 | } 318 | __typename 319 | } 320 | sessionType 321 | sourceId 322 | sourceVersion 323 | checkoutSessionIdentifier 324 | storefrontAnalyticsStartedOrderEventId 325 | negotiate( 326 | input: { checkpointData: $checkpointData, queueToken: $queueToken } 327 | ) { 328 | result { 329 | ... on NegotiationResultAvailable { 330 | queueToken 331 | buyerProposal { 332 | ...BuyerProposalDetails 333 | __typename 334 | } 335 | sellerProposal { 336 | ...ProposalDetails 337 | __typename 338 | } 339 | __typename 340 | } 341 | ... on CheckpointDenied { 342 | redirectUrl 343 | __typename 344 | } 345 | ... on Throttled { 346 | pollAfter 347 | queueToken 348 | pollUrl 349 | buyerProposal { 350 | ...BuyerProposalDetails 351 | __typename 352 | } 353 | __typename 354 | } 355 | ... on NegotiationResultFailed { 356 | __typename 357 | } 358 | ... on SubmittedForCompletion { 359 | __typename 360 | receipt { 361 | ...ReceiptDetails 362 | __typename 363 | } 364 | } 365 | __typename 366 | } 367 | errors { 368 | code 369 | localizedMessage 370 | nonLocalizedMessage 371 | localizedMessageHtml 372 | ... on RemoveTermViolation { 373 | target 374 | __typename 375 | } 376 | ... on AcceptNewTermViolation { 377 | target 378 | __typename 379 | } 380 | ... on ConfirmChangeViolation { 381 | from 382 | to 383 | __typename 384 | } 385 | ... on UnprocessableTermViolation { 386 | target 387 | __typename 388 | } 389 | ... on UnresolvableTermViolation { 390 | target 391 | __typename 392 | } 393 | ... on InputValidationError { 394 | field 395 | __typename 396 | } 397 | ... on GenericError { 398 | __typename 399 | } 400 | __typename 401 | } 402 | __typename 403 | } 404 | __typename 405 | } 406 | } 407 | fragment MerchandiseProperties on MerchandiseProperty { 408 | name 409 | value { 410 | ... on MerchandisePropertyValueString { 411 | string: value 412 | __typename 413 | } 414 | ... on MerchandisePropertyValueInt { 415 | int: value 416 | __typename 417 | } 418 | ... on MerchandisePropertyValueFloat { 419 | float: value 420 | __typename 421 | } 422 | ... on MerchandisePropertyValueBoolean { 423 | boolean: value 424 | __typename 425 | } 426 | ... on MerchandisePropertyValueJson { 427 | json: value 428 | __typename 429 | } 430 | __typename 431 | } 432 | visible 433 | __typename 434 | } 435 | fragment BuyerProposalDetails on Proposal { 436 | merchandiseDiscount { 437 | ...ProposalDiscountFragment 438 | __typename 439 | } 440 | deliveryDiscount { 441 | ...ProposalDiscountFragment 442 | __typename 443 | } 444 | delivery { 445 | ...ProposalDeliveryFragment 446 | __typename 447 | } 448 | merchandise { 449 | ... on FilledMerchandiseTerms { 450 | taxesIncluded 451 | merchandiseLines { 452 | stableId 453 | merchandise { 454 | ...SourceProvidedMerchandise 455 | ...ProductVariantMerchandiseDetails 456 | ...ContextualizedProductVariantMerchandiseDetails 457 | ... on MissingProductVariantMerchandise { 458 | id 459 | digest 460 | variantId 461 | __typename 462 | } 463 | __typename 464 | } 465 | quantity { 466 | ... on ProposalMerchandiseQuantityByItem { 467 | items { 468 | ... on IntValueConstraint { 469 | value 470 | __typename 471 | } 472 | __typename 473 | } 474 | __typename 475 | } 476 | __typename 477 | } 478 | totalAmount { 479 | ... on MoneyValueConstraint { 480 | value { 481 | amount 482 | currencyCode 483 | __typename 484 | } 485 | __typename 486 | } 487 | __typename 488 | } 489 | recurringTotal { 490 | title 491 | interval 492 | intervalCount 493 | recurringPrice { 494 | amount 495 | currencyCode 496 | __typename 497 | } 498 | fixedPrice { 499 | amount 500 | currencyCode 501 | __typename 502 | } 503 | fixedPriceCount 504 | __typename 505 | } 506 | lineAllocations { 507 | ...LineAllocationDetails 508 | __typename 509 | } 510 | lineComponents { 511 | ...MerchandiseBundleLineComponent 512 | __typename 513 | } 514 | __typename 515 | } 516 | __typename 517 | } 518 | __typename 519 | } 520 | runningTotal { 521 | ... on MoneyValueConstraint { 522 | value { 523 | amount 524 | currencyCode 525 | __typename 526 | } 527 | __typename 528 | } 529 | __typename 530 | } 531 | total { 532 | ... on MoneyValueConstraint { 533 | value { 534 | amount 535 | currencyCode 536 | __typename 537 | } 538 | __typename 539 | } 540 | __typename 541 | } 542 | checkoutTotalBeforeTaxesAndShipping { 543 | ... on MoneyValueConstraint { 544 | value { 545 | amount 546 | currencyCode 547 | __typename 548 | } 549 | __typename 550 | } 551 | __typename 552 | } 553 | checkoutTotalTaxes { 554 | ... on MoneyValueConstraint { 555 | value { 556 | amount 557 | currencyCode 558 | __typename 559 | } 560 | __typename 561 | } 562 | __typename 563 | } 564 | checkoutTotal { 565 | ... on MoneyValueConstraint { 566 | value { 567 | amount 568 | currencyCode 569 | __typename 570 | } 571 | __typename 572 | } 573 | __typename 574 | } 575 | deferredTotal { 576 | amount { 577 | ... on MoneyValueConstraint { 578 | value { 579 | amount 580 | currencyCode 581 | __typename 582 | } 583 | __typename 584 | } 585 | __typename 586 | } 587 | subtotalAmount { 588 | ... on MoneyValueConstraint { 589 | value { 590 | amount 591 | currencyCode 592 | __typename 593 | } 594 | __typename 595 | } 596 | __typename 597 | } 598 | taxes { 599 | ... on MoneyValueConstraint { 600 | value { 601 | amount 602 | currencyCode 603 | __typename 604 | } 605 | __typename 606 | } 607 | __typename 608 | } 609 | dueAt 610 | __typename 611 | } 612 | hasOnlyDeferredShipping 613 | subtotalBeforeTaxesAndShipping { 614 | ... on MoneyValueConstraint { 615 | value { 616 | amount 617 | currencyCode 618 | __typename 619 | } 620 | __typename 621 | } 622 | __typename 623 | } 624 | attribution { 625 | attributions { 626 | ... on AttributionItem { 627 | ... on RetailAttributions { 628 | deviceId 629 | locationId 630 | userId 631 | __typename 632 | } 633 | ... on DraftOrderAttributions { 634 | userIdentifier: userId 635 | sourceName 636 | locationIdentifier: locationId 637 | __typename 638 | } 639 | __typename 640 | } 641 | __typename 642 | } 643 | __typename 644 | } 645 | __typename 646 | } 647 | fragment ProposalDiscountFragment on DiscountTermsV2 { 648 | __typename 649 | ... on FilledDiscountTerms { 650 | lines { 651 | ...DiscountLineDetailsFragment 652 | __typename 653 | } 654 | __typename 655 | } 656 | ... on PendingTerms { 657 | pollDelay 658 | taskId 659 | __typename 660 | } 661 | ... on UnavailableTerms { 662 | __typename 663 | } 664 | } 665 | fragment DiscountLineDetailsFragment on DiscountLine { 666 | allocations { 667 | ... on DiscountAllocatedAllocationSet { 668 | __typename 669 | allocations { 670 | amount { 671 | ... on MoneyValueConstraint { 672 | value { 673 | amount 674 | currencyCode 675 | __typename 676 | } 677 | __typename 678 | } 679 | __typename 680 | } 681 | target { 682 | index 683 | targetType 684 | stableId 685 | __typename 686 | } 687 | __typename 688 | } 689 | } 690 | __typename 691 | } 692 | discount { 693 | ...DiscountDetailsFragment 694 | __typename 695 | } 696 | lineAmount { 697 | ... on MoneyValueConstraint { 698 | value { 699 | amount 700 | currencyCode 701 | __typename 702 | } 703 | __typename 704 | } 705 | __typename 706 | } 707 | __typename 708 | } 709 | fragment DiscountDetailsFragment on Discount { 710 | ... on CustomDiscount { 711 | title 712 | presentationLevel 713 | signature 714 | signatureUuid 715 | type 716 | value { 717 | ... on PercentageValue { 718 | percentage 719 | __typename 720 | } 721 | ... on FixedAmountValue { 722 | appliesOnEachItem 723 | fixedAmount { 724 | ... on MoneyValueConstraint { 725 | value { 726 | amount 727 | currencyCode 728 | __typename 729 | } 730 | __typename 731 | } 732 | __typename 733 | } 734 | __typename 735 | } 736 | __typename 737 | } 738 | __typename 739 | } 740 | ... on CodeDiscount { 741 | title 742 | code 743 | presentationLevel 744 | __typename 745 | } 746 | ... on DiscountCodeTrigger { 747 | code 748 | __typename 749 | } 750 | ... on AutomaticDiscount { 751 | presentationLevel 752 | title 753 | __typename 754 | } 755 | __typename 756 | } 757 | fragment ProposalDeliveryFragment on DeliveryTerms { 758 | __typename 759 | ... on FilledDeliveryTerms { 760 | intermediateRates 761 | progressiveRatesEstimatedTimeUntilCompletion 762 | shippingRatesStatusToken 763 | deliveryLines { 764 | destinationAddress { 765 | ... on StreetAddress { 766 | name 767 | firstName 768 | lastName 769 | company 770 | address1 771 | address2 772 | city 773 | countryCode 774 | zoneCode 775 | postalCode 776 | coordinates { 777 | latitude 778 | longitude 779 | __typename 780 | } 781 | phone 782 | __typename 783 | } 784 | ... on Geolocation { 785 | country { 786 | code 787 | __typename 788 | } 789 | zone { 790 | code 791 | __typename 792 | } 793 | coordinates { 794 | latitude 795 | longitude 796 | __typename 797 | } 798 | __typename 799 | } 800 | ... on PartialStreetAddress { 801 | name 802 | firstName 803 | lastName 804 | company 805 | address1 806 | address2 807 | city 808 | countryCode 809 | zoneCode 810 | postalCode 811 | phone 812 | coordinates { 813 | latitude 814 | longitude 815 | __typename 816 | } 817 | __typename 818 | } 819 | __typename 820 | } 821 | targetMerchandise { 822 | ... on FilledMerchandiseLineTargetCollection { 823 | lines { 824 | stableId 825 | merchandise { 826 | ...SourceProvidedMerchandise 827 | ... on ProductVariantMerchandise { 828 | id 829 | digest 830 | variantId 831 | title 832 | requiresShipping 833 | properties { 834 | ...MerchandiseProperties 835 | __typename 836 | } 837 | __typename 838 | } 839 | ... on ContextualizedProductVariantMerchandise { 840 | id 841 | digest 842 | variantId 843 | title 844 | requiresShipping 845 | sellingPlan { 846 | id 847 | digest 848 | name 849 | prepaid 850 | deliveriesPerBillingCycle 851 | subscriptionDetails { 852 | billingInterval 853 | billingIntervalCount 854 | billingMaxCycles 855 | deliveryInterval 856 | deliveryIntervalCount 857 | __typename 858 | } 859 | __typename 860 | } 861 | properties { 862 | ...MerchandiseProperties 863 | __typename 864 | } 865 | __typename 866 | } 867 | ... on MissingProductVariantMerchandise { 868 | id 869 | digest 870 | variantId 871 | __typename 872 | } 873 | __typename 874 | } 875 | totalAmount { 876 | ... on MoneyValueConstraint { 877 | value { 878 | amount 879 | currencyCode 880 | __typename 881 | } 882 | __typename 883 | } 884 | __typename 885 | } 886 | __typename 887 | } 888 | __typename 889 | } 890 | __typename 891 | } 892 | groupType 893 | selectedDeliveryStrategy { 894 | ... on CompleteDeliveryStrategy { 895 | handle 896 | __typename 897 | } 898 | ... on DeliveryStrategyReference { 899 | handle 900 | __typename 901 | } 902 | __typename 903 | } 904 | availableDeliveryStrategies { 905 | ... on CompleteDeliveryStrategy { 906 | title 907 | handle 908 | custom 909 | description 910 | acceptsInstructions 911 | phoneRequired 912 | methodType 913 | carrierName 914 | brandedPromise { 915 | logoUrl 916 | name 917 | __typename 918 | } 919 | deliveryStrategyBreakdown { 920 | amount { 921 | ... on MoneyValueConstraint { 922 | value { 923 | amount 924 | currencyCode 925 | __typename 926 | } 927 | __typename 928 | } 929 | __typename 930 | } 931 | discountRecurringCycleLimit 932 | excludeFromDeliveryOptionPrice 933 | targetMerchandise { 934 | ... on FilledMerchandiseLineTargetCollection { 935 | lines { 936 | stableId 937 | merchandise { 938 | ...SourceProvidedMerchandise 939 | ... on ProductVariantMerchandise { 940 | id 941 | digest 942 | variantId 943 | title 944 | requiresShipping 945 | properties { 946 | ...MerchandiseProperties 947 | __typename 948 | } 949 | __typename 950 | } 951 | ... on ContextualizedProductVariantMerchandise { 952 | id 953 | digest 954 | variantId 955 | title 956 | requiresShipping 957 | sellingPlan { 958 | id 959 | digest 960 | name 961 | prepaid 962 | deliveriesPerBillingCycle 963 | subscriptionDetails { 964 | billingInterval 965 | billingIntervalCount 966 | billingMaxCycles 967 | deliveryInterval 968 | deliveryIntervalCount 969 | __typename 970 | } 971 | __typename 972 | } 973 | properties { 974 | ...MerchandiseProperties 975 | __typename 976 | } 977 | __typename 978 | } 979 | ... on MissingProductVariantMerchandise { 980 | id 981 | digest 982 | variantId 983 | __typename 984 | } 985 | __typename 986 | } 987 | totalAmount { 988 | ... on MoneyValueConstraint { 989 | value { 990 | amount 991 | currencyCode 992 | __typename 993 | } 994 | __typename 995 | } 996 | __typename 997 | } 998 | __typename 999 | } 1000 | __typename 1001 | } 1002 | __typename 1003 | } 1004 | __typename 1005 | } 1006 | minDeliveryDateTime 1007 | maxDeliveryDateTime 1008 | deliveryPromisePresentmentTitle { 1009 | short 1010 | long 1011 | __typename 1012 | } 1013 | displayCheckoutRedesign 1014 | estimatedTimeInTransit { 1015 | ... on IntIntervalConstraint { 1016 | lowerBound 1017 | upperBound 1018 | __typename 1019 | } 1020 | ... on IntValueConstraint { 1021 | value 1022 | __typename 1023 | } 1024 | __typename 1025 | } 1026 | amount { 1027 | ... on MoneyValueConstraint { 1028 | value { 1029 | amount 1030 | currencyCode 1031 | __typename 1032 | } 1033 | __typename 1034 | } 1035 | __typename 1036 | } 1037 | amountAfterDiscounts { 1038 | ... on MoneyValueConstraint { 1039 | value { 1040 | amount 1041 | currencyCode 1042 | __typename 1043 | } 1044 | __typename 1045 | } 1046 | __typename 1047 | } 1048 | pickupLocation { 1049 | ... on PickupInStoreLocation { 1050 | address { 1051 | address1 1052 | address2 1053 | city 1054 | countryCode 1055 | phone 1056 | postalCode 1057 | zoneCode 1058 | __typename 1059 | } 1060 | instructions 1061 | name 1062 | __typename 1063 | } 1064 | ... on PickupPointLocation { 1065 | address { 1066 | address1 1067 | address2 1068 | address3 1069 | city 1070 | countryCode 1071 | zoneCode 1072 | postalCode 1073 | coordinates { 1074 | latitude 1075 | longitude 1076 | __typename 1077 | } 1078 | __typename 1079 | } 1080 | businessHours { 1081 | day 1082 | openingTime 1083 | closingTime 1084 | __typename 1085 | } 1086 | carrierCode 1087 | carrierName 1088 | handle 1089 | kind 1090 | name 1091 | __typename 1092 | } 1093 | __typename 1094 | } 1095 | __typename 1096 | } 1097 | __typename 1098 | } 1099 | __typename 1100 | } 1101 | __typename 1102 | } 1103 | ... on PendingTerms { 1104 | pollDelay 1105 | taskId 1106 | __typename 1107 | } 1108 | ... on UnavailableTerms { 1109 | __typename 1110 | } 1111 | } 1112 | fragment SourceProvidedMerchandise on Merchandise { 1113 | ... on SourceProvidedMerchandise { 1114 | __typename 1115 | product { 1116 | id 1117 | title 1118 | productType 1119 | vendor 1120 | __typename 1121 | } 1122 | digest 1123 | variantId 1124 | optionalIdentifier 1125 | title 1126 | subtitle 1127 | taxable 1128 | giftCard 1129 | requiresShipping 1130 | price { 1131 | amount 1132 | currencyCode 1133 | __typename 1134 | } 1135 | deferredAmount { 1136 | amount 1137 | currencyCode 1138 | __typename 1139 | } 1140 | image { 1141 | altText 1142 | one: transformedSrc(maxWidth: 64, maxHeight: 64) 1143 | two: transformedSrc(maxWidth: 128, maxHeight: 128) 1144 | four: transformedSrc(maxWidth: 256, maxHeight: 256) 1145 | __typename 1146 | } 1147 | options { 1148 | name 1149 | value 1150 | __typename 1151 | } 1152 | properties { 1153 | ...MerchandiseProperties 1154 | __typename 1155 | } 1156 | taxCode 1157 | taxesIncluded 1158 | weight { 1159 | value 1160 | unit 1161 | __typename 1162 | } 1163 | sku 1164 | } 1165 | __typename 1166 | } 1167 | fragment ProductVariantMerchandiseDetails on ProductVariantMerchandise { 1168 | id 1169 | digest 1170 | variantId 1171 | title 1172 | subtitle 1173 | product { 1174 | id 1175 | vendor 1176 | productType 1177 | __typename 1178 | } 1179 | image { 1180 | altText 1181 | one: transformedSrc(maxWidth: 64, maxHeight: 64) 1182 | two: transformedSrc(maxWidth: 128, maxHeight: 128) 1183 | four: transformedSrc(maxWidth: 256, maxHeight: 256) 1184 | __typename 1185 | } 1186 | properties { 1187 | ...MerchandiseProperties 1188 | __typename 1189 | } 1190 | requiresShipping 1191 | options { 1192 | name 1193 | value 1194 | __typename 1195 | } 1196 | sellingPlan { 1197 | id 1198 | subscriptionDetails { 1199 | billingInterval 1200 | __typename 1201 | } 1202 | __typename 1203 | } 1204 | giftCard 1205 | __typename 1206 | } 1207 | fragment ContextualizedProductVariantMerchandiseDetails on ContextualizedProductVariantMerchandise { 1208 | id 1209 | digest 1210 | variantId 1211 | title 1212 | subtitle 1213 | sku 1214 | price { 1215 | amount 1216 | currencyCode 1217 | __typename 1218 | } 1219 | product { 1220 | id 1221 | vendor 1222 | productType 1223 | __typename 1224 | } 1225 | image { 1226 | altText 1227 | one: transformedSrc(maxWidth: 64, maxHeight: 64) 1228 | two: transformedSrc(maxWidth: 128, maxHeight: 128) 1229 | four: transformedSrc(maxWidth: 256, maxHeight: 256) 1230 | __typename 1231 | } 1232 | properties { 1233 | ...MerchandiseProperties 1234 | __typename 1235 | } 1236 | requiresShipping 1237 | options { 1238 | name 1239 | value 1240 | __typename 1241 | } 1242 | sellingPlan { 1243 | name 1244 | id 1245 | digest 1246 | deliveriesPerBillingCycle 1247 | prepaid 1248 | subscriptionDetails { 1249 | billingInterval 1250 | billingIntervalCount 1251 | billingMaxCycles 1252 | deliveryInterval 1253 | deliveryIntervalCount 1254 | __typename 1255 | } 1256 | __typename 1257 | } 1258 | giftCard 1259 | deferredAmount { 1260 | amount 1261 | currencyCode 1262 | __typename 1263 | } 1264 | __typename 1265 | } 1266 | fragment LineAllocationDetails on LineAllocation { 1267 | stableId 1268 | quantity 1269 | totalAmountBeforeReductions { 1270 | amount 1271 | currencyCode 1272 | __typename 1273 | } 1274 | totalAmountAfterDiscounts { 1275 | amount 1276 | currencyCode 1277 | __typename 1278 | } 1279 | totalAmountAfterLineDiscounts { 1280 | amount 1281 | currencyCode 1282 | __typename 1283 | } 1284 | checkoutPriceAfterDiscounts { 1285 | amount 1286 | currencyCode 1287 | __typename 1288 | } 1289 | checkoutPriceBeforeReductions { 1290 | amount 1291 | currencyCode 1292 | __typename 1293 | } 1294 | unitPrice { 1295 | price { 1296 | amount 1297 | currencyCode 1298 | __typename 1299 | } 1300 | measurement { 1301 | referenceUnit 1302 | referenceValue 1303 | __typename 1304 | } 1305 | __typename 1306 | } 1307 | allocations { 1308 | ... on LineComponentDiscountAllocation { 1309 | discountLine { 1310 | discount { 1311 | ...DiscountDetailsFragment 1312 | __typename 1313 | } 1314 | lineAmount { 1315 | ... on MoneyValueConstraint { 1316 | value { 1317 | amount 1318 | currencyCode 1319 | __typename 1320 | } 1321 | __typename 1322 | } 1323 | __typename 1324 | } 1325 | __typename 1326 | } 1327 | allocation { 1328 | amount { 1329 | ... on MoneyValueConstraint { 1330 | value { 1331 | amount 1332 | currencyCode 1333 | __typename 1334 | } 1335 | __typename 1336 | } 1337 | __typename 1338 | } 1339 | __typename 1340 | } 1341 | amount { 1342 | amount 1343 | currencyCode 1344 | __typename 1345 | } 1346 | __typename 1347 | } 1348 | __typename 1349 | } 1350 | __typename 1351 | } 1352 | fragment MerchandiseBundleLineComponent on MerchandiseBundleLineComponent { 1353 | __typename 1354 | stableId 1355 | merchandise { 1356 | ...SourceProvidedMerchandise 1357 | ...ProductVariantMerchandiseDetails 1358 | ...ContextualizedProductVariantMerchandiseDetails 1359 | ... on MissingProductVariantMerchandise { 1360 | id 1361 | digest 1362 | variantId 1363 | __typename 1364 | } 1365 | __typename 1366 | } 1367 | quantity { 1368 | ... on ProposalMerchandiseQuantityByItem { 1369 | items { 1370 | ... on IntValueConstraint { 1371 | value 1372 | __typename 1373 | } 1374 | __typename 1375 | } 1376 | __typename 1377 | } 1378 | __typename 1379 | } 1380 | totalAmount { 1381 | ... on MoneyValueConstraint { 1382 | value { 1383 | amount 1384 | currencyCode 1385 | __typename 1386 | } 1387 | __typename 1388 | } 1389 | __typename 1390 | } 1391 | recurringTotal { 1392 | title 1393 | interval 1394 | intervalCount 1395 | recurringPrice { 1396 | amount 1397 | currencyCode 1398 | __typename 1399 | } 1400 | fixedPrice { 1401 | amount 1402 | currencyCode 1403 | __typename 1404 | } 1405 | fixedPriceCount 1406 | __typename 1407 | } 1408 | lineAllocations { 1409 | ...LineAllocationDetails 1410 | __typename 1411 | } 1412 | } 1413 | fragment ProposalDetails on Proposal { 1414 | merchandiseDiscount { 1415 | ...ProposalDiscountFragment 1416 | __typename 1417 | } 1418 | deliveryDiscount { 1419 | ...ProposalDiscountFragment 1420 | __typename 1421 | } 1422 | delivery { 1423 | ... on FilledDeliveryTerms { 1424 | intermediateRates 1425 | progressiveRatesEstimatedTimeUntilCompletion 1426 | shippingRatesStatusToken 1427 | deliveryLines { 1428 | destinationAddress { 1429 | ... on StreetAddress { 1430 | name 1431 | firstName 1432 | lastName 1433 | company 1434 | address1 1435 | address2 1436 | city 1437 | countryCode 1438 | zoneCode 1439 | postalCode 1440 | coordinates { 1441 | latitude 1442 | longitude 1443 | __typename 1444 | } 1445 | phone 1446 | __typename 1447 | } 1448 | ... on Geolocation { 1449 | country { 1450 | code 1451 | __typename 1452 | } 1453 | zone { 1454 | code 1455 | __typename 1456 | } 1457 | coordinates { 1458 | latitude 1459 | longitude 1460 | __typename 1461 | } 1462 | __typename 1463 | } 1464 | ... on PartialStreetAddress { 1465 | name 1466 | firstName 1467 | lastName 1468 | company 1469 | address1 1470 | address2 1471 | city 1472 | countryCode 1473 | zoneCode 1474 | postalCode 1475 | phone 1476 | coordinates { 1477 | latitude 1478 | longitude 1479 | __typename 1480 | } 1481 | __typename 1482 | } 1483 | __typename 1484 | } 1485 | targetMerchandise { 1486 | ... on FilledMerchandiseLineTargetCollection { 1487 | lines { 1488 | stableId 1489 | merchandise { 1490 | ...SourceProvidedMerchandise 1491 | ... on ProductVariantMerchandise { 1492 | id 1493 | digest 1494 | variantId 1495 | title 1496 | requiresShipping 1497 | properties { 1498 | ...MerchandiseProperties 1499 | __typename 1500 | } 1501 | __typename 1502 | } 1503 | ... on ContextualizedProductVariantMerchandise { 1504 | id 1505 | digest 1506 | variantId 1507 | title 1508 | requiresShipping 1509 | sellingPlan { 1510 | id 1511 | digest 1512 | name 1513 | prepaid 1514 | deliveriesPerBillingCycle 1515 | subscriptionDetails { 1516 | billingInterval 1517 | billingIntervalCount 1518 | billingMaxCycles 1519 | deliveryInterval 1520 | deliveryIntervalCount 1521 | __typename 1522 | } 1523 | __typename 1524 | } 1525 | properties { 1526 | ...MerchandiseProperties 1527 | __typename 1528 | } 1529 | __typename 1530 | } 1531 | ... on MissingProductVariantMerchandise { 1532 | id 1533 | digest 1534 | variantId 1535 | __typename 1536 | } 1537 | __typename 1538 | } 1539 | totalAmount { 1540 | ... on MoneyValueConstraint { 1541 | value { 1542 | amount 1543 | currencyCode 1544 | __typename 1545 | } 1546 | __typename 1547 | } 1548 | __typename 1549 | } 1550 | __typename 1551 | } 1552 | __typename 1553 | } 1554 | __typename 1555 | } 1556 | groupType 1557 | selectedDeliveryStrategy { 1558 | ... on CompleteDeliveryStrategy { 1559 | handle 1560 | __typename 1561 | } 1562 | __typename 1563 | } 1564 | availableDeliveryStrategies { 1565 | ... on CompleteDeliveryStrategy { 1566 | originLocation { 1567 | id 1568 | __typename 1569 | } 1570 | title 1571 | handle 1572 | custom 1573 | description 1574 | acceptsInstructions 1575 | phoneRequired 1576 | methodType 1577 | carrierName 1578 | brandedPromise { 1579 | logoUrl 1580 | name 1581 | __typename 1582 | } 1583 | deliveryStrategyBreakdown { 1584 | amount { 1585 | ... on MoneyValueConstraint { 1586 | value { 1587 | amount 1588 | currencyCode 1589 | __typename 1590 | } 1591 | __typename 1592 | } 1593 | __typename 1594 | } 1595 | discountRecurringCycleLimit 1596 | excludeFromDeliveryOptionPrice 1597 | targetMerchandise { 1598 | ... on FilledMerchandiseLineTargetCollection { 1599 | lines { 1600 | stableId 1601 | merchandise { 1602 | ...SourceProvidedMerchandise 1603 | ... on ProductVariantMerchandise { 1604 | id 1605 | digest 1606 | variantId 1607 | title 1608 | requiresShipping 1609 | properties { 1610 | ...MerchandiseProperties 1611 | __typename 1612 | } 1613 | __typename 1614 | } 1615 | ... on ContextualizedProductVariantMerchandise { 1616 | id 1617 | digest 1618 | variantId 1619 | title 1620 | subtitle 1621 | requiresShipping 1622 | sellingPlan { 1623 | id 1624 | digest 1625 | name 1626 | prepaid 1627 | deliveriesPerBillingCycle 1628 | subscriptionDetails { 1629 | billingInterval 1630 | billingIntervalCount 1631 | billingMaxCycles 1632 | deliveryInterval 1633 | deliveryIntervalCount 1634 | __typename 1635 | } 1636 | __typename 1637 | } 1638 | properties { 1639 | ...MerchandiseProperties 1640 | __typename 1641 | } 1642 | __typename 1643 | } 1644 | ... on MissingProductVariantMerchandise { 1645 | id 1646 | digest 1647 | variantId 1648 | __typename 1649 | } 1650 | __typename 1651 | } 1652 | totalAmount { 1653 | ... on MoneyValueConstraint { 1654 | value { 1655 | amount 1656 | currencyCode 1657 | __typename 1658 | } 1659 | __typename 1660 | } 1661 | __typename 1662 | } 1663 | __typename 1664 | } 1665 | __typename 1666 | } 1667 | __typename 1668 | } 1669 | __typename 1670 | } 1671 | minDeliveryDateTime 1672 | maxDeliveryDateTime 1673 | deliveryPromisePresentmentTitle { 1674 | short 1675 | long 1676 | __typename 1677 | } 1678 | displayCheckoutRedesign 1679 | estimatedTimeInTransit { 1680 | ... on IntIntervalConstraint { 1681 | lowerBound 1682 | upperBound 1683 | __typename 1684 | } 1685 | ... on IntValueConstraint { 1686 | value 1687 | __typename 1688 | } 1689 | __typename 1690 | } 1691 | amount { 1692 | ... on MoneyValueConstraint { 1693 | value { 1694 | amount 1695 | currencyCode 1696 | __typename 1697 | } 1698 | __typename 1699 | } 1700 | __typename 1701 | } 1702 | amountAfterDiscounts { 1703 | ... on MoneyValueConstraint { 1704 | value { 1705 | amount 1706 | currencyCode 1707 | __typename 1708 | } 1709 | __typename 1710 | } 1711 | __typename 1712 | } 1713 | pickupLocation { 1714 | ... on PickupInStoreLocation { 1715 | address { 1716 | address1 1717 | address2 1718 | city 1719 | countryCode 1720 | phone 1721 | postalCode 1722 | zoneCode 1723 | __typename 1724 | } 1725 | instructions 1726 | name 1727 | distanceFromBuyer { 1728 | unit 1729 | value 1730 | __typename 1731 | } 1732 | __typename 1733 | } 1734 | ... on PickupPointLocation { 1735 | address { 1736 | address1 1737 | address2 1738 | address3 1739 | city 1740 | countryCode 1741 | zoneCode 1742 | postalCode 1743 | coordinates { 1744 | latitude 1745 | longitude 1746 | __typename 1747 | } 1748 | __typename 1749 | } 1750 | businessHours { 1751 | day 1752 | openingTime 1753 | closingTime 1754 | __typename 1755 | } 1756 | carrierCode 1757 | carrierName 1758 | handle 1759 | kind 1760 | name 1761 | __typename 1762 | } 1763 | __typename 1764 | } 1765 | __typename 1766 | } 1767 | __typename 1768 | } 1769 | __typename 1770 | } 1771 | __typename 1772 | } 1773 | ... on PendingTerms { 1774 | pollDelay 1775 | taskId 1776 | __typename 1777 | } 1778 | ... on UnavailableTerms { 1779 | __typename 1780 | } 1781 | __typename 1782 | } 1783 | payment { 1784 | ... on FilledPaymentTerms { 1785 | availablePayments { 1786 | paymentMethod { 1787 | ... on AnyDirectPaymentMethod { 1788 | __typename 1789 | availablePaymentProviders { 1790 | paymentMethodIdentifier 1791 | name 1792 | brands 1793 | orderingIndex 1794 | displayName 1795 | __typename 1796 | } 1797 | } 1798 | ... on AnyOffsitePaymentMethod { 1799 | __typename 1800 | availableOffsiteProviders { 1801 | __typename 1802 | paymentMethodIdentifier 1803 | name 1804 | paymentBrands 1805 | orderingIndex 1806 | } 1807 | } 1808 | ... on DirectPaymentMethod { 1809 | __typename 1810 | paymentMethodIdentifier 1811 | } 1812 | ... on GiftCardPaymentMethod { 1813 | __typename 1814 | } 1815 | ... on AnyRedeemablePaymentMethod { 1816 | __typename 1817 | availableRedemptionSources 1818 | orderingIndex 1819 | } 1820 | ... on WalletsPlatformConfiguration { 1821 | name 1822 | configurationParams 1823 | __typename 1824 | } 1825 | ... on AnyWalletPaymentMethod { 1826 | availableWalletPaymentConfigs { 1827 | ... on PaypalWalletConfig { 1828 | __typename 1829 | name 1830 | merchantId 1831 | venmoEnabled 1832 | payflow 1833 | paymentIntent 1834 | paymentMethodIdentifier 1835 | orderingIndex 1836 | } 1837 | ... on ShopPayWalletConfig { 1838 | __typename 1839 | name 1840 | storefrontUrl 1841 | paymentMethodIdentifier 1842 | orderingIndex 1843 | } 1844 | ... on ShopifyInstallmentsWalletConfig { 1845 | __typename 1846 | name 1847 | availableLoanTypes 1848 | maxPrice { 1849 | amount 1850 | currencyCode 1851 | __typename 1852 | } 1853 | minPrice { 1854 | amount 1855 | currencyCode 1856 | __typename 1857 | } 1858 | supportedCountries 1859 | supportedCurrencies 1860 | giftCardsNotAllowed 1861 | subscriptionItemsNotAllowed 1862 | ineligibleTestModeCheckout 1863 | ineligibleLineItem 1864 | paymentMethodIdentifier 1865 | orderingIndex 1866 | } 1867 | ... on FacebookPayWalletConfig { 1868 | __typename 1869 | name 1870 | partnerId 1871 | partnerMerchantId 1872 | supportedContainers 1873 | acquirerCountryCode 1874 | mode 1875 | paymentMethodIdentifier 1876 | orderingIndex 1877 | } 1878 | ... on ApplePayWalletConfig { 1879 | __typename 1880 | name 1881 | supportedNetworks 1882 | walletAuthenticationToken 1883 | walletOrderTypeIdentifier 1884 | walletServiceUrl 1885 | paymentMethodIdentifier 1886 | orderingIndex 1887 | } 1888 | ... on GooglePayWalletConfig { 1889 | __typename 1890 | name 1891 | allowedAuthMethods 1892 | allowedCardNetworks 1893 | gateway 1894 | gatewayMerchantId 1895 | merchantId 1896 | authJwt 1897 | environment 1898 | paymentMethodIdentifier 1899 | orderingIndex 1900 | } 1901 | ... on AmazonPayClassicWalletConfig { 1902 | __typename 1903 | name 1904 | orderingIndex 1905 | } 1906 | __typename 1907 | } 1908 | __typename 1909 | } 1910 | ... on LocalPaymentMethodConfig { 1911 | __typename 1912 | paymentMethodIdentifier 1913 | name 1914 | displayName 1915 | additionalParameters { 1916 | ... on IdealBankSelectionParameterConfig { 1917 | __typename 1918 | label 1919 | options { 1920 | label 1921 | value 1922 | __typename 1923 | } 1924 | } 1925 | __typename 1926 | } 1927 | orderingIndex 1928 | } 1929 | ... on AnyPaymentOnDeliveryMethod { 1930 | __typename 1931 | additionalDetails 1932 | paymentInstructions 1933 | paymentMethodIdentifier 1934 | orderingIndex 1935 | displayName 1936 | } 1937 | ... on PaymentOnDeliveryMethod { 1938 | __typename 1939 | additionalDetails 1940 | paymentInstructions 1941 | paymentMethodIdentifier 1942 | } 1943 | ... on CustomPaymentMethod { 1944 | id 1945 | name 1946 | additionalDetails 1947 | paymentInstructions 1948 | __typename 1949 | } 1950 | ... on ManualPaymentMethodConfig { 1951 | id 1952 | name 1953 | additionalDetails 1954 | paymentInstructions 1955 | paymentMethodIdentifier 1956 | orderingIndex 1957 | __typename 1958 | } 1959 | ... on CustomPaymentMethodConfig { 1960 | id 1961 | name 1962 | additionalDetails 1963 | paymentInstructions 1964 | paymentMethodIdentifier 1965 | orderingIndex 1966 | __typename 1967 | } 1968 | ... on DeferredPaymentMethod { 1969 | orderingIndex 1970 | displayName 1971 | __typename 1972 | } 1973 | ... on NoopPaymentMethod { 1974 | __typename 1975 | } 1976 | ... on GiftCardPaymentMethod { 1977 | __typename 1978 | } 1979 | ... on CustomerCreditCardPaymentMethod { 1980 | __typename 1981 | expired 1982 | expiryMonth 1983 | expiryYear 1984 | name 1985 | orderingIndex 1986 | ...CustomerCreditCardPaymentMethodFragment 1987 | } 1988 | ... on PaypalBillingAgreementPaymentMethod { 1989 | __typename 1990 | orderingIndex 1991 | paypalAccountEmail 1992 | ...PaypalBillingAgreementPaymentMethodFragment 1993 | } 1994 | __typename 1995 | } 1996 | __typename 1997 | } 1998 | paymentLines { 1999 | ...PaymentLines 2000 | __typename 2001 | } 2002 | billingAddress { 2003 | ... on StreetAddress { 2004 | firstName 2005 | lastName 2006 | company 2007 | address1 2008 | address2 2009 | city 2010 | countryCode 2011 | zoneCode 2012 | postalCode 2013 | phone 2014 | __typename 2015 | } 2016 | ... on InvalidBillingAddress { 2017 | __typename 2018 | } 2019 | __typename 2020 | } 2021 | paymentFlexibilityPaymentTermsTemplate { 2022 | id 2023 | translatedName 2024 | dueDate 2025 | dueInDays 2026 | __typename 2027 | } 2028 | __typename 2029 | } 2030 | ... on PendingTerms { 2031 | pollDelay 2032 | __typename 2033 | } 2034 | ... on UnavailableTerms { 2035 | __typename 2036 | } 2037 | __typename 2038 | } 2039 | merchandise { 2040 | ... on FilledMerchandiseTerms { 2041 | taxesIncluded 2042 | merchandiseLines { 2043 | stableId 2044 | merchandise { 2045 | ...SourceProvidedMerchandise 2046 | ...ProductVariantMerchandiseDetails 2047 | ...ContextualizedProductVariantMerchandiseDetails 2048 | ... on MissingProductVariantMerchandise { 2049 | id 2050 | digest 2051 | variantId 2052 | __typename 2053 | } 2054 | __typename 2055 | } 2056 | quantity { 2057 | ... on ProposalMerchandiseQuantityByItem { 2058 | items { 2059 | ... on IntValueConstraint { 2060 | value 2061 | __typename 2062 | } 2063 | __typename 2064 | } 2065 | __typename 2066 | } 2067 | __typename 2068 | } 2069 | totalAmount { 2070 | ... on MoneyValueConstraint { 2071 | value { 2072 | amount 2073 | currencyCode 2074 | __typename 2075 | } 2076 | __typename 2077 | } 2078 | __typename 2079 | } 2080 | recurringTotal { 2081 | title 2082 | interval 2083 | intervalCount 2084 | recurringPrice { 2085 | amount 2086 | currencyCode 2087 | __typename 2088 | } 2089 | fixedPrice { 2090 | amount 2091 | currencyCode 2092 | __typename 2093 | } 2094 | fixedPriceCount 2095 | __typename 2096 | } 2097 | lineAllocations { 2098 | ...LineAllocationDetails 2099 | __typename 2100 | } 2101 | lineComponents { 2102 | ...MerchandiseBundleLineComponent 2103 | __typename 2104 | } 2105 | __typename 2106 | } 2107 | __typename 2108 | } 2109 | __typename 2110 | } 2111 | note { 2112 | customAttributes { 2113 | key 2114 | value 2115 | __typename 2116 | } 2117 | message 2118 | __typename 2119 | } 2120 | scriptFingerprint { 2121 | signature 2122 | signatureUuid 2123 | lineItemScriptChanges 2124 | paymentScriptChanges 2125 | shippingScriptChanges 2126 | __typename 2127 | } 2128 | transformerFingerprintV2 2129 | buyerIdentity { 2130 | ... on FilledBuyerIdentityTerms { 2131 | buyerIdentity { 2132 | ... on GuestProfile { 2133 | presentmentCurrency 2134 | countryCode 2135 | __typename 2136 | } 2137 | ... on CustomerProfile { 2138 | id 2139 | presentmentCurrency 2140 | fullName 2141 | firstName 2142 | lastName 2143 | countryCode 2144 | email 2145 | imageUrl 2146 | acceptsMarketing 2147 | phone 2148 | billingAddresses { 2149 | id 2150 | default 2151 | address { 2152 | firstName 2153 | lastName 2154 | address1 2155 | address2 2156 | phone 2157 | postalCode 2158 | city 2159 | company 2160 | zoneCode 2161 | countryCode 2162 | label 2163 | __typename 2164 | } 2165 | __typename 2166 | } 2167 | shippingAddresses { 2168 | id 2169 | default 2170 | address { 2171 | firstName 2172 | lastName 2173 | address1 2174 | address2 2175 | phone 2176 | postalCode 2177 | city 2178 | company 2179 | zoneCode 2180 | countryCode 2181 | label 2182 | __typename 2183 | } 2184 | __typename 2185 | } 2186 | __typename 2187 | } 2188 | ... on BusinessCustomerProfile { 2189 | checkoutExperienceConfiguration { 2190 | availablePaymentOptions 2191 | checkoutCompletionTarget 2192 | editableShippingAddress 2193 | __typename 2194 | } 2195 | id 2196 | presentmentCurrency 2197 | fullName 2198 | firstName 2199 | lastName 2200 | acceptsMarketing 2201 | companyName 2202 | countryCode 2203 | email 2204 | phone 2205 | selectedCompanyLocation { 2206 | id 2207 | name 2208 | __typename 2209 | } 2210 | locationCount 2211 | billingAddress { 2212 | firstName 2213 | lastName 2214 | address1 2215 | address2 2216 | phone 2217 | postalCode 2218 | city 2219 | company 2220 | zoneCode 2221 | countryCode 2222 | label 2223 | __typename 2224 | } 2225 | shippingAddress { 2226 | firstName 2227 | lastName 2228 | address1 2229 | address2 2230 | phone 2231 | postalCode 2232 | city 2233 | company 2234 | zoneCode 2235 | countryCode 2236 | label 2237 | __typename 2238 | } 2239 | __typename 2240 | } 2241 | __typename 2242 | } 2243 | contactInfoV2 { 2244 | ... on EmailFormContents { 2245 | email 2246 | __typename 2247 | } 2248 | ... on SMSFormContents { 2249 | phoneNumber 2250 | __typename 2251 | } 2252 | __typename 2253 | } 2254 | marketingConsent { 2255 | ... on SMSMarketingConsent { 2256 | value 2257 | __typename 2258 | } 2259 | ... on EmailMarketingConsent { 2260 | value 2261 | __typename 2262 | } 2263 | __typename 2264 | } 2265 | shopPayOptInPhone 2266 | __typename 2267 | } 2268 | __typename 2269 | } 2270 | recurringTotals { 2271 | title 2272 | interval 2273 | intervalCount 2274 | recurringPrice { 2275 | amount 2276 | currencyCode 2277 | __typename 2278 | } 2279 | fixedPrice { 2280 | amount 2281 | currencyCode 2282 | __typename 2283 | } 2284 | fixedPriceCount 2285 | __typename 2286 | } 2287 | subtotalBeforeTaxesAndShipping { 2288 | ... on MoneyValueConstraint { 2289 | value { 2290 | amount 2291 | currencyCode 2292 | __typename 2293 | } 2294 | __typename 2295 | } 2296 | __typename 2297 | } 2298 | runningTotal { 2299 | ... on MoneyValueConstraint { 2300 | value { 2301 | amount 2302 | currencyCode 2303 | __typename 2304 | } 2305 | __typename 2306 | } 2307 | __typename 2308 | } 2309 | total { 2310 | ... on MoneyValueConstraint { 2311 | value { 2312 | amount 2313 | currencyCode 2314 | __typename 2315 | } 2316 | __typename 2317 | } 2318 | __typename 2319 | } 2320 | checkoutTotalBeforeTaxesAndShipping { 2321 | ... on MoneyValueConstraint { 2322 | value { 2323 | amount 2324 | currencyCode 2325 | __typename 2326 | } 2327 | __typename 2328 | } 2329 | __typename 2330 | } 2331 | checkoutTotalTaxes { 2332 | ... on MoneyValueConstraint { 2333 | value { 2334 | amount 2335 | currencyCode 2336 | __typename 2337 | } 2338 | __typename 2339 | } 2340 | __typename 2341 | } 2342 | checkoutTotal { 2343 | ... on MoneyValueConstraint { 2344 | value { 2345 | amount 2346 | currencyCode 2347 | __typename 2348 | } 2349 | __typename 2350 | } 2351 | __typename 2352 | } 2353 | deferredTotal { 2354 | amount { 2355 | ... on MoneyValueConstraint { 2356 | value { 2357 | amount 2358 | currencyCode 2359 | __typename 2360 | } 2361 | __typename 2362 | } 2363 | __typename 2364 | } 2365 | subtotalAmount { 2366 | ... on MoneyValueConstraint { 2367 | value { 2368 | amount 2369 | currencyCode 2370 | __typename 2371 | } 2372 | __typename 2373 | } 2374 | __typename 2375 | } 2376 | taxes { 2377 | ... on MoneyValueConstraint { 2378 | value { 2379 | amount 2380 | currencyCode 2381 | __typename 2382 | } 2383 | __typename 2384 | } 2385 | __typename 2386 | } 2387 | dueAt 2388 | __typename 2389 | } 2390 | hasOnlyDeferredShipping 2391 | subtotalBeforeReductions { 2392 | ... on MoneyValueConstraint { 2393 | value { 2394 | amount 2395 | currencyCode 2396 | __typename 2397 | } 2398 | __typename 2399 | } 2400 | __typename 2401 | } 2402 | duty { 2403 | ... on FilledDutyTerms { 2404 | totalDutyAmount { 2405 | ... on MoneyValueConstraint { 2406 | value { 2407 | amount 2408 | currencyCode 2409 | __typename 2410 | } 2411 | __typename 2412 | } 2413 | __typename 2414 | } 2415 | totalTaxAndDutyAmount { 2416 | ... on MoneyValueConstraint { 2417 | value { 2418 | amount 2419 | currencyCode 2420 | __typename 2421 | } 2422 | __typename 2423 | } 2424 | __typename 2425 | } 2426 | __typename 2427 | } 2428 | ... on PendingTerms { 2429 | pollDelay 2430 | __typename 2431 | } 2432 | ... on UnavailableTerms { 2433 | __typename 2434 | } 2435 | __typename 2436 | } 2437 | tax { 2438 | ... on FilledTaxTerms { 2439 | totalTaxAmount { 2440 | ... on MoneyValueConstraint { 2441 | value { 2442 | amount 2443 | currencyCode 2444 | __typename 2445 | } 2446 | __typename 2447 | } 2448 | __typename 2449 | } 2450 | totalTaxAndDutyAmount { 2451 | ... on MoneyValueConstraint { 2452 | value { 2453 | amount 2454 | currencyCode 2455 | __typename 2456 | } 2457 | __typename 2458 | } 2459 | __typename 2460 | } 2461 | totalAmountIncludedInTarget { 2462 | ... on MoneyValueConstraint { 2463 | value { 2464 | amount 2465 | currencyCode 2466 | __typename 2467 | } 2468 | __typename 2469 | } 2470 | __typename 2471 | } 2472 | exemptions { 2473 | taxExemptionReason 2474 | targets { 2475 | ... on TargetAllLines { 2476 | __typename 2477 | } 2478 | __typename 2479 | } 2480 | __typename 2481 | } 2482 | __typename 2483 | } 2484 | ... on PendingTerms { 2485 | pollDelay 2486 | __typename 2487 | } 2488 | ... on UnavailableTerms { 2489 | __typename 2490 | } 2491 | __typename 2492 | } 2493 | tip { 2494 | tipSuggestions { 2495 | ... on TipSuggestion { 2496 | __typename 2497 | percentage 2498 | amount { 2499 | ... on MoneyValueConstraint { 2500 | value { 2501 | amount 2502 | currencyCode 2503 | __typename 2504 | } 2505 | __typename 2506 | } 2507 | __typename 2508 | } 2509 | } 2510 | __typename 2511 | } 2512 | terms { 2513 | ... on FilledTipTerms { 2514 | tipLines { 2515 | amount { 2516 | ... on MoneyValueConstraint { 2517 | value { 2518 | amount 2519 | currencyCode 2520 | __typename 2521 | } 2522 | __typename 2523 | } 2524 | __typename 2525 | } 2526 | __typename 2527 | } 2528 | __typename 2529 | } 2530 | __typename 2531 | } 2532 | __typename 2533 | } 2534 | localizationExtension { 2535 | ... on LocalizationExtension { 2536 | fields { 2537 | ... on LocalizationExtensionField { 2538 | key 2539 | title 2540 | value 2541 | __typename 2542 | } 2543 | __typename 2544 | } 2545 | __typename 2546 | } 2547 | __typename 2548 | } 2549 | landedCostDetails { 2550 | incotermInformation { 2551 | incoterm 2552 | reason 2553 | __typename 2554 | } 2555 | __typename 2556 | } 2557 | nonNegotiableTerms { 2558 | signature 2559 | contents { 2560 | signature 2561 | targetTerms 2562 | targetLine { 2563 | allLines 2564 | index 2565 | __typename 2566 | } 2567 | attributes 2568 | __typename 2569 | } 2570 | __typename 2571 | } 2572 | optionalDuties { 2573 | buyerRefusesDuties 2574 | refuseDutiesPermitted 2575 | __typename 2576 | } 2577 | attribution { 2578 | attributions { 2579 | ... on AttributionItem { 2580 | ... on RetailAttributions { 2581 | deviceId 2582 | locationId 2583 | userId 2584 | __typename 2585 | } 2586 | ... on DraftOrderAttributions { 2587 | userIdentifier: userId 2588 | sourceName 2589 | locationIdentifier: locationId 2590 | __typename 2591 | } 2592 | __typename 2593 | } 2594 | __typename 2595 | } 2596 | __typename 2597 | } 2598 | managedByMarketsPro 2599 | captcha { 2600 | ... on Captcha { 2601 | provider 2602 | challenge 2603 | sitekey 2604 | token 2605 | __typename 2606 | } 2607 | ... on PendingTerms { 2608 | taskId 2609 | pollDelay 2610 | __typename 2611 | } 2612 | __typename 2613 | } 2614 | __typename 2615 | } 2616 | fragment CustomerCreditCardPaymentMethodFragment on CustomerCreditCardPaymentMethod { 2617 | cvvSessionId 2618 | paymentMethodIdentifier 2619 | token 2620 | displayLastDigits 2621 | brand 2622 | billingAddress { 2623 | ... on StreetAddress { 2624 | address1 2625 | address2 2626 | city 2627 | company 2628 | countryCode 2629 | firstName 2630 | lastName 2631 | phone 2632 | postalCode 2633 | zoneCode 2634 | __typename 2635 | } 2636 | __typename 2637 | } 2638 | __typename 2639 | } 2640 | fragment PaypalBillingAgreementPaymentMethodFragment on PaypalBillingAgreementPaymentMethod { 2641 | paymentMethodIdentifier 2642 | token 2643 | billingAddress { 2644 | ... on StreetAddress { 2645 | address1 2646 | address2 2647 | city 2648 | company 2649 | countryCode 2650 | firstName 2651 | lastName 2652 | phone 2653 | postalCode 2654 | zoneCode 2655 | __typename 2656 | } 2657 | __typename 2658 | } 2659 | __typename 2660 | } 2661 | fragment PaymentLines on PaymentLine { 2662 | specialInstructions 2663 | amount { 2664 | ... on MoneyValueConstraint { 2665 | value { 2666 | amount 2667 | currencyCode 2668 | __typename 2669 | } 2670 | __typename 2671 | } 2672 | __typename 2673 | } 2674 | dueAt 2675 | paymentMethod { 2676 | ... on DirectPaymentMethod { 2677 | sessionId 2678 | paymentMethodIdentifier 2679 | creditCard { 2680 | ... on CreditCard { 2681 | brand 2682 | lastDigits 2683 | __typename 2684 | } 2685 | __typename 2686 | } 2687 | __typename 2688 | } 2689 | ... on GiftCardPaymentMethod { 2690 | code 2691 | balance { 2692 | amount 2693 | currencyCode 2694 | __typename 2695 | } 2696 | __typename 2697 | } 2698 | ... on RedeemablePaymentMethod { 2699 | redemptionSource 2700 | redemptionContent { 2701 | ... on ShopCashRedemptionContent { 2702 | billingAddress { 2703 | ... on StreetAddress { 2704 | firstName 2705 | lastName 2706 | company 2707 | address1 2708 | address2 2709 | city 2710 | countryCode 2711 | zoneCode 2712 | postalCode 2713 | phone 2714 | __typename 2715 | } 2716 | __typename 2717 | } 2718 | redemptionId 2719 | destinationAmount { 2720 | amount 2721 | currencyCode 2722 | __typename 2723 | } 2724 | sourceAmount { 2725 | amount 2726 | currencyCode 2727 | __typename 2728 | } 2729 | __typename 2730 | } 2731 | __typename 2732 | } 2733 | __typename 2734 | } 2735 | ... on WalletsPlatformPaymentMethod { 2736 | name 2737 | walletParams 2738 | __typename 2739 | } 2740 | ... on WalletPaymentMethod { 2741 | name 2742 | walletContent { 2743 | ... on ShopPayWalletContent { 2744 | billingAddress { 2745 | ... on StreetAddress { 2746 | firstName 2747 | lastName 2748 | company 2749 | address1 2750 | address2 2751 | city 2752 | countryCode 2753 | zoneCode 2754 | postalCode 2755 | phone 2756 | __typename 2757 | } 2758 | ... on InvalidBillingAddress { 2759 | __typename 2760 | } 2761 | __typename 2762 | } 2763 | sessionToken 2764 | paymentMethodIdentifier 2765 | __typename 2766 | } 2767 | ... on PaypalWalletContent { 2768 | paypalBillingAddress: billingAddress { 2769 | ... on StreetAddress { 2770 | firstName 2771 | lastName 2772 | company 2773 | address1 2774 | address2 2775 | city 2776 | countryCode 2777 | zoneCode 2778 | postalCode 2779 | phone 2780 | __typename 2781 | } 2782 | ... on InvalidBillingAddress { 2783 | __typename 2784 | } 2785 | __typename 2786 | } 2787 | email 2788 | payerId 2789 | token 2790 | paymentMethodIdentifier 2791 | acceptedSubscriptionTerms 2792 | expiresAt 2793 | __typename 2794 | } 2795 | ... on ApplePayWalletContent { 2796 | data 2797 | signature 2798 | version 2799 | lastDigits 2800 | paymentMethodIdentifier 2801 | __typename 2802 | } 2803 | ... on GooglePayWalletContent { 2804 | signature 2805 | signedMessage 2806 | protocolVersion 2807 | paymentMethodIdentifier 2808 | __typename 2809 | } 2810 | ... on FacebookPayWalletContent { 2811 | billingAddress { 2812 | ... on StreetAddress { 2813 | firstName 2814 | lastName 2815 | company 2816 | address1 2817 | address2 2818 | city 2819 | countryCode 2820 | zoneCode 2821 | postalCode 2822 | phone 2823 | __typename 2824 | } 2825 | ... on InvalidBillingAddress { 2826 | __typename 2827 | } 2828 | __typename 2829 | } 2830 | containerData 2831 | containerId 2832 | mode 2833 | paymentMethodIdentifier 2834 | __typename 2835 | } 2836 | ... on ShopifyInstallmentsWalletContent { 2837 | autoPayEnabled 2838 | billingAddress { 2839 | ... on StreetAddress { 2840 | firstName 2841 | lastName 2842 | company 2843 | address1 2844 | address2 2845 | city 2846 | countryCode 2847 | zoneCode 2848 | postalCode 2849 | phone 2850 | __typename 2851 | } 2852 | ... on InvalidBillingAddress { 2853 | __typename 2854 | } 2855 | __typename 2856 | } 2857 | disclosureDetails { 2858 | evidence 2859 | id 2860 | type 2861 | __typename 2862 | } 2863 | installmentsToken 2864 | sessionToken 2865 | paymentMethodIdentifier 2866 | __typename 2867 | } 2868 | __typename 2869 | } 2870 | __typename 2871 | } 2872 | ... on LocalPaymentMethod { 2873 | paymentMethodIdentifier 2874 | name 2875 | additionalParameters { 2876 | ... on IdealPaymentMethodParameters { 2877 | bank 2878 | __typename 2879 | } 2880 | __typename 2881 | } 2882 | __typename 2883 | } 2884 | ... on PaymentOnDeliveryMethod { 2885 | additionalDetails 2886 | paymentInstructions 2887 | paymentMethodIdentifier 2888 | __typename 2889 | } 2890 | ... on OffsitePaymentMethod { 2891 | paymentMethodIdentifier 2892 | name 2893 | __typename 2894 | } 2895 | ... on CustomPaymentMethod { 2896 | id 2897 | name 2898 | additionalDetails 2899 | paymentInstructions 2900 | paymentMethodIdentifier 2901 | __typename 2902 | } 2903 | ... on ManualPaymentMethod { 2904 | id 2905 | name 2906 | paymentMethodIdentifier 2907 | __typename 2908 | } 2909 | ... on DeferredPaymentMethod { 2910 | orderingIndex 2911 | displayName 2912 | __typename 2913 | } 2914 | ... on CustomerCreditCardPaymentMethod { 2915 | ...CustomerCreditCardPaymentMethodFragment 2916 | __typename 2917 | } 2918 | ... on PaypalBillingAgreementPaymentMethod { 2919 | ...PaypalBillingAgreementPaymentMethodFragment 2920 | __typename 2921 | } 2922 | ... on NoopPaymentMethod { 2923 | __typename 2924 | } 2925 | __typename 2926 | } 2927 | __typename 2928 | } 2929 | fragment ReceiptDetails on Receipt { 2930 | ... on ProcessedReceipt { 2931 | id 2932 | token 2933 | classicThankYouPageUrl 2934 | orderIdentity { 2935 | buyerIdentifier 2936 | id 2937 | __typename 2938 | } 2939 | shopPayArtifact { 2940 | optIn { 2941 | vaultPhone 2942 | __typename 2943 | } 2944 | __typename 2945 | } 2946 | eligibleForMarketingOptIn 2947 | purchaseOrder { 2948 | ...ReceiptPurchaseOrder 2949 | __typename 2950 | } 2951 | orderCreationStatus { 2952 | __typename 2953 | } 2954 | paymentDetails { 2955 | creditCardBrand 2956 | creditCardLastFourDigits 2957 | __typename 2958 | } 2959 | shopAppLinksAndResources { 2960 | mobileUrl 2961 | qrCodeUrl 2962 | canTrackOrderUpdates 2963 | shopInstallmentsViewSchedules 2964 | shopInstallmentsMobileUrl 2965 | installmentsHighlightEligible 2966 | mobileUrlAttributionPayload 2967 | shopAppEligible 2968 | shopAppQrCodeKillswitch 2969 | shopPayOrder 2970 | buyerHasShopApp 2971 | buyerHasShopPay 2972 | orderUpdateOptions 2973 | __typename 2974 | } 2975 | postPurchasePageRequested 2976 | postPurchaseVaultedPaymentMethodStatus 2977 | __typename 2978 | } 2979 | ... on ProcessingReceipt { 2980 | id 2981 | pollDelay 2982 | __typename 2983 | } 2984 | ... on ActionRequiredReceipt { 2985 | id 2986 | action { 2987 | ... on CompletePaymentChallenge { 2988 | offsiteRedirect 2989 | url 2990 | __typename 2991 | } 2992 | __typename 2993 | } 2994 | __typename 2995 | } 2996 | ... on FailedReceipt { 2997 | id 2998 | processingError { 2999 | ... on InventoryClaimFailure { 3000 | __typename 3001 | } 3002 | ... on InventoryReservationFailure { 3003 | __typename 3004 | } 3005 | ... on OrderCreationFailure { 3006 | paymentsHaveBeenReverted 3007 | __typename 3008 | } 3009 | ... on OrderCreationSchedulingFailure { 3010 | __typename 3011 | } 3012 | ... on PaymentFailed { 3013 | code 3014 | messageUntranslated 3015 | __typename 3016 | } 3017 | ... on DiscountUsageLimitExceededFailure { 3018 | __typename 3019 | } 3020 | ... on CustomerPersistenceFailure { 3021 | __typename 3022 | } 3023 | __typename 3024 | } 3025 | __typename 3026 | } 3027 | __typename 3028 | } 3029 | fragment ReceiptPurchaseOrder on PurchaseOrder { 3030 | __typename 3031 | sessionToken 3032 | totalAmountToPay { 3033 | amount 3034 | currencyCode 3035 | __typename 3036 | } 3037 | delivery { 3038 | ... on PurchaseOrderDeliveryTerms { 3039 | deliveryLines { 3040 | __typename 3041 | deliveryStrategy { 3042 | handle 3043 | title 3044 | description 3045 | methodType 3046 | pickupLocation { 3047 | ... on PickupInStoreLocation { 3048 | name 3049 | address { 3050 | address1 3051 | address2 3052 | city 3053 | countryCode 3054 | zoneCode 3055 | postalCode 3056 | phone 3057 | coordinates { 3058 | latitude 3059 | longitude 3060 | __typename 3061 | } 3062 | __typename 3063 | } 3064 | instructions 3065 | __typename 3066 | } 3067 | __typename 3068 | } 3069 | __typename 3070 | } 3071 | lineAmount { 3072 | amount 3073 | currencyCode 3074 | __typename 3075 | } 3076 | destinationAddress { 3077 | ... on StreetAddress { 3078 | name 3079 | firstName 3080 | lastName 3081 | company 3082 | address1 3083 | address2 3084 | city 3085 | countryCode 3086 | zoneCode 3087 | postalCode 3088 | coordinates { 3089 | latitude 3090 | longitude 3091 | __typename 3092 | } 3093 | phone 3094 | __typename 3095 | } 3096 | __typename 3097 | } 3098 | groupType 3099 | } 3100 | __typename 3101 | } 3102 | __typename 3103 | } 3104 | payment { 3105 | ... on PurchaseOrderPaymentTerms { 3106 | billingAddress { 3107 | __typename 3108 | ... on StreetAddress { 3109 | name 3110 | firstName 3111 | lastName 3112 | company 3113 | address1 3114 | address2 3115 | city 3116 | countryCode 3117 | zoneCode 3118 | postalCode 3119 | coordinates { 3120 | latitude 3121 | longitude 3122 | __typename 3123 | } 3124 | phone 3125 | __typename 3126 | } 3127 | ... on InvalidBillingAddress { 3128 | __typename 3129 | } 3130 | } 3131 | paymentLines { 3132 | amount { 3133 | amount 3134 | currencyCode 3135 | __typename 3136 | } 3137 | postPaymentMessage 3138 | dueAt 3139 | paymentMethod { 3140 | ... on DirectPaymentMethod { 3141 | sessionId 3142 | paymentMethodIdentifier 3143 | vaultingAgreement 3144 | creditCard { 3145 | brand 3146 | lastDigits 3147 | __typename 3148 | } 3149 | billingAddress { 3150 | ... on StreetAddress { 3151 | name 3152 | firstName 3153 | lastName 3154 | company 3155 | address1 3156 | address2 3157 | city 3158 | countryCode 3159 | zoneCode 3160 | postalCode 3161 | coordinates { 3162 | latitude 3163 | longitude 3164 | __typename 3165 | } 3166 | phone 3167 | __typename 3168 | } 3169 | ... on InvalidBillingAddress { 3170 | __typename 3171 | } 3172 | __typename 3173 | } 3174 | __typename 3175 | } 3176 | ... on CustomerCreditCardPaymentMethod { 3177 | brand 3178 | displayLastDigits 3179 | token 3180 | billingAddress { 3181 | ... on StreetAddress { 3182 | address1 3183 | address2 3184 | city 3185 | company 3186 | countryCode 3187 | firstName 3188 | lastName 3189 | phone 3190 | postalCode 3191 | zoneCode 3192 | __typename 3193 | } 3194 | __typename 3195 | } 3196 | __typename 3197 | } 3198 | ... on PurchaseOrderGiftCardPaymentMethod { 3199 | code 3200 | __typename 3201 | } 3202 | ... on WalletPaymentMethod { 3203 | name 3204 | walletContent { 3205 | ... on ShopPayWalletContent { 3206 | billingAddress { 3207 | ... on StreetAddress { 3208 | firstName 3209 | lastName 3210 | company 3211 | address1 3212 | address2 3213 | city 3214 | countryCode 3215 | zoneCode 3216 | postalCode 3217 | phone 3218 | __typename 3219 | } 3220 | ... on InvalidBillingAddress { 3221 | __typename 3222 | } 3223 | __typename 3224 | } 3225 | sessionToken 3226 | paymentMethodIdentifier 3227 | __typename 3228 | } 3229 | ... on PaypalWalletContent { 3230 | billingAddress { 3231 | ... on StreetAddress { 3232 | firstName 3233 | lastName 3234 | company 3235 | address1 3236 | address2 3237 | city 3238 | countryCode 3239 | zoneCode 3240 | postalCode 3241 | phone 3242 | __typename 3243 | } 3244 | ... on InvalidBillingAddress { 3245 | __typename 3246 | } 3247 | __typename 3248 | } 3249 | email 3250 | payerId 3251 | token 3252 | expiresAt 3253 | __typename 3254 | } 3255 | ... on ApplePayWalletContent { 3256 | billingAddress { 3257 | ... on StreetAddress { 3258 | firstName 3259 | lastName 3260 | company 3261 | address1 3262 | address2 3263 | city 3264 | countryCode 3265 | zoneCode 3266 | postalCode 3267 | phone 3268 | __typename 3269 | } 3270 | ... on InvalidBillingAddress { 3271 | __typename 3272 | } 3273 | __typename 3274 | } 3275 | data 3276 | signature 3277 | version 3278 | __typename 3279 | } 3280 | ... on GooglePayWalletContent { 3281 | billingAddress { 3282 | ... on StreetAddress { 3283 | firstName 3284 | lastName 3285 | company 3286 | address1 3287 | address2 3288 | city 3289 | countryCode 3290 | zoneCode 3291 | postalCode 3292 | phone 3293 | __typename 3294 | } 3295 | ... on InvalidBillingAddress { 3296 | __typename 3297 | } 3298 | __typename 3299 | } 3300 | signature 3301 | signedMessage 3302 | protocolVersion 3303 | __typename 3304 | } 3305 | ... on FacebookPayWalletContent { 3306 | billingAddress { 3307 | ... on StreetAddress { 3308 | firstName 3309 | lastName 3310 | company 3311 | address1 3312 | address2 3313 | city 3314 | countryCode 3315 | zoneCode 3316 | postalCode 3317 | phone 3318 | __typename 3319 | } 3320 | ... on InvalidBillingAddress { 3321 | __typename 3322 | } 3323 | __typename 3324 | } 3325 | containerData 3326 | containerId 3327 | mode 3328 | __typename 3329 | } 3330 | ... on ShopifyInstallmentsWalletContent { 3331 | autoPayEnabled 3332 | billingAddress { 3333 | ... on StreetAddress { 3334 | firstName 3335 | lastName 3336 | company 3337 | address1 3338 | address2 3339 | city 3340 | countryCode 3341 | zoneCode 3342 | postalCode 3343 | phone 3344 | __typename 3345 | } 3346 | ... on InvalidBillingAddress { 3347 | __typename 3348 | } 3349 | __typename 3350 | } 3351 | disclosureDetails { 3352 | evidence 3353 | id 3354 | type 3355 | __typename 3356 | } 3357 | installmentsToken 3358 | sessionToken 3359 | __typename 3360 | } 3361 | __typename 3362 | } 3363 | __typename 3364 | } 3365 | ... on LocalPaymentMethod { 3366 | paymentMethodIdentifier 3367 | name 3368 | billingAddress { 3369 | ... on StreetAddress { 3370 | name 3371 | firstName 3372 | lastName 3373 | company 3374 | address1 3375 | address2 3376 | city 3377 | countryCode 3378 | zoneCode 3379 | postalCode 3380 | coordinates { 3381 | latitude 3382 | longitude 3383 | __typename 3384 | } 3385 | phone 3386 | __typename 3387 | } 3388 | ... on InvalidBillingAddress { 3389 | __typename 3390 | } 3391 | __typename 3392 | } 3393 | __typename 3394 | } 3395 | ... on PaymentOnDeliveryMethod { 3396 | additionalDetails 3397 | paymentInstructions 3398 | paymentMethodIdentifier 3399 | billingAddress { 3400 | ... on StreetAddress { 3401 | name 3402 | firstName 3403 | lastName 3404 | company 3405 | address1 3406 | address2 3407 | city 3408 | countryCode 3409 | zoneCode 3410 | postalCode 3411 | coordinates { 3412 | latitude 3413 | longitude 3414 | __typename 3415 | } 3416 | phone 3417 | __typename 3418 | } 3419 | ... on InvalidBillingAddress { 3420 | __typename 3421 | } 3422 | __typename 3423 | } 3424 | __typename 3425 | } 3426 | ... on OffsitePaymentMethod { 3427 | paymentMethodIdentifier 3428 | name 3429 | billingAddress { 3430 | ... on StreetAddress { 3431 | name 3432 | firstName 3433 | lastName 3434 | company 3435 | address1 3436 | address2 3437 | city 3438 | countryCode 3439 | zoneCode 3440 | postalCode 3441 | coordinates { 3442 | latitude 3443 | longitude 3444 | __typename 3445 | } 3446 | phone 3447 | __typename 3448 | } 3449 | ... on InvalidBillingAddress { 3450 | __typename 3451 | } 3452 | __typename 3453 | } 3454 | __typename 3455 | } 3456 | ... on ManualPaymentMethod { 3457 | additionalDetails 3458 | name 3459 | paymentInstructions 3460 | id 3461 | paymentMethodIdentifier 3462 | billingAddress { 3463 | ... on StreetAddress { 3464 | name 3465 | firstName 3466 | lastName 3467 | company 3468 | address1 3469 | address2 3470 | city 3471 | countryCode 3472 | zoneCode 3473 | postalCode 3474 | coordinates { 3475 | latitude 3476 | longitude 3477 | __typename 3478 | } 3479 | phone 3480 | __typename 3481 | } 3482 | ... on InvalidBillingAddress { 3483 | __typename 3484 | } 3485 | __typename 3486 | } 3487 | __typename 3488 | } 3489 | ... on CustomPaymentMethod { 3490 | additionalDetails 3491 | name 3492 | paymentInstructions 3493 | id 3494 | paymentMethodIdentifier 3495 | billingAddress { 3496 | ... on StreetAddress { 3497 | name 3498 | firstName 3499 | lastName 3500 | company 3501 | address1 3502 | address2 3503 | city 3504 | countryCode 3505 | zoneCode 3506 | postalCode 3507 | coordinates { 3508 | latitude 3509 | longitude 3510 | __typename 3511 | } 3512 | phone 3513 | __typename 3514 | } 3515 | ... on InvalidBillingAddress { 3516 | __typename 3517 | } 3518 | __typename 3519 | } 3520 | __typename 3521 | } 3522 | ... on DeferredPaymentMethod { 3523 | orderingIndex 3524 | displayName 3525 | __typename 3526 | } 3527 | ... on PaypalBillingAgreementPaymentMethod { 3528 | token 3529 | billingAddress { 3530 | ... on StreetAddress { 3531 | address1 3532 | address2 3533 | city 3534 | company 3535 | countryCode 3536 | firstName 3537 | lastName 3538 | phone 3539 | postalCode 3540 | zoneCode 3541 | __typename 3542 | } 3543 | __typename 3544 | } 3545 | __typename 3546 | } 3547 | __typename 3548 | } 3549 | __typename 3550 | } 3551 | __typename 3552 | } 3553 | __typename 3554 | } 3555 | buyerIdentity { 3556 | ... on PurchaseOrderBuyerIdentityTerms { 3557 | contactMethod { 3558 | ... on PurchaseOrderEmailContactMethod { 3559 | email 3560 | __typename 3561 | } 3562 | ... on PurchaseOrderSMSContactMethod { 3563 | phoneNumber 3564 | __typename 3565 | } 3566 | __typename 3567 | } 3568 | marketingConsent { 3569 | ... on PurchaseOrderEmailContactMethod { 3570 | email 3571 | __typename 3572 | } 3573 | ... on PurchaseOrderSMSContactMethod { 3574 | phoneNumber 3575 | __typename 3576 | } 3577 | __typename 3578 | } 3579 | __typename 3580 | } 3581 | __typename 3582 | } 3583 | merchandise { 3584 | merchandiseLines { 3585 | merchandise { 3586 | ...ProductVariantSnapshotMerchandiseDetails 3587 | __typename 3588 | } 3589 | __typename 3590 | } 3591 | __typename 3592 | } 3593 | tax { 3594 | totalTaxAmount { 3595 | amount 3596 | currencyCode 3597 | __typename 3598 | } 3599 | __typename 3600 | } 3601 | discounts { 3602 | lines { 3603 | deliveryAllocations { 3604 | amount { 3605 | amount 3606 | currencyCode 3607 | __typename 3608 | } 3609 | index 3610 | __typename 3611 | } 3612 | __typename 3613 | } 3614 | __typename 3615 | } 3616 | } 3617 | fragment ProductVariantSnapshotMerchandiseDetails on ProductVariantSnapshot { 3618 | variantId 3619 | options { 3620 | name 3621 | value 3622 | __typename 3623 | } 3624 | productTitle 3625 | title 3626 | sellingPlan { 3627 | name 3628 | id 3629 | digest 3630 | deliveriesPerBillingCycle 3631 | prepaid 3632 | subscriptionDetails { 3633 | billingInterval 3634 | billingIntervalCount 3635 | billingMaxCycles 3636 | deliveryInterval 3637 | deliveryIntervalCount 3638 | __typename 3639 | } 3640 | __typename 3641 | } 3642 | __typename 3643 | } 3644 | --------------------------------------------------------------------------------