├── .github ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE │ ├── BUG_REPORT.md │ └── FEATURE_REQUEST.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── Gemfile ├── LICENSE.txt ├── License.erb ├── README.md ├── Rakefile ├── codegen ├── Rakefile ├── lib │ ├── graphql_java_gen.rb │ └── graphql_java_gen │ │ ├── annotation.rb │ │ ├── reformatter.rb │ │ ├── scalar.rb │ │ ├── templates │ │ ├── APISchema.java.erb │ │ ├── Enum.java.erb │ │ ├── Input.java.erb │ │ ├── Interface.java.erb │ │ ├── Object.java.erb │ │ ├── Operations.java.erb │ │ ├── Query.java.erb │ │ ├── QueryDefinition.java.erb │ │ └── Responses.java.erb │ │ └── version.rb └── test │ ├── graphql_java_gen_test.rb │ ├── support │ └── schema.rb │ └── test_helper.rb ├── example-magento-generator.rb ├── graphql_java_gen.gemspec ├── schemas ├── magento-schema-2.3.0.json ├── magento-schema-2.3.1.json ├── magento-schema-2.3.2.json ├── magento-schema-2.3.3.json ├── magento-schema-2.3.4.json ├── magento-schema-2.3.5.json ├── magento-schema-2.4.0.json ├── magento-schema-2.4.1ee.json ├── magento-schema-2.4.2ee.json ├── magento-schema-2.4.3ee.json └── magento-schema-2.4.4ee.json └── support ├── .gitignore ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── graphql.java.gen.build.gradle └── src ├── main └── java │ └── com │ └── shopify │ └── graphql │ └── support │ ├── AbstractQuery.java │ ├── AbstractResponse.java │ ├── Arguments.java │ ├── CustomFieldInterface.java │ ├── CustomFieldQuery.java │ ├── CustomFieldQueryDefinition.java │ ├── Error.java │ ├── Fragment.java │ ├── ID.java │ ├── Input.java │ ├── InvalidGraphQLException.java │ ├── Node.java │ ├── Nullable.java │ ├── SchemaViolationError.java │ └── TopLevelResponse.java └── test └── java └── com └── shopify └── graphql └── support ├── AnnotationTest.java ├── Generated.java ├── GeneratedMinimal.java ├── IntegrationTest.java └── QueryTest.java /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Adobe 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 making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | 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 both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | 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 Grp-opensourceoffice@adobe.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 [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thanks for choosing to contribute! 4 | 5 | The following are a set of guidelines to follow when contributing to this project. 6 | 7 | ## Code Of Conduct 8 | 9 | This project adheres to the Adobe [code of conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. Please report unacceptable behavior to the team. 10 | 11 | ## Contributor License Agreement 12 | 13 | All third-party contributions to this project must be accompanied by a signed contributor license agreement. This gives Adobe permission to redistribute your contributions as part of the project. [Sign our CLA](http://opensource.adobe.com/cla.html). You only need to submit an Adobe CLA one time, so if you have submitted one previously, you are good to go! 14 | 15 | ## How to contribute 16 | 17 | New code contributions should be made primarily using GitHub pull requests. This involves creating a fork of the project in your personal space, adding your new code in a branch and triggering a pull request. 18 | 19 | See how to perform pull requests at https://help.github.com/articles/using-pull-requests. 20 | 21 | Please follow the [pull request template](PULL_REQUEST_TEMPLATE.md) when submitting a pull request! 22 | 23 | ## New Feature request 24 | Please follow the [feature template](ISSUE_TEMPLATE/FEATURE_REQUEST.md) to open new feature requests. 25 | 26 | 27 | ## Issues 28 | 29 | Please follow the [issue template](ISSUE_TEMPLATE/BUG_REPORT.md) to open new [issues](https://github.com/adobe/graphql-java-generator/issues) and join the conversations to provide feedback. 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/BUG_REPORT.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Issue report 3 | about: Having an issue? Please create a report to explain what it is about. 4 | 5 | --- 6 | 7 | ### Expected Behaviour 8 | 9 | ### Actual Behaviour 10 | 11 | ### Reproduce Scenario (including but not limited to) 12 | 13 | #### Steps to Reproduce 14 | 15 | #### Platform and Version 16 | 17 | #### Sample Code that illustrates the problem 18 | 19 | #### Logs taken while reproducing problem -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Do you have new ideas for this project? Please fill in a request. 4 | 5 | --- 6 | 7 | ### User Story 8 | 9 | 10 | 11 | ### Description & Motivation 12 | 13 | 14 | ### Deliverables 15 | 16 | 17 | 18 | 19 | 20 | ### Acceptance Criteria 21 | 22 | 23 | 24 | 25 | 26 | ### Verification Steps 27 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Description 4 | 5 | 6 | 7 | ## Related Issue 8 | 9 | 10 | 11 | 12 | 13 | 14 | ## Motivation and Context 15 | 16 | 17 | 18 | ## How Has This Been Tested? 19 | 20 | 21 | 22 | 23 | 24 | ## Screenshots (if appropriate): 25 | 26 | ## Types of changes 27 | 28 | 29 | 30 | - [ ] Bug fix (non-breaking change which fixes an issue) 31 | - [ ] New feature (non-breaking change which adds functionality) 32 | - [ ] Breaking change (fix or feature that would cause existing functionality to change) 33 | 34 | ## Checklist: 35 | 36 | 37 | 38 | 39 | - [ ] I have signed the [Adobe Open Source CLA](http://opensource.adobe.com/cla.html). 40 | - [ ] My change requires a change to the documentation. 41 | - [ ] I have updated the documentation accordingly. 42 | - [ ] I have read the **CONTRIBUTING** document. 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /Gemfile.lock 3 | /pkg/ 4 | /tmp/ 5 | .idea -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Shopify 4 | Copyright (c) 2019 Adobe 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /License.erb: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * 3 | * Copyright 2020 Adobe. All rights reserved. 4 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. You may obtain a copy 6 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software distributed under 9 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 10 | * OF ANY KIND, either express or implied. See the License for the specific language 11 | * governing permissions and limitations under the License. 12 | * 13 | ******************************************************************************/ 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GraphQL Java Generator 2 | 3 | This project generates Java data models, query builders and response classes based on a GraphQL schema. It uses the JSON response of a GraphQL introspection query as input, and generates all the necessary classes to build GraphQL queries and parse GraphQL JSON responses for the input schema. 4 | 5 | ## Installation 6 | 7 | The code generator requires ruby version 2.1 or later. It is recommended to use [bundler](http://bundler.io/) to install the code generators ruby package. 8 | 9 | If you only want to locally install the generator, make sure you have `ruby`, `gem` and `bundle(r)` installed on your system, then simply install the generator with: 10 | 11 | bundle exec rake install 12 | 13 | (you might have to use `sudo` to get this working). 14 | 15 | The generated code depends on the `com.shopify.graphql.support` java package available [here](support/src/main/java). We recommend that you simply copy/include these Java files to your project along with the generated files of your GraphQL schema. You should include these files with their existing Java package structure, because they are referenced like that in the generated Java files of your schema. 16 | 17 | An example project built with this generator is https://github.com/adobe/commerce-cif-magento-graphql where you can see the expected output project. 18 | 19 | *Note: on MacOS, one has to install bundle 1.14 to get it working, with `sudo gem install bundler -v 1.14`* 20 | 21 | ## Usage 22 | 23 | The input of the generator is the JSON response of a standard GraphQL introspection query. This requires that you can access the GraphQL HTTP endpoint of your running GraphQL server. You can easily get the JSON response with for example the [GraphiQL](https://github.com/graphql/graphiql) extension for the Chrome browser. Simply install the extension, set the endpoint to your GraphQL server, open the Chrome's inspector, and click "set endpoint" in GraphiQL. In the inspector, you can then simply copy the JSON response of the introspection query. 24 | 25 | To generate separate class files for each schema entity, use the `save_granular` command. In this case, you must provide the path to the target directory where the java files will be generated. This directoy MUST exist prior to generating the files, it is not automatically created. 26 | 27 | e.g. 28 | ```ruby 29 | require 'graphql_java_gen' 30 | require 'graphql_schema' 31 | require 'json' 32 | 33 | introspection_result = File.read("graphql_schema.json") 34 | schema = GraphQLSchema.new(JSON.parse(introspection_result)) 35 | 36 | GraphQLJavaGen.new(schema, 37 | package_name: "com.example.myapp", # The Java package of the generated classes 38 | license_header_file: "./License.erb", # The license header that will be added to all Java files 39 | nest_under: 'Schema', # Not used, but must be defined 40 | custom_scalars: [ 41 | GraphQLJavaGen::Scalar.new( 42 | type_name: 'Decimal', 43 | java_type: 'BigDecimal', 44 | deserialize_expr: ->(expr) { "new BigDecimal(jsonAsString(#{expr}, key))" }, 45 | imports: ['java.math.BigDecimal'], 46 | ), 47 | ] 48 | ).save_granular("#{Dir.pwd}/MyApp/src/main/java/com/example/myapp/") 49 | ``` 50 | 51 | When using the granular option, the `com.example.myapp` package will contain many small class files each containing a single GraphQL schema entity. 52 | 53 | You can try this out with the example generator configuration provided in this repository. It uses the Magento GraphQL schema available in the [schemas](schemas) subfolder. Once the generator is installed on your system, you can simply generate the Magento classes with 54 | 55 | ``` 56 | mkdir -p src/main/java/com/adobe/cq/commerce/magento/graphql 57 | ruby example-magento-generator.rb 58 | ``` 59 | 60 | *Note: It is possible to also generate one single monolithic java file with all the schema classes, but we do not recommend to do that.* 61 | 62 | ### Generated code 63 | 64 | The generated code includes query builders that can be used to create a GraphQL query in a type-safe manner. One can for example generate a simple query with: 65 | 66 | ```java 67 | String queryString = Operations.query(query -> query 68 | .user(user -> user 69 | .firstName() 70 | .lastName() 71 | ) 72 | ).toString(); 73 | ``` 74 | 75 | The generated code also includes response classes that will deserialize the response and provide methods for accessing the field data with it already coerced to the correct type. 76 | 77 | We recommend that you check the project at https://github.com/adobe/commerce-cif-magento-graphql where we also have unit tests that show how to parse and deserialise JSON responses. 78 | 79 | ### Deserialization 80 | 81 | The generated code uses the [Gson](https://github.com/google/gson) Java library to deserialize JSON responses. We recommend that you define a custom deserializer for each root type defined in your GraphQL schema, similar to what is done in the https://github.com/adobe/commerce-cif-magento-graphql project with the [QueryDeserializer](https://github.com/adobe/commerce-cif-magento-graphql/blob/master/src/main/java/com/adobe/cq/commerce/magento/graphql/gson/QueryDeserializer.java) class that handles the Magento `Query` root type. 82 | 83 | ## Lambda Expressions 84 | 85 | The generated Java files heavily rely on Java 8 lambda expressions. 86 | 87 | ## Development 88 | 89 | After checking out the repo, run `bundle` to install ruby dependencies. Then, run `rake test` to run the tests. 90 | 91 | To install this gem onto your local machine, run `bundle exec rake install` or reference it from a Gemfile using the path option (e.g. `gem 'graphql_java_gen', path: '~/src/graphql_java_gen'`). 92 | 93 | ### Contributing 94 | 95 | Contributions are welcomed! Read the [Contributing Guide](.github/CONTRIBUTING.md) for more information. 96 | 97 | ## License 98 | 99 | The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT). 100 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | 3 | task test: :generate do |t| 4 | Dir.chdir('codegen') do 5 | system('rake', 'test') || abort 6 | end 7 | Dir.chdir('support') do 8 | system('./gradlew', 'check') || abort 9 | end 10 | end 11 | 12 | task :generate do 13 | require 'graphql_schema' 14 | require 'graphql_java_gen' 15 | require_relative 'codegen/test/support/schema' 16 | 17 | GraphQLJavaGen.new( 18 | GraphQLSchema.new(Support::Schema.introspection_result), 19 | package_name: 'com.shopify.graphql.support', 20 | license_header_file: "./License.erb", 21 | nest_under: 'Generated', 22 | custom_scalars: [ 23 | GraphQLJavaGen::Scalar.new( 24 | type_name: 'Time', 25 | java_type: 'LocalDateTime', 26 | deserialize_expr: ->(expr) { "LocalDateTime.parse(jsonAsString(#{expr}, key))" }, 27 | imports: ['java.time.LocalDateTime'], 28 | ) 29 | ], 30 | custom_annotations: [ 31 | GraphQLJavaGen::Annotation.new( 32 | 'Nullable', 33 | imports: ['com.shopify.graphql.support.Nullable'] 34 | ) { |field| !field.type.non_null? }, 35 | ] 36 | ).save('support/src/test/java/com/shopify/graphql/support/Generated.java') 37 | 38 | GraphQLJavaGen.new( 39 | GraphQLSchema.new(Support::Schema.introspection_result(Support::Schema::MinimalSchema)), 40 | license_header_file: "./License.erb", 41 | package_name: 'com.shopify.graphql.support', 42 | nest_under: 'GeneratedMinimal', 43 | ).save('support/src/test/java/com/shopify/graphql/support/GeneratedMinimal.java') 44 | end 45 | 46 | task :default => :test 47 | -------------------------------------------------------------------------------- /codegen/Rakefile: -------------------------------------------------------------------------------- 1 | require "rake/testtask" 2 | 3 | Rake::TestTask.new(:test) do |t| 4 | t.libs << "test" 5 | t.libs << "lib" 6 | t.test_files = FileList['test/**/*_test.rb'] 7 | t.warning = false 8 | end 9 | 10 | task :default => :test 11 | -------------------------------------------------------------------------------- /codegen/lib/graphql_java_gen.rb: -------------------------------------------------------------------------------- 1 | require 'graphql_java_gen/version' 2 | require 'graphql_java_gen/reformatter' 3 | require 'graphql_java_gen/scalar' 4 | require 'graphql_java_gen/annotation' 5 | 6 | require 'erb' 7 | require 'set' 8 | 9 | class GraphQLJavaGen 10 | attr_reader :schema, :package_name, :scalars, :imports, :script_name, :schema_name, :include_deprecated 11 | 12 | def initialize(schema, 13 | package_name:, nest_under:, script_name: 'graphql_java_gen gem', 14 | custom_scalars: [], custom_annotations: [], include_deprecated: false, license_header_file: 15 | ) 16 | @schema = schema 17 | @schema_name = nest_under 18 | @script_name = script_name 19 | @package_name = package_name 20 | @scalars = (BUILTIN_SCALARS + custom_scalars).reduce({}) { |hash, scalar| hash[scalar.type_name] = scalar; hash } 21 | @scalars.default_proc = ->(hash, key) { DEFAULT_SCALAR } 22 | @annotations = custom_annotations 23 | @imports = (@scalars.values.map(&:imports) + @annotations.map(&:imports)).flatten.sort.uniq 24 | @include_deprecated = include_deprecated 25 | @license_header_file = license_header_file 26 | end 27 | 28 | def save(path) 29 | File.write(path, generate) 30 | end 31 | 32 | def save_granular(path) 33 | write_static_methods(path) 34 | write_response(path, :query, schema.query_root_name) 35 | write_response(path, :mutation, schema.mutation_root_name) 36 | write_entities(path) 37 | end 38 | 39 | def generate 40 | reformat(TEMPLATE_ERB.result(binding)) 41 | end 42 | 43 | private 44 | 45 | class << self 46 | private 47 | 48 | def erb_for(template_filename) 49 | erb = ERB.new(File.read(template_filename), nil, '-') 50 | erb.filename = template_filename 51 | erb 52 | end 53 | end 54 | 55 | TEMPLATE_ERB = erb_for(File.expand_path("../graphql_java_gen/templates/APISchema.java.erb", __FILE__)) 56 | private_constant :TEMPLATE_ERB 57 | 58 | def erb_for_entity(template) 59 | template_filename = File.expand_path("../graphql_java_gen/templates/#{template}.erb", __FILE__) 60 | erb = ERB.new(File.read(template_filename), nil, '-') 61 | erb.filename = template_filename 62 | erb 63 | end 64 | 65 | def generate_entity(template, type) 66 | erb_template = erb_for_entity(template) 67 | reformat(erb_template.result(binding)) 68 | end 69 | 70 | def write_static_methods(path) 71 | File.write(path + "/Operations.java", reformat(erb_for_entity("Operations.java").result(binding))) 72 | end 73 | 74 | def write_response(path, query, root_name) 75 | response_type = query.to_s.capitalize 76 | response = reformat(erb_for_entity("Responses.java").result(binding)) 77 | File.write(path + "/#{response_type}Response.java", response) 78 | end 79 | 80 | def write_entities(path) 81 | schema.types.reject{ |type| type.scalar? }.each do |type| 82 | case type.kind when 'OBJECT', 'INTERFACE', 'UNION' 83 | File.write(path + "/#{type.name}QueryDefinition.java", generate_entity("QueryDefinition.java", type)) 84 | File.write(path + "/#{type.name}Query.java", generate_entity("Query.java", type)) 85 | File.write(path + "/#{type.name}.java", generate_entity("Interface.java", type)) 86 | 87 | class_name = type.object? ? type.name : "Unknown#{type.name}" 88 | File.write(path + "/#{class_name}.java", generate_entity("Object.java", type)) 89 | when 'INPUT_OBJECT' 90 | File.write(path + "/#{type.name}.java", generate_entity("Input.java", type)) 91 | when 'ENUM' 92 | File.write(path + "/#{type.name}.java", generate_entity("Enum.java", type)) 93 | else 94 | raise NotImplementedError, "unhandled #{type.kind} type #{type.name}" 95 | end 96 | end 97 | end 98 | 99 | DEFAULT_SCALAR = Scalar.new( 100 | type_name: nil, 101 | java_type: 'String', 102 | deserialize_expr: ->(expr) { "jsonAsString(#{expr}, key)" }, 103 | ) 104 | private_constant :DEFAULT_SCALAR 105 | 106 | BUILTIN_SCALARS = [ 107 | Scalar.new( 108 | type_name: 'Int', 109 | java_type: 'int', 110 | deserialize_expr: ->(expr) { "jsonAsInteger(#{expr}, key)" }, 111 | ), 112 | Scalar.new( 113 | type_name: 'Float', 114 | java_type: 'double', 115 | deserialize_expr: ->(expr) { "jsonAsDouble(#{expr}, key)" }, 116 | ), 117 | Scalar.new( 118 | type_name: 'String', 119 | java_type: 'String', 120 | deserialize_expr: ->(expr) { "jsonAsString(#{expr}, key)" }, 121 | ), 122 | Scalar.new( 123 | type_name: 'Boolean', 124 | java_type: 'boolean', 125 | deserialize_expr: ->(expr) { "jsonAsBoolean(#{expr}, key)" }, 126 | ), 127 | Scalar.new( 128 | type_name: 'ID', 129 | java_type: 'ID', 130 | deserialize_expr: ->(expr) { "new ID(jsonAsString(#{expr}, key))" }, 131 | imports: ['com.shopify.graphql.support.ID'], 132 | ), 133 | ] 134 | private_constant :BUILTIN_SCALARS 135 | 136 | # From: http://docs.oracle.com/javase/tutorial/java/nutsandbolts/_keywords.html 137 | RESERVED_WORDS = [ 138 | "abstract", "assert", "boolean", "break", "byte", "case", "catch", "char", "class", "const", "continue", "default", "do", "double", "else", "enum", "extends", "final", "finally", "float", 139 | "for", "goto", "if", "implements", "import", "instanceof", "int", "interface", "long", "native", "new", "package", "private", "protected", "public", "return", "short", "static", "strictfp", "super", 140 | "switch", "synchronized", "this", "throw", "throws", "transient", "try", "void", "volatile", "while", "null" 141 | ] 142 | private_constant :RESERVED_WORDS 143 | 144 | def escape_reserved_word(word) 145 | return word unless RESERVED_WORDS.include?(word) 146 | "#{word}Value" 147 | end 148 | 149 | def reformat(code) 150 | Reformatter.new(indent: " " * 4).reformat(code) 151 | end 152 | 153 | def java_input_type(type, non_null: false) 154 | case type.kind 155 | when "NON_NULL" 156 | java_input_type(type.of_type, non_null: true) 157 | when "SCALAR" 158 | non_null ? scalars[type.name].non_nullable_type : scalars[type.name].nullable_type 159 | when 'LIST' 160 | "List<#{java_input_type(type.of_type.unwrap_non_null)}>" 161 | when 'INPUT_OBJECT', 'ENUM' 162 | type.name 163 | else 164 | raise NotImplementedError, "Unhandled #{type.kind} input type" 165 | end 166 | end 167 | 168 | def java_output_type(type) 169 | type = type.unwrap_non_null 170 | case type.kind 171 | when "SCALAR" 172 | scalars[type.name].nullable_type 173 | when 'LIST' 174 | "List<#{java_output_type(type.of_type)}>" 175 | when 'ENUM', 'OBJECT', 'INTERFACE', 'UNION' 176 | type.name 177 | else 178 | raise NotImplementedError, "Unhandled #{type.kind} response type" 179 | end 180 | end 181 | 182 | def generate_build_input_code(expr, type, depth: 1) 183 | type = type.unwrap_non_null 184 | case type.kind 185 | when 'SCALAR' 186 | if ['Int', 'Float', 'Boolean'].include?(type.name) 187 | "_queryBuilder.append(#{expr});" 188 | else 189 | "AbstractQuery.appendQuotedString(_queryBuilder, #{expr}.toString());" 190 | end 191 | when 'ENUM' 192 | "_queryBuilder.append(#{expr}.toString());" 193 | when 'LIST' 194 | item_type = type.of_type 195 | <<-JAVA 196 | _queryBuilder.append('['); 197 | { 198 | String listSeperator#{depth} = ""; 199 | for (#{java_input_type(item_type)} item#{depth} : #{expr}) { 200 | _queryBuilder.append(listSeperator#{depth}); 201 | listSeperator#{depth} = ","; 202 | #{generate_build_input_code("item#{depth}", item_type, depth: depth + 1)} 203 | } 204 | } 205 | _queryBuilder.append(']'); 206 | JAVA 207 | when 'INPUT_OBJECT' 208 | "#{expr}.appendTo(_queryBuilder);" 209 | else 210 | raise NotImplementedError, "Unexpected #{type.kind} argument type" 211 | end 212 | end 213 | 214 | def generate_build_output_code(expr, type, depth: 1, non_null: false, &block) 215 | if type.non_null? 216 | return generate_build_output_code(expr, type.of_type, depth: depth, non_null: true, &block) 217 | end 218 | 219 | statements = "" 220 | unless non_null 221 | optional_name = "optional#{depth}" 222 | generate_build_output_code(expr, type, depth: depth, non_null: true) do |item_statements, item_expr| 223 | statements = <<-JAVA 224 | #{java_output_type(type)} #{optional_name} = null; 225 | if (!#{expr}.isJsonNull()) { 226 | #{item_statements} 227 | #{optional_name} = #{item_expr}; 228 | } 229 | JAVA 230 | end 231 | return yield statements, optional_name 232 | end 233 | 234 | expr = case type.kind 235 | when 'SCALAR' 236 | scalars[type.name].deserialize(expr) 237 | when 'LIST' 238 | list_name = "list#{depth}" 239 | element_name = "element#{depth}" 240 | generate_build_output_code(element_name, type.of_type, depth: depth + 1) do |item_statements, item_expr| 241 | statements = <<-JAVA 242 | #{java_output_type(type)} #{list_name} = new ArrayList<>(); 243 | for (JsonElement #{element_name} : jsonAsArray(#{expr}, key)) { 244 | #{item_statements} 245 | #{list_name}.add(#{item_expr}); 246 | } 247 | JAVA 248 | end 249 | list_name 250 | when 'OBJECT' 251 | "new #{type.name}(jsonAsObject(#{expr}, key))" 252 | when 'INTERFACE', 'UNION' 253 | "Unknown#{type.name}.create(jsonAsObject(#{expr}, key))" 254 | when 'ENUM' 255 | "#{type.name}.fromGraphQl(jsonAsString(#{expr}, key))" 256 | else 257 | raise NotImplementedError, "Unexpected #{type.kind} argument type" 258 | end 259 | yield statements, expr 260 | end 261 | 262 | def java_arg_defs(field, skip_optional: false) 263 | defs = [] 264 | field.required_args.each do |arg| 265 | defs << "#{java_input_type(arg.type)} #{escape_reserved_word(arg.camelize_name)}" 266 | end 267 | unless field.optional_args.empty? || skip_optional 268 | defs << "#{field.classify_name}ArgumentsDefinition argsDef" 269 | end 270 | if field.subfields? 271 | defs << "#{field.type.unwrap.name}QueryDefinition queryDef" 272 | end 273 | defs.join(', ') 274 | end 275 | 276 | def java_required_arg_defs(field) 277 | defs = [] 278 | field.required_args.each do |arg| 279 | defs << "#{java_input_type(arg.type)} #{escape_reserved_word(arg.camelize_name)}" 280 | end 281 | unless field.optional_args.empty? 282 | defs << "#{field.classify_name}ArgumentsDefinition argsDef" 283 | end 284 | if field.subfields? 285 | defs << "#{field.type.unwrap.classify_name}QueryDefinition queryDef" 286 | end 287 | defs.join(', ') 288 | end 289 | 290 | def java_arg_expresions_with_empty_optional_args(field) 291 | expressions = field.required_args.map { |arg| escape_reserved_word(arg.camelize_name) } 292 | expressions << "args -> {}" 293 | if field.subfields? 294 | expressions << "queryDef" 295 | end 296 | expressions.join(', ') 297 | end 298 | 299 | def java_implements(type) 300 | return "implements #{type.name} " unless type.object? 301 | interfaces = abstract_types.fetch(type.name) 302 | return "" if interfaces.empty? 303 | "implements #{interfaces.to_a.join(', ')} " 304 | end 305 | 306 | def java_annotations(field, in_argument: false) 307 | annotations = @annotations.map do |annotation| 308 | "@#{annotation.name}" if annotation.annotate?(field) 309 | end.compact 310 | return "" unless annotations.any? 311 | 312 | if in_argument 313 | annotations.join(" ") + " " 314 | else 315 | annotations.join("\n") 316 | end 317 | end 318 | 319 | def type_names_set 320 | @type_names_set ||= schema.types.map(&:name).to_set 321 | end 322 | 323 | def abstract_types 324 | @abstract_types ||= schema.types.each_with_object({}) do |type, result| 325 | case type.kind 326 | when 'OBJECT' 327 | result[type.name] ||= Set.new 328 | when 'INTERFACE', 'UNION' 329 | type.possible_types.each do |possible_type| 330 | (result[possible_type.name] ||= Set.new).add(type.name) 331 | end 332 | end 333 | end 334 | end 335 | 336 | def java_doc(element) 337 | doc = '' 338 | unless element.description.nil? || element.description.empty? 339 | description = wrap_text(element.description, 100) 340 | description = description.chomp("\n").gsub("\n", "\n* ") 341 | doc << '* ' 342 | doc << description 343 | end 344 | 345 | if element.respond_to?(:deprecated?) && element.deprecated? 346 | unless doc.empty? 347 | doc << "\n*" 348 | doc << "\n*" 349 | else 350 | doc << '*' 351 | end 352 | doc << ' @deprecated ' 353 | doc << element.deprecation_reason 354 | end 355 | 356 | doc.empty? ? doc : "/**\n" + ERB::Util.html_escape(doc) + "\n*/" 357 | end 358 | 359 | def wrap_text(text, col_width=80) 360 | text.gsub!( /(\S{#{col_width}})(?=\S)/, '\1 ' ) 361 | text.gsub!( /(.{1,#{col_width}})(?:\s+|$)/, "\\1\n" ) 362 | text 363 | end 364 | 365 | def render_license 366 | content = File.read(File.expand_path(@license_header_file)) 367 | t = ERB.new(content) 368 | t.result(binding) 369 | end 370 | 371 | end 372 | -------------------------------------------------------------------------------- /codegen/lib/graphql_java_gen/annotation.rb: -------------------------------------------------------------------------------- 1 | class GraphQLJavaGen 2 | class Annotation 3 | attr_reader :name, :imports 4 | 5 | def initialize(name, imports: [], &block) 6 | @name = name 7 | @imports = imports 8 | @condition = block 9 | end 10 | 11 | def annotate?(field) 12 | @condition.call(field) 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /codegen/lib/graphql_java_gen/reformatter.rb: -------------------------------------------------------------------------------- 1 | class GraphQLJavaGen 2 | # Reformat code that uses curly brace blocks 3 | class Reformatter 4 | INDENT_START_CHARS = ["{", "("] 5 | INDENT_END_CHARS = ["}", ")"] 6 | 7 | def initialize(indent: "\t") 8 | @indent = indent 9 | end 10 | 11 | def reformat(code) 12 | output = "" 13 | indent_level = 0 14 | squeeze_newlines = true 15 | javadoc = false 16 | 17 | code.lines.each do |line| 18 | stripped_line = line.strip 19 | 20 | if INDENT_END_CHARS.include?(stripped_line[0]) 21 | indent_level -= 1 22 | # no blank lines immediately preceding end of block 23 | output.rstrip! 24 | output << "\n" 25 | end 26 | 27 | if stripped_line.empty? 28 | output << "\n" unless squeeze_newlines 29 | squeeze_newlines = true 30 | elsif javadoc 31 | output << @indent * indent_level + " " << line.lstrip 32 | squeeze_newlines = false 33 | else 34 | output << @indent * indent_level << line.lstrip 35 | squeeze_newlines = false 36 | end 37 | 38 | if INDENT_START_CHARS.include?(stripped_line[-1]) 39 | indent_level += 1 40 | # no blank lines following start of block 41 | squeeze_newlines = true 42 | end 43 | 44 | if !javadoc && stripped_line.start_with?("/**") 45 | javadoc = true 46 | elsif javadoc && stripped_line.end_with?("*/") 47 | javadoc = false; 48 | end 49 | end 50 | output 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /codegen/lib/graphql_java_gen/scalar.rb: -------------------------------------------------------------------------------- 1 | class GraphQLJavaGen 2 | class Scalar 3 | attr_reader :type_name, :imports 4 | 5 | WRAPPER_OBJECT = { 6 | 'int' => 'Integer', 7 | 'long' => 'Long', 8 | 'double' => 'Double', 9 | 'boolean' => 'Boolean', 10 | } 11 | WRAPPER_OBJECT.default_proc = ->(_, key) { key } 12 | private_constant :WRAPPER_OBJECT 13 | 14 | def initialize(type_name:, java_type:, deserialize_expr:, imports: []) 15 | @type_name = type_name 16 | @java_type = java_type 17 | @deserialize_expr = deserialize_expr 18 | @imports = imports 19 | end 20 | 21 | def nullable_type 22 | WRAPPER_OBJECT[@java_type] 23 | end 24 | 25 | def non_nullable_type 26 | @java_type 27 | end 28 | 29 | def deserialize(expr) 30 | @deserialize_expr.call(expr) 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /codegen/lib/graphql_java_gen/templates/APISchema.java.erb: -------------------------------------------------------------------------------- 1 | <%= render_license %> 2 | 3 | package <%= package_name %>; 4 | 5 | import com.google.gson.Gson; 6 | import com.google.gson.GsonBuilder; 7 | import com.google.gson.JsonElement; 8 | import com.google.gson.JsonObject; 9 | import com.shopify.graphql.support.AbstractResponse; 10 | import com.shopify.graphql.support.Arguments; 11 | import com.shopify.graphql.support.Error; 12 | import com.shopify.graphql.support.AbstractQuery; 13 | import com.shopify.graphql.support.SchemaViolationError; 14 | import com.shopify.graphql.support.TopLevelResponse; 15 | import com.shopify.graphql.support.Input; 16 | <% imports.each do |import| %> 17 | import <%= import %>; 18 | <% end %> 19 | 20 | import java.io.Serializable; 21 | import java.util.ArrayList; 22 | import java.util.List; 23 | import java.util.Map; 24 | 25 | public class <%= schema_name %> { 26 | <% [[:query, schema.query_root_name], [:mutation, schema.mutation_root_name]].each do |operation_type, root_name| %> 27 | <% next unless root_name %> 28 | public static <%= root_name %>Query <%= operation_type %>(<%= root_name %>QueryDefinition queryDef) { 29 | StringBuilder queryString = new StringBuilder("<%= operation_type unless operation_type == :query %>{"); 30 | <%= root_name %>Query query = new <%= root_name %>Query(queryString); 31 | queryDef.define(query); 32 | queryString.append('}'); 33 | return query; 34 | } 35 | 36 | public static class <%= operation_type.capitalize %>Response { 37 | private TopLevelResponse response; 38 | private <%= root_name %> data; 39 | 40 | public <%= operation_type.capitalize %>Response(TopLevelResponse response) throws SchemaViolationError { 41 | this.response = response; 42 | this.data = response.getData() != null ? new <%= root_name %>(response.getData()) : null; 43 | } 44 | 45 | public <%= root_name %> getData() { 46 | return data; 47 | } 48 | 49 | public List getErrors() { 50 | return response.getErrors(); 51 | } 52 | 53 | public String toJson() { 54 | return new Gson().toJson(response); 55 | } 56 | 57 | public String prettyPrintJson() { 58 | final Gson gson = new GsonBuilder().setPrettyPrinting().create(); 59 | return gson.toJson(response); 60 | } 61 | 62 | public static <%= operation_type.capitalize %>Response fromJson(String json) throws SchemaViolationError { 63 | final TopLevelResponse response = new Gson().fromJson(json, TopLevelResponse.class); 64 | return new <%= operation_type.capitalize %>Response(response); 65 | } 66 | } 67 | <% end %> 68 | 69 | <% schema.types.reject{ |type| type.name.start_with?('__') || type.scalar? }.each do |type| %> 70 | <% case type.kind when 'OBJECT', 'INTERFACE', 'UNION' %> 71 | <% fields = type.fields(include_deprecated: include_deprecated) || [] %> 72 | public interface <%= type.name %>QueryDefinition { 73 | void define(<%= type.name %>Query _queryBuilder); 74 | } 75 | 76 | <%= java_doc(type) %> 77 | public static class <%= type.name %>Query extends AbstractQuery<<%= type.name %>Query> { 78 | <%= type.name %>Query(StringBuilder _queryBuilder) { 79 | super(_queryBuilder); 80 | <% if type.object? && type.implement?("Node") %> 81 | startField("id"); 82 | <% end %> 83 | <% unless type.object? %> 84 | startField("__typename"); 85 | <% end %> 86 | } 87 | 88 | <% fields.each do |field| %> 89 | <% next if field.name == "id" && type.object? && type.implement?("Node") %> 90 | <% unless field.optional_args.empty? %> 91 | public class <%= field.classify_name %>Arguments extends Arguments { 92 | <%= field.classify_name %>Arguments(StringBuilder _queryBuilder) { 93 | super(_queryBuilder, <%= !!field.required_args.empty? %>); 94 | } 95 | 96 | <% field.optional_args.each do |arg| %> 97 | <%= java_doc(arg) %> 98 | public <%= field.classify_name %>Arguments <%= escape_reserved_word(arg.camelize_name) %>(<%= java_input_type(arg.type) %> value) { 99 | if (value != null) { 100 | startArgument("<%= arg.name %>"); 101 | <%= generate_build_input_code('value', arg.type) %> 102 | } 103 | return this; 104 | } 105 | <% end %> 106 | } 107 | 108 | public interface <%= field.classify_name %>ArgumentsDefinition { 109 | void define(<%= field.classify_name %>Arguments args); 110 | } 111 | 112 | <%= java_doc(field) %> 113 | public <%= type.name %>Query <%= escape_reserved_word(field.camelize_name) %>(<%= java_arg_defs(field, skip_optional: true) %>) { 114 | return <%= escape_reserved_word(field.camelize_name) %>(<%= java_arg_expresions_with_empty_optional_args(field) %>); 115 | } 116 | <% end %> 117 | 118 | <%= java_doc(field) %> 119 | <%= field.deprecated? ? "@Deprecated\n" : '' -%> 120 | public <%= type.name %>Query <%= escape_reserved_word(field.camelize_name) %>(<%= java_arg_defs(field) %>) { 121 | startField("<%= field.name %>"); 122 | <% unless field.args.empty? %> 123 | <% if field.required_args.empty? %> 124 | <%= field.classify_name %>Arguments args = new <%= field.classify_name %>Arguments(_queryBuilder); 125 | argsDef.define(args); 126 | <%= field.classify_name %>Arguments.end(args); 127 | <% else %> 128 | <% field.required_args.each_with_index do |arg, i| %> 129 | _queryBuilder.append("<%= i == 0 ? "(" : "," %><%= arg.name %>:"); 130 | <%= generate_build_input_code(escape_reserved_word(arg.camelize_name), arg.type) %> 131 | <% end %> 132 | <% unless field.optional_args.empty? %> 133 | argsDef.define(new <%= field.classify_name %>Arguments(_queryBuilder)); 134 | <% end %> 135 | _queryBuilder.append(')'); 136 | <% end %> 137 | <% end %> 138 | <% if field.subfields? %> 139 | _queryBuilder.append('{'); 140 | queryDef.define(new <%= field.type.unwrap.name %>Query(_queryBuilder)); 141 | _queryBuilder.append('}'); 142 | <% end %> 143 | return this; 144 | } 145 | <% end %> 146 | <% unless type.object? %> 147 | <% type.possible_types.each do |possible_type| %> 148 | public <%= type.name %>Query on<%= possible_type.name %>(<%= possible_type.name %>QueryDefinition queryDef) { 149 | startInlineFragment("<%= possible_type.name %>"); 150 | queryDef.define(new <%= possible_type.name %>Query(_queryBuilder)); 151 | _queryBuilder.append('}'); 152 | return this; 153 | } 154 | <% end %> 155 | <% end %> 156 | 157 | <% if schema.root_name?(type.name) %> 158 | public String toString() { 159 | return _queryBuilder.toString(); 160 | } 161 | <% end %> 162 | } 163 | 164 | <% unless type.object? %> 165 | <% if type.name == 'Node' %> 166 | public interface <%= type.name %> extends com.shopify.graphql.support.Node { 167 | <% else %> 168 | public interface <%= type.name %> { 169 | <% end %> 170 | String getGraphQlTypeName(); 171 | <% fields.each do |field| %> 172 | <%= java_output_type(field.type) %> get<%= field.classify_name %>(); 173 | <% end %> 174 | } 175 | <% end %> 176 | 177 | <% class_name = type.object? ? type.name : "Unknown#{type.name}" %> 178 | <%= java_doc(type) %> 179 | public static class <%= class_name %> extends AbstractResponse<<%= class_name %>> <%= java_implements(type) %>{ 180 | public <%= class_name %>() { 181 | } 182 | 183 | public <%= class_name %>(JsonObject fields) throws SchemaViolationError { 184 | for (Map.Entry field : fields.entrySet()) { 185 | String key = field.getKey(); 186 | String fieldName = getFieldName(key); 187 | switch (fieldName) { 188 | <% fields.each do |field| %> 189 | case "<%= field.name %>": { 190 | <% generate_build_output_code("field.getValue()", field.type) do |statements, expr| %> 191 | <%= statements %> 192 | responseData.put(key, <%= expr %>); 193 | <% end %> 194 | break; 195 | } 196 | <% end %> 197 | case "__typename": { 198 | responseData.put(key, jsonAsString(field.getValue(), key)); 199 | break; 200 | } 201 | default: { 202 | throw new SchemaViolationError(this, key, field.getValue()); 203 | } 204 | } 205 | } 206 | } 207 | 208 | <% if type.object? && type.implement?("Node") %> 209 | public <%= class_name %>(<%= scalars['ID'].non_nullable_type %> id) { 210 | this(); 211 | optimisticData.put("id", id); 212 | } 213 | <% end %> 214 | 215 | <% if type.object? %> 216 | public String getGraphQlTypeName() { 217 | return "<%= type.name %>"; 218 | } 219 | <% else %> 220 | public static <%= type.name %> create(JsonObject fields) throws SchemaViolationError { 221 | String typeName = fields.getAsJsonPrimitive("__typename").getAsString(); 222 | switch (typeName) { 223 | <% type.possible_types.each do |possible_type| %> 224 | case "<%= possible_type.name %>": { 225 | return new <%= possible_type.name %>(fields); 226 | } 227 | <% end %> 228 | default: { 229 | return new <%= class_name %>(fields); 230 | } 231 | } 232 | } 233 | 234 | public String getGraphQlTypeName() { 235 | return (String) get("__typename"); 236 | } 237 | <% end %> 238 | 239 | <% fields.each do |field| %> 240 | <%= java_doc(field) %> 241 | <%= java_annotations(field) %> 242 | public <%= java_output_type(field.type) %> get<%= field.classify_name %>() { 243 | return (<%= java_output_type(field.type) %>) get("<%= field.name %>"); 244 | } 245 | 246 | <% next if field.name == "id" && type.object? && type.implement?("Node") %> 247 | public <%= class_name %> set<%= field.classify_name %>(<%= java_output_type(field.type) %> arg) { 248 | optimisticData.put(getKey("<%= field.name %>"), arg); 249 | return this; 250 | } 251 | <% end %> 252 | 253 | public boolean unwrapsToObject(String key) { 254 | switch (getFieldName(key)) { 255 | <% fields.each do |field| %> 256 | case "<%= field.name %>": return <%= field.type.unwrap.object? %>; 257 | <% end %> 258 | default: return false; 259 | } 260 | } 261 | } 262 | <% when 'INPUT_OBJECT' %> 263 | public static class <%= type.name %> implements Serializable { 264 | <% type.required_input_fields.each do |field| %> 265 | private <%= java_input_type(field.type) %> <%= escape_reserved_word(field.camelize_name) %>; 266 | <% end %> 267 | <% type.optional_input_fields.each do |field| %> 268 | private Input<<%= java_input_type(field.type) %>> <%= escape_reserved_word(field.camelize_name) %> = Input.undefined(); 269 | <% end %> 270 | 271 | <% unless type.required_input_fields.empty? %> 272 | public <%= type.name %>(<%= type.required_input_fields.map{ |field| "#{java_input_type(field.type)} #{escape_reserved_word(field.camelize_name)}" }.join(', ') %>) { 273 | <% type.required_input_fields.each do |field| %> 274 | this.<%= escape_reserved_word(field.camelize_name) %> = <%= escape_reserved_word(field.camelize_name) %>; 275 | <% end %> 276 | } 277 | <% end %> 278 | 279 | <% type.required_input_fields.each do |field| %> 280 | <%= java_annotations(field) %> 281 | public <%= java_input_type(field.type) %> get<%= field.classify_name %>() { 282 | return <%= escape_reserved_word(field.camelize_name) %>; 283 | } 284 | 285 | public <%= type.name %> set<%= field.classify_name %>(<%= java_annotations(field, in_argument: true) %><%= java_input_type(field.type) %> <%= escape_reserved_word(field.camelize_name) %>) { 286 | this.<%= escape_reserved_word(field.camelize_name) %> = <%= escape_reserved_word(field.camelize_name) %>; 287 | return this; 288 | } 289 | 290 | <% end %> 291 | <% type.optional_input_fields.each do |field| %> 292 | <%= java_annotations(field) %> 293 | public <%= java_input_type(field.type) %> get<%= field.classify_name %>() { 294 | return <%= escape_reserved_word(field.camelize_name) %>.getValue(); 295 | } 296 | 297 | public Input<<%= java_input_type(field.type) %>> get<%= field.classify_name %>Input() { 298 | return <%= escape_reserved_word(field.camelize_name) %>; 299 | } 300 | 301 | public <%= type.name %> set<%= field.classify_name %>(<%= java_annotations(field, in_argument: true) %><%= java_input_type(field.type) %> <%= escape_reserved_word(field.camelize_name) %>) { 302 | this.<%= escape_reserved_word(field.camelize_name) %> = Input.optional(<%= escape_reserved_word(field.camelize_name) %>); 303 | return this; 304 | } 305 | 306 | public <%= type.name %> set<%= field.classify_name %>Input(Input<<%= java_input_type(field.type) %>> <%= escape_reserved_word(field.camelize_name) %>) { 307 | if (<%= escape_reserved_word(field.camelize_name) %> == null) { 308 | throw new IllegalArgumentException("Input can not be null"); 309 | } 310 | this.<%= escape_reserved_word(field.camelize_name) %> = <%= escape_reserved_word(field.camelize_name) %>; 311 | return this; 312 | } 313 | 314 | <% end %> 315 | 316 | public void appendTo(StringBuilder _queryBuilder) { 317 | String separator = ""; 318 | _queryBuilder.append('{'); 319 | <% type.required_input_fields.each do |field| %> 320 | _queryBuilder.append(separator); 321 | separator = ","; 322 | _queryBuilder.append("<%= field.name %>:"); 323 | <%= generate_build_input_code(escape_reserved_word(field.camelize_name), field.type) %> 324 | <% end %> 325 | <% type.optional_input_fields.each do |field| %> 326 | if (this.<%= escape_reserved_word(field.camelize_name) %>.isDefined()) { 327 | _queryBuilder.append(separator); 328 | separator = ","; 329 | _queryBuilder.append("<%= field.name %>:"); 330 | if (<%= escape_reserved_word(field.camelize_name) %>.getValue() != null) { 331 | <%= generate_build_input_code(escape_reserved_word(field.camelize_name).concat(".getValue()"), field.type) %> 332 | } else { 333 | _queryBuilder.append("null"); 334 | } 335 | } 336 | <% end %> 337 | _queryBuilder.append('}'); 338 | } 339 | } 340 | <% when 'ENUM' %> 341 | <%= java_doc(type) %> 342 | public enum <%= type.name %> { 343 | <% type.enum_values(include_deprecated: include_deprecated).each do |value| %> 344 | <%= java_doc(value) %> 345 | <%= value.deprecated? ? "@Deprecated\n" : '' -%> 346 | <%= value.upcase_name %>, 347 | <% end %> 348 | 349 | UNKNOWN_VALUE; 350 | 351 | public static <%= type.name %> fromGraphQl(String value) { 352 | if (value == null) { 353 | return null; 354 | } 355 | 356 | switch (value) { 357 | <% type.enum_values.each do |value| %> 358 | case "<%= value.name %>": { 359 | return <%= value.upcase_name %>; 360 | } 361 | <% end %> 362 | default: { 363 | return UNKNOWN_VALUE; 364 | } 365 | } 366 | } 367 | public String toString() { 368 | switch (this) { 369 | <% type.enum_values.each do |value| %> 370 | case <%= value.upcase_name %>: { 371 | return "<%= value.name %>"; 372 | } 373 | <% end %> 374 | default: { 375 | return ""; 376 | } 377 | } 378 | } 379 | } 380 | <% else %> 381 | <% raise NotImplementedError, "unhandled #{type.kind} type #{type.name}" %> 382 | <% end %> 383 | <% end %> 384 | } 385 | -------------------------------------------------------------------------------- /codegen/lib/graphql_java_gen/templates/Enum.java.erb: -------------------------------------------------------------------------------- 1 | <%= render_license %> 2 | 3 | package <%= package_name %>; 4 | 5 | import com.google.gson.Gson; 6 | import com.google.gson.GsonBuilder; 7 | import com.google.gson.JsonElement; 8 | import com.google.gson.JsonObject; 9 | import com.shopify.graphql.support.AbstractResponse; 10 | import com.shopify.graphql.support.Arguments; 11 | import com.shopify.graphql.support.Error; 12 | import com.shopify.graphql.support.AbstractQuery; 13 | import com.shopify.graphql.support.SchemaViolationError; 14 | import com.shopify.graphql.support.TopLevelResponse; 15 | import com.shopify.graphql.support.Input; 16 | <% imports.each do |import| %> 17 | import <%= import %>; 18 | <% end %> 19 | 20 | import java.io.Serializable; 21 | import java.util.ArrayList; 22 | import java.util.List; 23 | import java.util.Map; 24 | 25 | <% case type.kind when 'OBJECT', 'INTERFACE', 'UNION' %> 26 | <% when 'INPUT_OBJECT' %> 27 | <% when 'ENUM' %> 28 | <%= java_doc(type) %> 29 | public enum <%= type.name %> { 30 | <% enum_values = type.enum_values(include_deprecated: include_deprecated).sort_by{ |value| value.name } %> 31 | <% enum_values.each do |value| %> 32 | <%= java_doc(value) %> 33 | <%= value.deprecated? ? "@Deprecated\n" : '' -%> 34 | <%= value.upcase_name %>, 35 | <% end %> 36 | 37 | UNKNOWN_VALUE; 38 | 39 | public static <%= type.name %> fromGraphQl(String value) { 40 | if (value == null) { 41 | return null; 42 | } 43 | 44 | switch (value) { 45 | <% enum_values.each do |value| %> 46 | case "<%= value.name %>": { 47 | return <%= value.upcase_name %>; 48 | } 49 | <% end %> 50 | default: { 51 | return UNKNOWN_VALUE; 52 | } 53 | } 54 | } 55 | public String toString() { 56 | switch (this) { 57 | <% enum_values.each do |value| %> 58 | case <%= value.upcase_name %>: { 59 | return "<%= value.name %>"; 60 | } 61 | <% end %> 62 | default: { 63 | return ""; 64 | } 65 | } 66 | } 67 | } 68 | <% else %> 69 | <% raise NotImplementedError, "unhandled #{type.kind} type #{type.name}" %> 70 | <% end %> 71 | -------------------------------------------------------------------------------- /codegen/lib/graphql_java_gen/templates/Input.java.erb: -------------------------------------------------------------------------------- 1 | <%= render_license %> 2 | 3 | package <%= package_name %>; 4 | 5 | import com.google.gson.Gson; 6 | import com.google.gson.GsonBuilder; 7 | import com.google.gson.JsonElement; 8 | import com.google.gson.JsonObject; 9 | import com.shopify.graphql.support.AbstractResponse; 10 | import com.shopify.graphql.support.Arguments; 11 | import com.shopify.graphql.support.Error; 12 | import com.shopify.graphql.support.AbstractQuery; 13 | import com.shopify.graphql.support.SchemaViolationError; 14 | import com.shopify.graphql.support.TopLevelResponse; 15 | import com.shopify.graphql.support.Input; 16 | <% imports.each do |import| %> 17 | import <%= import %>; 18 | <% end %> 19 | 20 | import java.io.Serializable; 21 | import java.lang.reflect.InvocationTargetException; 22 | import java.lang.reflect.Method; 23 | import java.util.ArrayList; 24 | import java.util.HashMap; 25 | import java.util.List; 26 | import java.util.Map; 27 | 28 | <% required_input_fields = type.required_input_fields.sort_by{ |field| field.name } || [] %> 29 | <% optional_input_fields = type.optional_input_fields.sort_by{ |field| field.name } || [] %> 30 | <%= java_doc(type) %> 31 | public class <%= type.name %> implements Serializable { 32 | <% required_input_fields.each do |field| %> 33 | private <%= java_input_type(field.type) %> <%= escape_reserved_word(field.camelize_name) %>; 34 | <% end %> 35 | <% optional_input_fields.each do |field| %> 36 | private Input<<%= java_input_type(field.type) %>> <%= escape_reserved_word(field.camelize_name) %> = Input.undefined(); 37 | <% end %> 38 | 39 | private Map> customFilters = new HashMap<>(); 40 | 41 | <% unless required_input_fields.empty? %> 42 | public <%= type.name %>(<%= required_input_fields.map{ |field| "#{java_input_type(field.type)} #{escape_reserved_word(field.camelize_name)}" }.join(', ') %>) { 43 | <% required_input_fields.each do |field| %> 44 | this.<%= escape_reserved_word(field.camelize_name) %> = <%= escape_reserved_word(field.camelize_name) %>; 45 | <% end %> 46 | } 47 | <% end %> 48 | 49 | <% required_input_fields.each do |field| %> 50 | <%= java_annotations(field) %> 51 | <%= java_doc(field) %> 52 | public <%= java_input_type(field.type) %> get<%= field.classify_name %>() { 53 | return <%= escape_reserved_word(field.camelize_name) %>; 54 | } 55 | 56 | <%= java_doc(field) %> 57 | public <%= type.name %> set<%= field.classify_name %>(<%= java_annotations(field, in_argument: true) %><%= java_input_type(field.type) %> <%= escape_reserved_word(field.camelize_name) %>) { 58 | this.<%= escape_reserved_word(field.camelize_name) %> = <%= escape_reserved_word(field.camelize_name) %>; 59 | return this; 60 | } 61 | 62 | <% end %> 63 | <% optional_input_fields.each do |field| %> 64 | <%= java_annotations(field) %> 65 | <%= java_doc(field) %> 66 | public <%= java_input_type(field.type) %> get<%= field.classify_name %>() { 67 | return <%= escape_reserved_word(field.camelize_name) %>.getValue(); 68 | } 69 | 70 | <%= java_doc(field) %> 71 | public Input<<%= java_input_type(field.type) %>> get<%= field.classify_name %>Input() { 72 | return <%= escape_reserved_word(field.camelize_name) %>; 73 | } 74 | 75 | <%= java_doc(field) %> 76 | public <%= type.name %> set<%= field.classify_name %>(<%= java_annotations(field, in_argument: true) %><%= java_input_type(field.type) %> <%= escape_reserved_word(field.camelize_name) %>) { 77 | this.<%= escape_reserved_word(field.camelize_name) %> = Input.optional(<%= escape_reserved_word(field.camelize_name) %>); 78 | return this; 79 | } 80 | 81 | <%= java_doc(field) %> 82 | public <%= type.name %> set<%= field.classify_name %>Input(Input<<%= java_input_type(field.type) %>> <%= escape_reserved_word(field.camelize_name) %>) { 83 | if (<%= escape_reserved_word(field.camelize_name) %> == null) { 84 | throw new IllegalArgumentException("Input can not be null"); 85 | } 86 | this.<%= escape_reserved_word(field.camelize_name) %> = <%= escape_reserved_word(field.camelize_name) %>; 87 | return this; 88 | } 89 | 90 | <% end %> 91 | 92 | /** 93 | * Set custom filter. 94 | */ 95 | public <%= type.name %> setCustomFilter(String name, Serializable filterInput) { 96 | this.customFilters.put(name, Input.optional(filterInput)); 97 | return this; 98 | } 99 | 100 | public void appendTo(StringBuilder _queryBuilder) { 101 | String separator = ""; 102 | _queryBuilder.append('{'); 103 | 104 | if (!this.customFilters.isEmpty()) { 105 | for (Map.Entry> entry : customFilters.entrySet()) { 106 | _queryBuilder.append(separator); 107 | separator = ","; 108 | _queryBuilder.append(entry.getKey() + ":"); 109 | 110 | Serializable filter = entry.getValue().getValue(); 111 | 112 | if (filter != null) { 113 | try { 114 | Method appendTo = filter.getClass().getMethod("appendTo", StringBuilder.class); 115 | appendTo.invoke(filter, _queryBuilder); 116 | } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { 117 | _queryBuilder.append("null"); 118 | } 119 | } else { 120 | _queryBuilder.append("null"); 121 | } 122 | } 123 | } 124 | 125 | <% required_input_fields.each do |field| %> 126 | _queryBuilder.append(separator); 127 | separator = ","; 128 | _queryBuilder.append("<%= field.name %>:"); 129 | <%= generate_build_input_code(escape_reserved_word(field.camelize_name), field.type) %> 130 | <% end %> 131 | <% optional_input_fields.each do |field| %> 132 | if (this.<%= escape_reserved_word(field.camelize_name) %>.isDefined()) { 133 | _queryBuilder.append(separator); 134 | separator = ","; 135 | _queryBuilder.append("<%= field.name %>:"); 136 | if (<%= escape_reserved_word(field.camelize_name) %>.getValue() != null) { 137 | <%= generate_build_input_code(escape_reserved_word(field.camelize_name).concat(".getValue()"), field.type) %> 138 | } else { 139 | _queryBuilder.append("null"); 140 | } 141 | } 142 | <% end %> 143 | _queryBuilder.append('}'); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /codegen/lib/graphql_java_gen/templates/Interface.java.erb: -------------------------------------------------------------------------------- 1 | <%= render_license %> 2 | 3 | package <%= package_name %>; 4 | 5 | import com.google.gson.Gson; 6 | import com.google.gson.GsonBuilder; 7 | import com.google.gson.JsonElement; 8 | import com.google.gson.JsonObject; 9 | import com.shopify.graphql.support.AbstractResponse; 10 | import com.shopify.graphql.support.Arguments; 11 | import com.shopify.graphql.support.CustomFieldInterface; 12 | import com.shopify.graphql.support.Error; 13 | import com.shopify.graphql.support.AbstractQuery; 14 | import com.shopify.graphql.support.SchemaViolationError; 15 | import com.shopify.graphql.support.TopLevelResponse; 16 | import com.shopify.graphql.support.Input; 17 | <% imports.each do |import| %> 18 | import <%= import %>; 19 | <% end %> 20 | 21 | import java.io.Serializable; 22 | import java.util.ArrayList; 23 | import java.util.List; 24 | import java.util.Map; 25 | 26 | <% fields = type.fields != nil ? type.fields(include_deprecated: include_deprecated).sort_by{ |field| field.name } : [] %> 27 | <%= java_doc(type) %> 28 | <% unless type.object? %> 29 | <% if type.name == 'Node' %> 30 | public interface <%= type.name %> extends com.shopify.graphql.support.Node { 31 | <% else %> 32 | public interface <%= type.name %> extends CustomFieldInterface { 33 | <% end %> 34 | String getGraphQlTypeName(); 35 | <% fields.each do |field| %> 36 | <%= java_doc(field) %> 37 | <%= field.deprecated? ? "@Deprecated\n" : '' -%> 38 | <%= java_output_type(field.type) %> get<%= field.classify_name %>(); 39 | <% end %> 40 | } 41 | <% end %> 42 | -------------------------------------------------------------------------------- /codegen/lib/graphql_java_gen/templates/Object.java.erb: -------------------------------------------------------------------------------- 1 | <%= render_license %> 2 | 3 | package <%= package_name %>; 4 | 5 | import com.google.gson.Gson; 6 | import com.google.gson.GsonBuilder; 7 | import com.google.gson.JsonElement; 8 | import com.google.gson.JsonObject; 9 | import com.shopify.graphql.support.AbstractResponse; 10 | import com.shopify.graphql.support.Arguments; 11 | import com.shopify.graphql.support.Error; 12 | import com.shopify.graphql.support.AbstractQuery; 13 | import com.shopify.graphql.support.SchemaViolationError; 14 | import com.shopify.graphql.support.TopLevelResponse; 15 | import com.shopify.graphql.support.Input; 16 | <% imports.each do |import| %> 17 | import <%= import %>; 18 | <% end %> 19 | 20 | import java.io.Serializable; 21 | import java.util.ArrayList; 22 | import java.util.List; 23 | import java.util.Map; 24 | 25 | <% fields = type.fields(include_deprecated: include_deprecated) || [] %> 26 | <% fields.sort_by{ |field| field.name } %> 27 | <% class_name = type.object? ? type.name : "Unknown#{type.name}" %> 28 | <%= java_doc(type) %> 29 | public class <%= class_name %> extends AbstractResponse<<%= class_name %>> <%= java_implements(type) %>{ 30 | public <%= class_name %>() { 31 | } 32 | 33 | public <%= class_name %>(JsonObject fields) throws SchemaViolationError { 34 | for (Map.Entry field : fields.entrySet()) { 35 | String key = field.getKey(); 36 | String fieldName = getFieldName(key); 37 | switch (fieldName) { 38 | <% fields.each do |field| %> 39 | case "<%= field.name %>": { 40 | <% generate_build_output_code("field.getValue()", field.type) do |statements, expr| %> 41 | <%= statements %> 42 | responseData.put(key, <%= expr %>); 43 | <% end %> 44 | break; 45 | } 46 | <% end %> 47 | case "__typename": { 48 | responseData.put(key, jsonAsString(field.getValue(), key)); 49 | break; 50 | } 51 | <%if class_name == "Query" %> 52 | 53 | case "__schema": { 54 | __Schema optional1 = null; 55 | if (!field.getValue().isJsonNull()) { 56 | optional1 = new __Schema(jsonAsObject(field.getValue(), key)); 57 | } 58 | 59 | responseData.put(key, optional1); 60 | 61 | break; 62 | } 63 | 64 | case "__type": { 65 | __Type optional1 = null; 66 | if (!field.getValue().isJsonNull()) { 67 | optional1 = new __Type(jsonAsObject(field.getValue(), key)); 68 | } 69 | 70 | responseData.put(key, optional1); 71 | 72 | break; 73 | } 74 | <% end %> 75 | default: { 76 | readCustomField(fieldName, field.getValue()); 77 | } 78 | } 79 | } 80 | } 81 | 82 | <% if type.object? && type.implement?("Node") %> 83 | public <%= class_name %>(<%= scalars['ID'].non_nullable_type %> id) { 84 | this(); 85 | optimisticData.put("id", id); 86 | } 87 | <% end %> 88 | 89 | <% if type.object? %> 90 | public String getGraphQlTypeName() { 91 | return "<%= type.name %>"; 92 | } 93 | <% else %> 94 | public static <%= type.name %> create(JsonObject fields) throws SchemaViolationError { 95 | String typeName = fields.getAsJsonPrimitive("__typename").getAsString(); 96 | switch (typeName) { 97 | <% type.possible_types.each do |possible_type| %> 98 | case "<%= possible_type.name %>": { 99 | return new <%= possible_type.name %>(fields); 100 | } 101 | <% end %> 102 | default: { 103 | return new <%= class_name %>(fields); 104 | } 105 | } 106 | } 107 | 108 | public String getGraphQlTypeName() { 109 | return (String) get("__typename"); 110 | } 111 | <% end %> 112 | 113 | <% fields.each do |field| %> 114 | <%= java_doc(field) %> 115 | <%= java_annotations(field) -%> 116 | <%= field.deprecated? ? "@Deprecated\n" : '' -%> 117 | public <%= java_output_type(field.type) %> get<%= field.classify_name %>() { 118 | return (<%= java_output_type(field.type) %>) get("<%= field.name %>"); 119 | } 120 | 121 | <% next if field.name == "id" && type.object? && type.implement?("Node") %> 122 | public <%= class_name %> set<%= field.classify_name %>(<%= java_output_type(field.type) %> arg) { 123 | optimisticData.put(getKey("<%= field.name %>"), arg); 124 | return this; 125 | } 126 | <% end %> 127 | <% if class_name == "Query" %> 128 | 129 | /** 130 | * The root __schema field for introspection queries. 131 | */ 132 | public __Schema __getSchema() { 133 | return (__Schema) get("__schema"); 134 | } 135 | 136 | /** 137 | * The root __type field for introspection queries. 138 | */ 139 | public __Type __getType() { 140 | return (__Type) get("__type"); 141 | } 142 | <% end %> 143 | 144 | public boolean unwrapsToObject(String key) { 145 | switch (getFieldName(key)) { 146 | <% fields.each do |field| %> 147 | case "<%= field.name %>": return <%= field.type.unwrap.object? %>; 148 | <% end %> 149 | default: return false; 150 | } 151 | } 152 | } 153 | 154 | -------------------------------------------------------------------------------- /codegen/lib/graphql_java_gen/templates/Operations.java.erb: -------------------------------------------------------------------------------- 1 | <%= render_license %> 2 | 3 | package <%= package_name %>; 4 | 5 | import com.google.gson.Gson; 6 | import com.google.gson.GsonBuilder; 7 | import com.google.gson.JsonElement; 8 | import com.google.gson.JsonObject; 9 | import com.shopify.graphql.support.AbstractResponse; 10 | import com.shopify.graphql.support.Arguments; 11 | import com.shopify.graphql.support.Error; 12 | import com.shopify.graphql.support.AbstractQuery; 13 | import com.shopify.graphql.support.SchemaViolationError; 14 | import com.shopify.graphql.support.TopLevelResponse; 15 | import com.shopify.graphql.support.Input; 16 | <% imports.each do |import| %> 17 | import <%= import %>; 18 | <% end %> 19 | 20 | import java.io.Serializable; 21 | import java.util.ArrayList; 22 | import java.util.List; 23 | import java.util.Map; 24 | 25 | public class Operations { 26 | <% [[:query, schema.query_root_name], [:mutation, schema.mutation_root_name]].each do |operation_type, root_name| %> 27 | <% next unless root_name %> 28 | public static <%= root_name %>Query <%= operation_type %>(<%= root_name %>QueryDefinition queryDef) { 29 | StringBuilder queryString = new StringBuilder("<%= operation_type unless operation_type == :query %>{"); 30 | <%= root_name %>Query query = new <%= root_name %>Query(queryString); 31 | queryDef.define(query); 32 | queryString.append('}'); 33 | return query; 34 | } 35 | <% end %> 36 | } 37 | -------------------------------------------------------------------------------- /codegen/lib/graphql_java_gen/templates/Query.java.erb: -------------------------------------------------------------------------------- 1 | <%= render_license %> 2 | 3 | package <%= package_name %>; 4 | 5 | import com.google.gson.Gson; 6 | import com.google.gson.GsonBuilder; 7 | import com.google.gson.JsonElement; 8 | import com.google.gson.JsonObject; 9 | import com.shopify.graphql.support.AbstractResponse; 10 | import com.shopify.graphql.support.Arguments; 11 | import com.shopify.graphql.support.Error; 12 | import com.shopify.graphql.support.AbstractQuery; 13 | import com.shopify.graphql.support.SchemaViolationError; 14 | import com.shopify.graphql.support.TopLevelResponse; 15 | import com.shopify.graphql.support.Input; 16 | import com.shopify.graphql.support.Fragment; 17 | <% imports.each do |import| %> 18 | import <%= import %>; 19 | <% end %> 20 | 21 | import java.io.Serializable; 22 | import java.util.ArrayList; 23 | import java.util.List; 24 | <%- if schema.root_name?(type.name) -%> 25 | import java.util.LinkedHashMap; 26 | <%- end -%> 27 | import java.util.Map; 28 | 29 | 30 | <% fields = type.fields != nil ? type.fields(include_deprecated: include_deprecated).sort_by{ |field| field.name } : [] %> 31 | <%= java_doc(type) %> 32 | public class <%= type.name %>Query extends AbstractQuery<<%= type.name %>Query> { 33 | <% if type.object? %> 34 | <%= type.name %>Query(StringBuilder _queryBuilder) { 35 | super(_queryBuilder); 36 | <% if type.object? && type.implement?("Node") %> 37 | startField("id"); 38 | <% end %> 39 | } 40 | <% else %> 41 | <%= type.name %>Query(StringBuilder _queryBuilder) { 42 | this(_queryBuilder, true); 43 | } 44 | <%= type.name %>Query(StringBuilder _queryBuilder, boolean addTypename) { 45 | super(_queryBuilder); 46 | <%- if type.object? && type.implement?("Node") -%> 47 | startField("id"); 48 | <%- end -%> 49 | if (addTypename) { 50 | startField("__typename"); 51 | } 52 | } 53 | <% end %> 54 | 55 | <% fields.each do |field| %> 56 | <% next if field.name == "id" && type.object? && type.implement?("Node") %> 57 | <% unless field.optional_args.empty? %> 58 | public class <%= field.classify_name %>Arguments extends Arguments { 59 | <%= field.classify_name %>Arguments(StringBuilder _queryBuilder) { 60 | super(_queryBuilder, <%= !!field.required_args.empty? %>); 61 | } 62 | 63 | <% field.optional_args.each do |arg| %> 64 | <%= java_doc(arg) %> 65 | public <%= field.classify_name %>Arguments <%= escape_reserved_word(arg.camelize_name) %>(<%= java_input_type(arg.type) %> value) { 66 | if (value != null) { 67 | startArgument("<%= arg.name %>"); 68 | <%= generate_build_input_code('value', arg.type) %> 69 | } 70 | return this; 71 | } 72 | <% end %> 73 | } 74 | 75 | public interface <%= field.classify_name %>ArgumentsDefinition { 76 | void define(<%= field.classify_name %>Arguments args); 77 | } 78 | 79 | <%= java_doc(field) %> 80 | public <%= type.name %>Query <%= escape_reserved_word(field.camelize_name) %>(<%= java_arg_defs(field, skip_optional: true) %>) { 81 | return <%= escape_reserved_word(field.camelize_name) %>(<%= java_arg_expresions_with_empty_optional_args(field) %>); 82 | } 83 | <% end %> 84 | 85 | <%= java_doc(field) %> 86 | <%= field.deprecated? ? "@Deprecated\n" : '' -%> 87 | public <%= type.name %>Query <%= escape_reserved_word(field.camelize_name) %>(<%= java_arg_defs(field) %>) { 88 | startField("<%= field.name %>"); 89 | <% unless field.args.empty? %> 90 | <% if field.required_args.empty? %> 91 | <%= field.classify_name %>Arguments args = new <%= field.classify_name %>Arguments(_queryBuilder); 92 | argsDef.define(args); 93 | <%= field.classify_name %>Arguments.end(args); 94 | <% else %> 95 | <% field.required_args.each_with_index do |arg, i| %> 96 | _queryBuilder.append("<%= i == 0 ? "(" : "," %><%= arg.name %>:"); 97 | <%= generate_build_input_code(escape_reserved_word(arg.camelize_name), arg.type) %> 98 | <% end %> 99 | <% unless field.optional_args.empty? %> 100 | argsDef.define(new <%= field.classify_name %>Arguments(_queryBuilder)); 101 | <% end %> 102 | _queryBuilder.append(')'); 103 | <% end %> 104 | <% end %> 105 | <% if field.subfields? %> 106 | _queryBuilder.append('{'); 107 | queryDef.define(new <%= field.type.unwrap.name %>Query(_queryBuilder)); 108 | _queryBuilder.append('}'); 109 | <% end %> 110 | return this; 111 | } 112 | <% end %> 113 | <% unless type.object? %> 114 | <% interfaceFragments = [] %> 115 | <% type.possible_types.each do |possible_type| %> 116 | public <%= type.name %>Query on<%= possible_type.name %>(<%= possible_type.name %>QueryDefinition queryDef) { 117 | startInlineFragment("<%= possible_type.name %>"); 118 | queryDef.define(new <%= possible_type.name %>Query(_queryBuilder)); 119 | _queryBuilder.append('}'); 120 | return this; 121 | } 122 | <% schema.types_by_name[possible_type.name].interfaces.select {|intf| intf.name != type.name }.each do |intf| %> 123 | <% interfaceFragments << intf.name %> 124 | <% end %> 125 | <% end %> 126 | <% interfaceFragments.uniq.each do |interfaceFragment| %> 127 | public <%= type.name %>Query on<%= interfaceFragment %>(<%= interfaceFragment %>QueryDefinition queryDef) { 128 | startInlineFragment("<%= interfaceFragment %>"); 129 | queryDef.define(new <%= interfaceFragment %>Query(_queryBuilder)); 130 | _queryBuilder.append('}'); 131 | return this; 132 | } 133 | <% end %> 134 | <% end %> 135 | 136 | <% if schema.root_name?(type.name) %> 137 | public String toString() { 138 | if (fragments != null) { 139 | for (Fragment fragment : fragments.values()) { 140 | _queryBuilder.append(fragment.toString()); 141 | } 142 | } 143 | return _queryBuilder.toString(); 144 | } 145 | 146 | private Map fragments; 147 | 148 | /** 149 | * Adds a GraphQL "named" fragment to the query. If a fragment with the same name is already added, 150 | * calling this method will replace the existing fragment. 151 | * 152 | * @param fragment The fragment to add. 153 | */ 154 | public <%= type.name %>Query addFragment(Fragment fragment) { 155 | if (fragments == null) { 156 | fragments = new LinkedHashMap<>(); 157 | } 158 | fragments.put(fragment.getName(), fragment); 159 | return this; 160 | } 161 | <% end %> 162 | <% if type.name == "Query" %> 163 | 164 | /** 165 | * The root __schema field for introspection queries. 166 | */ 167 | public QueryQuery __schema(__SchemaQueryDefinition queryDef) { 168 | startField("__schema"); 169 | 170 | _queryBuilder.append('{'); 171 | queryDef.define(new __SchemaQuery(_queryBuilder)); 172 | _queryBuilder.append('}'); 173 | 174 | return this; 175 | } 176 | 177 | /** 178 | * The root __type field for introspection queries. 179 | */ 180 | public QueryQuery __type(String name, __TypeQueryDefinition queryDef) { 181 | startField("__type"); 182 | 183 | _queryBuilder.append("(name:"); 184 | AbstractQuery.appendQuotedString(_queryBuilder, name); 185 | _queryBuilder.append(')'); 186 | 187 | _queryBuilder.append('{'); 188 | queryDef.define(new __TypeQuery(_queryBuilder)); 189 | _queryBuilder.append('}'); 190 | 191 | return this; 192 | } 193 | <% end %> 194 | 195 | /** 196 | * Creates a GraphQL "named" fragment with the specified query type definition. 197 | * The generics nature of fragments ensures that a fragment can only be used at the right place in the GraphQL request. 198 | * 199 | * @param name The name of the fragment, must be unique for a given GraphQL request. 200 | * @param queryDef The fragment definition. 201 | * @return The fragment of a given generics type. 202 | */ 203 | public static Fragment<<%= type.name %>Query> createFragment(String name, <%= type.name %>QueryDefinition queryDef) { 204 | StringBuilder sb = new StringBuilder(); 205 | <%- if type.object? -%> 206 | queryDef.define(new <%= type.name %>Query(sb)); 207 | <%- else -%> 208 | queryDef.define(new <%= type.name %>Query(sb, false)); 209 | <%- end -%> 210 | return new Fragment<>(name, "<%= type.name %>", sb.toString()); 211 | } 212 | 213 | /** 214 | * Adds a <%= type.name %>Query fragment reference at the current position of the query. 215 | * For example for a fragment named test, calling this method will add the 216 | * reference ...test in the query. For GraphQL types implementing an interface, there 217 | * will be some similar methods using the Query type of each implemented interface. 218 | * 219 | * @param fragment The fragment to reference. 220 | */ 221 | public <%= type.name %>Query addFragmentReference(Fragment<<%= type.name %>Query> fragment) { 222 | startField("..." + fragment.getName()); 223 | return this; 224 | } 225 | 226 | <% if type.object? && !type.interfaces.empty? %> 227 | <% type.interfaces.each do |interface| %> 228 | /** 229 | * Adds a <%= interface.name %>Query fragment reference at the current position of the query. 230 | * For example for a fragment named test, calling this method will add the 231 | * reference ...test in the query. 232 | * 233 | * @param fragment The fragment to reference. 234 | */ 235 | public <%= type.name %>Query add<%= interface.name %>FragmentReference(Fragment<<%= interface.name %>Query> fragment) { 236 | startField("..." + fragment.getName()); 237 | return this; 238 | } 239 | <% end %> 240 | <% end %> 241 | } 242 | -------------------------------------------------------------------------------- /codegen/lib/graphql_java_gen/templates/QueryDefinition.java.erb: -------------------------------------------------------------------------------- 1 | <%= render_license %> 2 | 3 | package <%= package_name %>; 4 | 5 | import com.google.gson.Gson; 6 | import com.google.gson.GsonBuilder; 7 | import com.google.gson.JsonElement; 8 | import com.google.gson.JsonObject; 9 | import com.shopify.graphql.support.AbstractResponse; 10 | import com.shopify.graphql.support.Arguments; 11 | import com.shopify.graphql.support.Error; 12 | import com.shopify.graphql.support.AbstractQuery; 13 | import com.shopify.graphql.support.SchemaViolationError; 14 | import com.shopify.graphql.support.TopLevelResponse; 15 | import com.shopify.graphql.support.Input; 16 | <% imports.each do |import| %> 17 | import <%= import %>; 18 | <% end %> 19 | 20 | import java.io.Serializable; 21 | import java.util.ArrayList; 22 | import java.util.List; 23 | import java.util.Map; 24 | 25 | 26 | <% fields = type.fields(include_deprecated: include_deprecated) || [] %> 27 | public interface <%= type.name %>QueryDefinition { 28 | void define(<%= type.name %>Query _queryBuilder); 29 | } -------------------------------------------------------------------------------- /codegen/lib/graphql_java_gen/templates/Responses.java.erb: -------------------------------------------------------------------------------- 1 | <%= render_license %> 2 | 3 | package <%= package_name %>; 4 | 5 | import com.google.gson.Gson; 6 | import com.google.gson.GsonBuilder; 7 | import com.google.gson.JsonElement; 8 | import com.google.gson.JsonObject; 9 | import com.shopify.graphql.support.AbstractResponse; 10 | import com.shopify.graphql.support.Arguments; 11 | import com.shopify.graphql.support.Error; 12 | import com.shopify.graphql.support.AbstractQuery; 13 | import com.shopify.graphql.support.SchemaViolationError; 14 | import com.shopify.graphql.support.TopLevelResponse; 15 | import com.shopify.graphql.support.Input; 16 | <% imports.each do |import| %> 17 | import <%= import %>; 18 | <% end %> 19 | 20 | import java.io.Serializable; 21 | import java.util.ArrayList; 22 | import java.util.List; 23 | import java.util.Map; 24 | 25 | public class <%= response_type %>Response { 26 | private TopLevelResponse response; 27 | private <%= root_name %> data; 28 | 29 | public <%= response_type %>Response(TopLevelResponse response) throws SchemaViolationError { 30 | this.response = response; 31 | this.data = response.getData() != null ? new <%= root_name %>(response.getData()) : null; 32 | } 33 | 34 | public <%= root_name %> getData() { 35 | return data; 36 | } 37 | 38 | public List getErrors() { 39 | return response.getErrors(); 40 | } 41 | 42 | public String toJson() { 43 | return new Gson().toJson(response); 44 | } 45 | 46 | public String prettyPrintJson() { 47 | final Gson gson = new GsonBuilder().setPrettyPrinting().create(); 48 | return gson.toJson(response); 49 | } 50 | 51 | public static <%= response_type %>Response fromJson(String json) throws SchemaViolationError { 52 | final TopLevelResponse response = new Gson().fromJson(json, TopLevelResponse.class); 53 | return new <%= response_type %>Response(response); 54 | } 55 | } -------------------------------------------------------------------------------- /codegen/lib/graphql_java_gen/version.rb: -------------------------------------------------------------------------------- 1 | class GraphQLJavaGen 2 | VERSION = "0.1.0" 3 | end 4 | -------------------------------------------------------------------------------- /codegen/test/graphql_java_gen_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class GraphQLJavaGenTest < Minitest::Test 4 | def test_that_it_has_a_version_number 5 | refute_nil ::GraphQLJavaGen::VERSION 6 | end 7 | 8 | def test_default_script_name 9 | output = GraphQLJavaGen.new(MINIMAL_SCHEMA, **required_args).generate 10 | assert_match %r{.*This file is licensed to you under the Apache License.*}, output 11 | end 12 | 13 | def test_script_name_option 14 | output = GraphQLJavaGen.new(MINIMAL_SCHEMA, script_name: 'script/update_schema', **required_args).generate 15 | assert_match %r{.*This file is licensed to you under the Apache License.*}, output 16 | end 17 | 18 | def test_generate 19 | refute_empty GraphQLJavaGen.new(LARGER_SCHEMA, **required_args).generate 20 | end 21 | 22 | private 23 | 24 | def required_args 25 | { 26 | package_name: "com.example.MyApp", 27 | license_header_file: "../License.erb", 28 | nest_under: 'ExampleSchema', 29 | } 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /codegen/test/support/schema.rb: -------------------------------------------------------------------------------- 1 | require 'graphql' 2 | require 'json' 3 | 4 | module Support 5 | module Schema 6 | KeyType = GraphQL::EnumType.define do 7 | name "KeyType" 8 | description "Types of values that can be stored in a key" 9 | value("STRING") 10 | value("INTEGER") 11 | value("NOT_FOUND", deprecation_reason: "GraphQL null now used instead") 12 | end 13 | 14 | TimeType = GraphQL::ScalarType.define do 15 | name "Time" 16 | description "Time since epoch in seconds" 17 | end 18 | 19 | EntryType = GraphQL::InterfaceType.define do 20 | name "Entry" 21 | field :key, !types.String 22 | field :ttl, TimeType 23 | end 24 | 25 | StringEntryType = GraphQL::ObjectType.define do 26 | name "StringEntry" 27 | interfaces [EntryType] 28 | field :key, !types.String 29 | field :value, !types.String 30 | field :ttl, TimeType 31 | end 32 | 33 | IntegerEntryType = GraphQL::ObjectType.define do 34 | name "IntegerEntry" 35 | interfaces [EntryType] 36 | field :key, !types.String 37 | field :value, !types.Int 38 | field :ttl, TimeType 39 | end 40 | 41 | EntryUnionType = GraphQL::UnionType.define do 42 | name "EntryUnion" 43 | possible_types [StringEntryType, IntegerEntryType] 44 | end 45 | 46 | QueryType = GraphQL::ObjectType.define do 47 | name "QueryRoot" 48 | 49 | field :version, types.String 50 | field :get, types.String do 51 | description "Get a string value with the given key" 52 | deprecation_reason "Ambiguous, use string instead" 53 | argument :key, !types.String 54 | end 55 | field :string, types.String do 56 | description "Get a string value with the given key" 57 | argument :key, !types.String 58 | end 59 | field :integer, types.Int do 60 | description "Get a integer value with the given key" 61 | argument :key, !types.String 62 | end 63 | field :entry, EntryType do 64 | description "Get an entry of any type with the given key" 65 | argument :key, !types.String 66 | end 67 | field :entry_union, EntryUnionType do 68 | description "Get an entry of any type with the given key as a union" 69 | argument :key, !types.String 70 | end 71 | field :type, KeyType do 72 | argument :key, !types.String 73 | end 74 | field :ttl, TimeType do 75 | argument :key, !types.String 76 | end 77 | field :keys, !types[!types.String] do 78 | argument :first, !types.Int 79 | argument :after, types.String 80 | argument :type, KeyType 81 | end 82 | field :entries, !types[!EntryType] do 83 | argument :first, !types.Int 84 | argument :after, types.String 85 | end 86 | end 87 | 88 | SetIntegerInput = GraphQL::InputObjectType.define do 89 | name "SetIntegerInput" 90 | argument :key, !types.String 91 | argument :value, !types.Int 92 | argument :ttl, TimeType 93 | argument :negate, types.Boolean, default_value: false 94 | argument :api_client, types.String 95 | end 96 | 97 | MutationType = GraphQL::ObjectType.define do 98 | name "Mutation" 99 | 100 | field :set, types.String do 101 | deprecation_reason "Ambiguous, use set_string instead" 102 | argument :key, !types.String 103 | end 104 | field :set_string, !types.Boolean do 105 | argument :key, !types.String 106 | argument :value, !types.String 107 | end 108 | field :set_string_with_default, !types.Boolean do 109 | argument :key, !types.String 110 | argument :value, types.String, default_value: "I am default" 111 | end 112 | field :set_integer, !types.Boolean do 113 | argument :input, !SetIntegerInput 114 | end 115 | end 116 | 117 | ExampleSchema = GraphQL::Schema.define do 118 | query QueryType 119 | mutation MutationType 120 | orphan_types [StringEntryType, IntegerEntryType] 121 | resolve_type ->(obj, ctx) {} 122 | end 123 | 124 | MinimalQueryType = GraphQL::ObjectType.define do 125 | name "QueryRoot" 126 | 127 | field :version, types.String 128 | end 129 | 130 | MinimalSchema = GraphQL::Schema.define do 131 | query MinimalQueryType 132 | resolve_type ->(obj, ctx) {} 133 | end 134 | 135 | def self.introspection_result(schema = ExampleSchema) 136 | GraphQL::Query.new(schema, GraphQL::Introspection::INTROSPECTION_QUERY).result 137 | end 138 | end 139 | end 140 | -------------------------------------------------------------------------------- /codegen/test/test_helper.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) 2 | require 'graphql_java_gen' 3 | require 'graphql_schema' 4 | require 'json' 5 | 6 | require 'minitest/autorun' 7 | 8 | require 'support/schema' 9 | 10 | MINIMAL_SCHEMA = GraphQLSchema.new(Support::Schema.introspection_result(Support::Schema::MinimalSchema)) 11 | LARGER_SCHEMA = GraphQLSchema.new(Support::Schema.introspection_result) 12 | -------------------------------------------------------------------------------- /example-magento-generator.rb: -------------------------------------------------------------------------------- 1 | require 'graphql_java_gen' 2 | require 'graphql_schema' 3 | require 'json' 4 | 5 | introspection_result = File.read("schemas/magento-schema-2.4.4ee.json") 6 | schema = GraphQLSchema.new(JSON.parse(introspection_result)) 7 | 8 | GraphQLJavaGen.new(schema, 9 | package_name: "com.adobe.cq.commerce.magento.graphql", 10 | license_header_file: "./License.erb", 11 | include_deprecated: true, 12 | nest_under: 'Schema', # Not used, but must be defined 13 | custom_scalars: [ 14 | GraphQLJavaGen::Scalar.new( 15 | type_name: 'Decimal', 16 | java_type: 'BigDecimal', 17 | deserialize_expr: ->(expr) { "new BigDecimal(jsonAsString(#{expr}, key))" }, 18 | imports: ['java.math.BigDecimal'], 19 | ), 20 | ] 21 | ).save_granular("#{Dir.pwd}/src/main/java/com/adobe/cq/commerce/magento/graphql") 22 | -------------------------------------------------------------------------------- /graphql_java_gen.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../codegen/lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'graphql_java_gen/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "graphql_java_gen" 8 | spec.version = GraphQLJavaGen::VERSION 9 | spec.authors = ["Dylan Thacker-Smith"] 10 | spec.email = ["gems@shopify.com"] 11 | 12 | spec.summary = "GraphQL java client code generator" 13 | spec.description = %q{Generates java code based on the GraphQL schema to provide type-safe API for building GraphQL queries and using their responses.} 14 | spec.homepage = "https://github.com/adobe/graphql-java-generator" 15 | spec.license = "MIT" 16 | 17 | spec.files = `git ls-files -z codegen/lib LICENSE.txt README.md`.split("\x0") 18 | spec.require_paths = ["codegen/lib"] 19 | 20 | spec.required_ruby_version = ">= 2.1.0" 21 | 22 | spec.add_dependency "graphql_schema", "~> 0.1.1" 23 | 24 | spec.add_development_dependency "bundler", "~> 1.14" 25 | spec.add_development_dependency "rake", "~> 12.0" 26 | spec.add_development_dependency "minitest", "~> 5.10" 27 | spec.add_development_dependency "graphql", "~> 1.3" 28 | end 29 | -------------------------------------------------------------------------------- /support/.gitignore: -------------------------------------------------------------------------------- 1 | /.gradle 2 | /build 3 | *.iml 4 | local.properties 5 | .classpath 6 | .project 7 | .settings 8 | bin/ -------------------------------------------------------------------------------- /support/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | jcenter() 4 | } 5 | dependencies { 6 | classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4' 7 | } 8 | } 9 | 10 | apply from: 'graphql.java.gen.build.gradle' -------------------------------------------------------------------------------- /support/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adobe/graphql-java-generator/e3c923eacac9dbbed01bc1750e55e9f43abd7a89/support/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /support/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.8.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /support/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn ( ) { 37 | echo "$*" 38 | } 39 | 40 | die ( ) { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save ( ) { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /support/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /support/graphql.java.gen.build.gradle: -------------------------------------------------------------------------------- 1 | def VERSION_NAME = '0.2.1' 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | } 8 | 9 | repositories { 10 | jcenter() 11 | } 12 | 13 | apply plugin: 'java' 14 | apply plugin: 'maven' 15 | apply plugin: 'com.jfrog.bintray' 16 | 17 | dependencies { 18 | implementation 'com.google.code.gson:gson:2.8.5' 19 | testImplementation 'junit:junit:4.12' 20 | } 21 | 22 | compileJava { 23 | sourceCompatibility = '1.8' 24 | targetCompatibility = '1.8' 25 | } 26 | 27 | compileTestJava { 28 | sourceCompatibility = '1.8' 29 | targetCompatibility = '1.8' 30 | } 31 | 32 | test { 33 | testLogging { 34 | exceptionFormat = 'full' 35 | } 36 | } 37 | 38 | version = VERSION_NAME 39 | 40 | task sourcesJar(type: Jar) { 41 | from sourceSets.main.allSource 42 | classifier = 'sources' 43 | } 44 | 45 | task javadocJar(type: Jar, dependsOn: javadoc) { 46 | classifier = 'javadoc' 47 | from javadoc.destinationDir 48 | } 49 | 50 | artifacts { 51 | archives javadocJar 52 | archives sourcesJar 53 | } 54 | 55 | ext { 56 | publishedGroupId = 'com.shopify.graphql.support' 57 | artifact = 'support' 58 | libraryName = 'graphql-support' 59 | 60 | libraryDescription = 'GraphQL support package generated client code' 61 | 62 | siteUrl = 'https://github.com/adobe/graphql-java-generator' 63 | gitUrl = 'https://github.com/adobe/graphql-java-generator.git' 64 | 65 | licenseName = 'The MIT License' 66 | licenseUrl = 'https://opensource.org/licenses/MIT' 67 | allLicenses = ["MIT"] 68 | } 69 | 70 | group = publishedGroupId 71 | 72 | install { 73 | repositories.mavenInstaller { 74 | // Generates POM.xml with proper parameters 75 | pom { 76 | project { 77 | groupId publishedGroupId 78 | artifactId artifact 79 | 80 | name libraryName 81 | description libraryDescription 82 | url siteUrl 83 | 84 | licenses { 85 | license { 86 | name licenseName 87 | url licenseUrl 88 | } 89 | } 90 | 91 | scm { 92 | connection gitUrl 93 | developerConnection gitUrl 94 | url siteUrl 95 | } 96 | } 97 | } 98 | } 99 | } 100 | 101 | bintray { 102 | /* 103 | These values can be found on https://bintray.com/profile/edit 104 | BINTRAY_USER : your personal profile name (from "Your Profile") 105 | BINTRAY_KEY : found on the left menu, under "API Key" 106 | */ 107 | user = System.getenv('BINTRAY_USER') 108 | key = System.getenv('BINTRAY_KEY') 109 | 110 | configurations = ['archives'] 111 | publish = true 112 | pkg { 113 | userOrg = 'shopify' 114 | repo = 'shopify-java' 115 | name = libraryName 116 | desc = libraryDescription 117 | websiteUrl = siteUrl 118 | vcsUrl = gitUrl 119 | licenses = allLicenses 120 | version { 121 | name = VERSION_NAME 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /support/src/main/java/com/shopify/graphql/support/AbstractQuery.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 Shopify 3 | * Copyright 2019 Adobe 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a 6 | * copy of this software and associated documentation files (the "Software"), 7 | * to deal in the Software without restriction, including without limitation 8 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 | * and/or sell copies of the Software, and to permit persons to whom the Software 10 | * is furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in all 13 | * copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 19 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | */ 22 | 23 | package com.shopify.graphql.support; 24 | 25 | /** 26 | * Created by eapache on 2015-11-17. 27 | */ 28 | public abstract class AbstractQuery { 29 | public static final String ALIAS_SUFFIX_SEPARATOR = "__"; 30 | private static final String BAD_ALIAS_SEPARATOR = "-"; 31 | private static final String ALIAS_DELIMITER = ":"; 32 | 33 | public static final String CUSTOM_FIELD_LABEL = "_custom_"; 34 | 35 | protected final StringBuilder _queryBuilder; 36 | private boolean firstSelection = true; 37 | private String aliasSuffix = null; 38 | 39 | protected AbstractQuery(StringBuilder queryBuilder) { 40 | this._queryBuilder = queryBuilder; 41 | } 42 | 43 | public static void appendQuotedString(StringBuilder query, String string) { 44 | query.append('"'); 45 | for (char c : string.toCharArray()) { 46 | switch (c) { 47 | case '"': 48 | case '\\': 49 | query.append('\\'); 50 | query.append(c); 51 | break; 52 | case '\r': 53 | query.append("\\r"); 54 | break; 55 | case '\n': 56 | query.append("\\n"); 57 | break; 58 | default: 59 | if (c < 0x20) { 60 | query.append(String.format("\\u%04x", (int) c)); 61 | } else { 62 | query.append(c); 63 | } 64 | break; 65 | } 66 | } 67 | query.append('"'); 68 | } 69 | 70 | private void startSelection() { 71 | if (firstSelection) { 72 | firstSelection = false; 73 | } else { 74 | _queryBuilder.append(','); 75 | } 76 | } 77 | 78 | protected void startInlineFragment(String typeName) { 79 | if (aliasSuffix != null) { 80 | throw new IllegalStateException("An alias cannot be specified on inline fragments"); 81 | } 82 | 83 | startSelection(); 84 | _queryBuilder.append("... on "); 85 | _queryBuilder.append(typeName); 86 | _queryBuilder.append('{'); 87 | } 88 | 89 | protected void startField(String fieldName) { 90 | startSelection(); 91 | _queryBuilder.append(fieldName); 92 | if (aliasSuffix != null) { 93 | _queryBuilder.append(ALIAS_SUFFIX_SEPARATOR); 94 | _queryBuilder.append(aliasSuffix); 95 | _queryBuilder.append(ALIAS_DELIMITER); 96 | _queryBuilder.append(fieldName); 97 | aliasSuffix = null; 98 | } 99 | } 100 | 101 | @SuppressWarnings("unchecked") 102 | public T withAlias(String aliasSuffix) { 103 | if (this.aliasSuffix != null) { 104 | throw new IllegalStateException("Can only define a single alias for a field"); 105 | } 106 | if (aliasSuffix == null || aliasSuffix.isEmpty()) { 107 | throw new IllegalArgumentException("Can't specify an empty alias"); 108 | } 109 | if (aliasSuffix.contains(ALIAS_SUFFIX_SEPARATOR)) { 110 | throw new IllegalArgumentException("Alias must not contain __"); 111 | } 112 | if (aliasSuffix.contains(BAD_ALIAS_SEPARATOR)) { 113 | throw new IllegalArgumentException("Alias must not contain -"); 114 | } 115 | this.aliasSuffix = aliasSuffix; 116 | return (T) this; 117 | } 118 | 119 | /** 120 | * Adds a custom simple field to the GraphQL query. The adjective "simple" here refers to 121 | * a scalar/primitive field like String, Integer, Double, Boolean, or an array of fields. 122 | * 123 | * @param fieldName The name of the field that will be added to the GraphQL request. 124 | * @return The current query builder. 125 | */ 126 | @SuppressWarnings("unchecked") 127 | public T addCustomSimpleField(String fieldName) { 128 | startField(fieldName + CUSTOM_FIELD_LABEL + ALIAS_DELIMITER + fieldName); 129 | return (T) this; 130 | } 131 | 132 | /** 133 | * Adds a custom object field to the GraphQL query. The term "object" here refers to 134 | * a GraphQL object, which means some fields of the custom object must also be defined 135 | * in the query. 136 | * 137 | * @param fieldName The name of the field that will be added to the GraphQL request. 138 | * @param queryDef The definition of the requested sub-fields of the object. 139 | * @return The current query builder. 140 | */ 141 | @SuppressWarnings("unchecked") 142 | public T addCustomObjectField(String fieldName, CustomFieldQueryDefinition queryDef) { 143 | startField(fieldName + CUSTOM_FIELD_LABEL + ALIAS_DELIMITER + fieldName); 144 | 145 | _queryBuilder.append('{'); 146 | queryDef.define(new CustomFieldQuery(_queryBuilder)); 147 | _queryBuilder.append('}'); 148 | 149 | return (T) this; 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /support/src/main/java/com/shopify/graphql/support/AbstractResponse.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 Shopify 3 | * Copyright 2019 Adobe 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a 6 | * copy of this software and associated documentation files (the "Software"), 7 | * to deal in the Software without restriction, including without limitation 8 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 | * and/or sell copies of the Software, and to permit persons to whom the Software 10 | * is furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in all 13 | * copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 19 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | */ 22 | 23 | package com.shopify.graphql.support; 24 | 25 | import java.io.Serializable; 26 | import java.util.ArrayList; 27 | import java.util.HashMap; 28 | import java.util.List; 29 | import java.util.function.BiFunction; 30 | 31 | import com.google.gson.JsonArray; 32 | import com.google.gson.JsonElement; 33 | import com.google.gson.JsonObject; 34 | 35 | /** 36 | * Created by dylansmith on 2015-11-23. 37 | */ 38 | public abstract class AbstractResponse implements Serializable { 39 | public final HashMap responseData = new HashMap<>(); 40 | public final HashMap optimisticData = new HashMap<>(); 41 | private String aliasSuffix = null; 42 | 43 | @SuppressWarnings("unchecked") 44 | public T withAlias(String aliasSuffix) { 45 | if (this.aliasSuffix != null) { 46 | throw new IllegalStateException("Can only define a single alias for a field"); 47 | } 48 | if (aliasSuffix == null || aliasSuffix.isEmpty()) { 49 | throw new IllegalArgumentException("Can't specify an empty alias"); 50 | } 51 | if (aliasSuffix.contains(AbstractQuery.ALIAS_SUFFIX_SEPARATOR)) { 52 | throw new IllegalArgumentException("Alias must not contain __"); 53 | } 54 | 55 | this.aliasSuffix = aliasSuffix; 56 | return (T) this; 57 | } 58 | 59 | /** 60 | * Gets a field as a raw object. 61 | * 62 | * @param field The name of the field. 63 | * @return The raw object. 64 | */ 65 | public Object get(String field) { 66 | String key = getKey(field); 67 | if (optimisticData.containsKey(key)) { 68 | return optimisticData.get(key); 69 | } 70 | return responseData.get(key); 71 | } 72 | 73 | @FunctionalInterface 74 | private interface BiFunctionWithException { 75 | R apply(T t, U u) throws SchemaViolationError; 76 | } 77 | 78 | private BiFunction converterWrapper(BiFunctionWithException converter) { 79 | return (t, u) -> { 80 | try { 81 | return converter.apply(t, u); 82 | } catch (Exception e) { 83 | throw new RuntimeException(e); 84 | } 85 | }; 86 | } 87 | 88 | private R getAs(String field, BiFunction converter) { 89 | String key = getKey(field); 90 | if (optimisticData.containsKey(key)) { 91 | return converter.apply((JsonElement) optimisticData.get(key), key); 92 | } 93 | return converter.apply((JsonElement) responseData.get(key), key); 94 | } 95 | 96 | /** 97 | * Tries to deserialise and return the given JSON field as a String. The field itself is expected 98 | * to be stored in the object data as a {@link JsonElement}, which is the case for custom simple fields. 99 | * 100 | * @param field The name of the field. 101 | * @return The value of the field. 102 | * @throws SchemaViolationError If the field cannot be converted to a String. 103 | */ 104 | public String getAsString(String field) throws SchemaViolationError { 105 | BiFunction converter = converterWrapper(this::jsonAsString); 106 | return getAs(field, converter); 107 | } 108 | 109 | /** 110 | * Tries to deserialise and return the given JSON field as an Integer. The field itself is expected 111 | * to be stored in the object data as a {@link JsonElement}, which is the case for custom simple fields. 112 | * 113 | * @param field The name of the field. 114 | * @return The value of the field. 115 | * @throws SchemaViolationError If the field cannot be converted to an Integer. 116 | */ 117 | public Integer getAsInteger(String field) throws SchemaViolationError { 118 | BiFunction converter = converterWrapper(this::jsonAsInteger); 119 | return getAs(field, converter); 120 | } 121 | 122 | /** 123 | * Tries to deserialise and return the given JSON field as a Double. The field itself is expected 124 | * to be stored in the object data as a {@link JsonElement}, which is the case for custom simple fields. 125 | * 126 | * @param field The name of the field. 127 | * @return The value of the field. 128 | * @throws SchemaViolationError If the field cannot be converted to a Double. 129 | */ 130 | public Double getAsDouble(String field) throws SchemaViolationError { 131 | BiFunction converter = converterWrapper(this::jsonAsDouble); 132 | return getAs(field, converter); 133 | } 134 | 135 | /** 136 | * Tries to deserialise and return the given JSON field as a Boolean. The field itself is expected 137 | * to be stored in the object data as a {@link JsonElement}, which is the case for custom simple fields. 138 | * 139 | * @param field The name of the field. 140 | * @return The value of the field. 141 | * @throws SchemaViolationError If the field cannot be converted to a Boolean. 142 | */ 143 | public Boolean getAsBoolean(String field) throws SchemaViolationError { 144 | BiFunction converter = converterWrapper(this::jsonAsBoolean); 145 | return getAs(field, converter); 146 | } 147 | 148 | /** 149 | * Tries to deserialise and return the given JSON field as an Array. The field itself is expected 150 | * to be stored in the object data as a {@link JsonElement}, which is the case for custom simple fields. 151 | * 152 | * @param field The name of the field. 153 | * @return The value of the field. 154 | * @throws SchemaViolationError If the field cannot be converted to an Array. 155 | */ 156 | public JsonArray getAsArray(String field) throws SchemaViolationError { 157 | BiFunction converter = converterWrapper(this::jsonAsArray); 158 | return getAs(field, converter); 159 | } 160 | 161 | /** 162 | * Tries to read a custom field from the GraphQL JSON response. The method throws a schema violation 163 | * exception if the field name does not contain the _custom_ alias suffix added by the library, 164 | * which would indicate that the field does not belong to the GraphQL Schema and was not explicitly requested 165 | * as a custom field. 166 | * 167 | * @param fieldName The field name. 168 | * @param element The JSON element parsed by the JSON deserialiser. 169 | * @throws SchemaViolationError If the field name does not contain the _custom_ suffix. 170 | */ 171 | protected void readCustomField(String fieldName, JsonElement element) throws SchemaViolationError { 172 | if (!fieldName.endsWith(AbstractQuery.CUSTOM_FIELD_LABEL)) { 173 | throw new SchemaViolationError(this, fieldName, element); 174 | } 175 | 176 | int end = fieldName.lastIndexOf(AbstractQuery.CUSTOM_FIELD_LABEL); 177 | responseData.put(fieldName.substring(0, end), element); 178 | } 179 | 180 | protected String getFieldName(String key) { 181 | int i = key.lastIndexOf(AbstractQuery.ALIAS_SUFFIX_SEPARATOR); 182 | if (i > 1) { 183 | key = key.substring(0, i); 184 | } 185 | return key; 186 | } 187 | 188 | protected String getKey(String field) { 189 | if (aliasSuffix != null) { 190 | field += AbstractQuery.ALIAS_SUFFIX_SEPARATOR + aliasSuffix; 191 | aliasSuffix = null; 192 | } 193 | return field; 194 | } 195 | 196 | protected String jsonAsString(JsonElement element, String field) throws SchemaViolationError { 197 | if (!element.isJsonPrimitive() || !element.getAsJsonPrimitive().isString()) { 198 | throw new SchemaViolationError(this, field, element); 199 | } 200 | return element.getAsJsonPrimitive().getAsString(); 201 | } 202 | 203 | protected Integer jsonAsInteger(JsonElement element, String field) throws SchemaViolationError { 204 | if (!element.isJsonPrimitive() || (!element.getAsJsonPrimitive().isNumber() && !element.getAsJsonPrimitive().isString())) { 205 | throw new SchemaViolationError(this, field, element); 206 | } 207 | try { 208 | return element.getAsJsonPrimitive().getAsInt(); 209 | } catch (NumberFormatException exc) { 210 | throw new SchemaViolationError(this, field, element); 211 | } 212 | } 213 | 214 | protected Double jsonAsDouble(JsonElement element, String field) throws SchemaViolationError { 215 | if (!element.isJsonPrimitive() || (!element.getAsJsonPrimitive().isNumber() && !element.getAsJsonPrimitive().isString())) { 216 | throw new SchemaViolationError(this, field, element); 217 | } 218 | try { 219 | return element.getAsJsonPrimitive().getAsDouble(); 220 | } catch (NumberFormatException exc) { 221 | throw new SchemaViolationError(this, field, element); 222 | } 223 | } 224 | 225 | protected Boolean jsonAsBoolean(JsonElement element, String field) throws SchemaViolationError { 226 | if (!element.isJsonPrimitive() || (!element.getAsJsonPrimitive().isBoolean() && !element.getAsJsonPrimitive().isString())) { 227 | throw new SchemaViolationError(this, field, element); 228 | } 229 | return element.getAsJsonPrimitive().getAsBoolean(); 230 | } 231 | 232 | protected JsonObject jsonAsObject(JsonElement element, String field) throws SchemaViolationError { 233 | if (!element.isJsonObject()) { 234 | throw new SchemaViolationError(this, field, element); 235 | } 236 | return element.getAsJsonObject(); 237 | } 238 | 239 | protected JsonArray jsonAsArray(JsonElement element, String field) throws SchemaViolationError { 240 | if (!element.isJsonArray()) { 241 | throw new SchemaViolationError(this, field, element); 242 | } 243 | return element.getAsJsonArray(); 244 | } 245 | 246 | public List collectNodes() { 247 | final ArrayList children = new ArrayList<>(); 248 | 249 | collectNodes(this, children); 250 | 251 | return children; 252 | } 253 | 254 | private static void collectNodes(Object o, List collection) { 255 | if (o instanceof AbstractResponse) { 256 | final AbstractResponse response = (AbstractResponse) o; 257 | 258 | if (response instanceof Node) { 259 | collection.add((Node) response); 260 | } 261 | 262 | for (Object key : response.responseData.keySet()) { 263 | collectNodes(response.get((String) key), collection); 264 | } 265 | } else if (o instanceof List) { 266 | for (Object element : (List) o) { 267 | collectNodes(element, collection); 268 | } 269 | } 270 | } 271 | 272 | public abstract boolean unwrapsToObject(String key); 273 | } 274 | -------------------------------------------------------------------------------- /support/src/main/java/com/shopify/graphql/support/Arguments.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 Shopify 3 | * Copyright 2019 Adobe 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a 6 | * copy of this software and associated documentation files (the "Software"), 7 | * to deal in the Software without restriction, including without limitation 8 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 | * and/or sell copies of the Software, and to permit persons to whom the Software 10 | * is furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in all 13 | * copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 19 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | */ 22 | 23 | package com.shopify.graphql.support; 24 | 25 | import java.util.HashSet; 26 | import java.util.Set; 27 | 28 | /** 29 | * Created by dylansmith on 2015-11-20. 30 | */ 31 | public abstract class Arguments { 32 | private final StringBuilder query; 33 | private final Set arguments = new HashSet<>(); 34 | private boolean firstArgument; 35 | 36 | protected Arguments(StringBuilder query, boolean firstArgument) { 37 | this.query = query; 38 | this.firstArgument = firstArgument; 39 | } 40 | 41 | public static void end(Arguments arguments) { 42 | if (!arguments.firstArgument) { 43 | arguments.query.append(')'); 44 | } 45 | } 46 | 47 | protected void startArgument(String name) { 48 | if (!arguments.add(name)) { 49 | throw new RuntimeException("Already specified argument " + name); 50 | } 51 | if (firstArgument) { 52 | firstArgument = false; 53 | query.append('('); 54 | } else { 55 | query.append(','); 56 | } 57 | query.append(name); 58 | query.append(':'); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /support/src/main/java/com/shopify/graphql/support/CustomFieldInterface.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Adobe 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the Software 9 | * is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 18 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | 22 | package com.shopify.graphql.support; 23 | 24 | import com.google.gson.JsonArray; 25 | import com.google.gson.JsonElement; 26 | 27 | /** 28 | * In order to allow custom fields from being "gettable" via java interface classes, this class 29 | * defines all the getter methods for custom fields. Note that all these methods are already implemented in 30 | * {@link AbstractResponse} so they are immediately available to all interfaces. 31 | */ 32 | public interface CustomFieldInterface { 33 | 34 | /** 35 | * Gets a field as a raw object. 36 | * 37 | * @param field The name of the field. 38 | * @return The raw object. 39 | */ 40 | public default Object get(String field) { 41 | return null; 42 | } 43 | 44 | /** 45 | * Tries to deserialise and return the given JSON field as a String. The field itself is expected 46 | * to be stored in the object data as a {@link JsonElement}, which is the case for custom simple fields. 47 | * 48 | * @param field The name of the field. 49 | * @return The value of the field. 50 | * @throws SchemaViolationError If the field cannot be converted to a String. 51 | */ 52 | public default String getAsString(String field) throws SchemaViolationError { 53 | return null; 54 | } 55 | 56 | /** 57 | * Tries to deserialise and return the given JSON field as an Integer. The field itself is expected 58 | * to be stored in the object data as a {@link JsonElement}, which is the case for custom simple fields. 59 | * 60 | * @param field The name of the field. 61 | * @return The value of the field. 62 | * @throws SchemaViolationError If the field cannot be converted to an Integer. 63 | */ 64 | public default Integer getAsInteger(String field) throws SchemaViolationError { 65 | return null; 66 | } 67 | 68 | /** 69 | * Tries to deserialise and return the given JSON field as a Double. The field itself is expected 70 | * to be stored in the object data as a {@link JsonElement}, which is the case for custom simple fields. 71 | * 72 | * @param field The name of the field. 73 | * @return The value of the field. 74 | * @throws SchemaViolationError If the field cannot be converted to a Double. 75 | */ 76 | public default Double getAsDouble(String field) throws SchemaViolationError { 77 | return null; 78 | } 79 | 80 | /** 81 | * Tries to deserialise and return the given JSON field as a Boolean. The field itself is expected 82 | * to be stored in the object data as a {@link JsonElement}, which is the case for custom simple fields. 83 | * 84 | * @param field The name of the field. 85 | * @return The value of the field. 86 | * @throws SchemaViolationError If the field cannot be converted to a Boolean. 87 | */ 88 | public default Boolean getAsBoolean(String field) throws SchemaViolationError { 89 | return null; 90 | } 91 | 92 | /** 93 | * Tries to deserialise and return the given JSON field as an Array. The field itself is expected 94 | * to be stored in the object data as a {@link JsonElement}, which is the case for custom simple fields. 95 | * 96 | * @param field The name of the field. 97 | * @return The value of the field. 98 | * @throws SchemaViolationError If the field cannot be converted to an Array. 99 | */ 100 | public default JsonArray getAsArray(String field) throws SchemaViolationError { 101 | return null; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /support/src/main/java/com/shopify/graphql/support/CustomFieldQuery.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Adobe 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the Software 9 | * is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 18 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | 22 | package com.shopify.graphql.support; 23 | 24 | public class CustomFieldQuery extends AbstractQuery { 25 | 26 | public CustomFieldQuery(StringBuilder _queryBuilder) { 27 | super(_queryBuilder); 28 | } 29 | 30 | /** 31 | * Adds a standard "non-custom" field to the query. This method is typically used to specify 32 | * the fields of a custom object field that has an existing GraphQL type that can already be 33 | * parsed by the library. 34 | * 35 | * @param fieldName The name of the field that will be added to the GraphQL query. 36 | * @return The current query builder. 37 | */ 38 | public CustomFieldQuery addField(String fieldName) { 39 | startField(fieldName); 40 | return this; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /support/src/main/java/com/shopify/graphql/support/CustomFieldQueryDefinition.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Adobe 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the Software 9 | * is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 18 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | 22 | package com.shopify.graphql.support; 23 | 24 | public interface CustomFieldQueryDefinition { 25 | void define(CustomFieldQuery _queryBuilder); 26 | } 27 | -------------------------------------------------------------------------------- /support/src/main/java/com/shopify/graphql/support/Error.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 Shopify 3 | * Copyright 2019 Adobe 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a 6 | * copy of this software and associated documentation files (the "Software"), 7 | * to deal in the Software without restriction, including without limitation 8 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 | * and/or sell copies of the Software, and to permit persons to whom the Software 10 | * is furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in all 13 | * copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 19 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | */ 22 | 23 | package com.shopify.graphql.support; 24 | 25 | import java.io.Serializable; 26 | 27 | import com.google.gson.JsonElement; 28 | import com.google.gson.JsonObject; 29 | 30 | /** 31 | * Created by eapache on 2015-11-17. 32 | */ 33 | public class Error implements Serializable { 34 | private final String message; 35 | private final int line; 36 | private final int column; 37 | 38 | 39 | public Error(String message) { 40 | this.message = message; 41 | line = 0; 42 | column = 0; 43 | } 44 | 45 | public Error(JsonObject fields) { 46 | JsonElement message = fields.get("message"); 47 | if (message != null && message.isJsonPrimitive() && message.getAsJsonPrimitive().isString()) { 48 | this.message = message.getAsString(); 49 | } else { 50 | this.message = "Unknown error"; 51 | } 52 | 53 | JsonElement line = fields.get("line"); 54 | if (line != null && line.isJsonPrimitive() && line.getAsJsonPrimitive().isNumber()) { 55 | this.line = line.getAsInt(); 56 | } else { 57 | this.line = 0; 58 | } 59 | 60 | JsonElement column = fields.get("column"); 61 | if (column != null && column.isJsonPrimitive() && column.getAsJsonPrimitive().isNumber()) { 62 | this.column = column.getAsInt(); 63 | } else { 64 | this.column = 0; 65 | } 66 | } 67 | 68 | @Override 69 | public String toString() { 70 | return message(); 71 | } 72 | 73 | public String message() { 74 | return message; 75 | } 76 | 77 | public int line() { 78 | return line; 79 | } 80 | 81 | public int column() { 82 | return column; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /support/src/main/java/com/shopify/graphql/support/Fragment.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * 3 | * Copyright 2020 Adobe. All rights reserved. 4 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. You may obtain a copy 6 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software distributed under 9 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 10 | * OF ANY KIND, either express or implied. See the License for the specific language 11 | * governing permissions and limitations under the License. 12 | * 13 | ******************************************************************************/ 14 | 15 | package com.shopify.graphql.support; 16 | 17 | /** 18 | * This class is used to define a GraphQL "named" fragment.
19 | *
20 | * A fragment can be referenced in any request with the addFragmentReference(Fragment) method of the corresponding fragment's 21 | * generic type and must be added to the root request with the addFragment(Fragment) method of the QueryQuery 22 | * or MutationQuery classes. 23 | */ 24 | public class Fragment { 25 | 26 | private static final String FORMAT = "fragment %s on %s{%s}"; 27 | 28 | protected String name; 29 | protected String type; 30 | protected String query; 31 | 32 | public Fragment(String name, String type, String query) { 33 | this.name = name; 34 | this.type = type; 35 | this.query = query; 36 | } 37 | 38 | @Override 39 | public String toString() { 40 | return String.format(FORMAT, name, type, query); 41 | } 42 | 43 | public String getName() { 44 | return name; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /support/src/main/java/com/shopify/graphql/support/ID.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Shopify 3 | * Copyright 2019 Adobe 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a 6 | * copy of this software and associated documentation files (the "Software"), 7 | * to deal in the Software without restriction, including without limitation 8 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 | * and/or sell copies of the Software, and to permit persons to whom the Software 10 | * is furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in all 13 | * copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 19 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | */ 22 | 23 | package com.shopify.graphql.support; 24 | 25 | import java.io.Serializable; 26 | 27 | /** 28 | * Created by dylansmith on 2016-11-01. 29 | */ 30 | 31 | public class ID implements Serializable { 32 | protected final String id; 33 | 34 | public ID(String id) { 35 | this.id = id; 36 | } 37 | 38 | @Override 39 | public String toString() { 40 | return id; 41 | } 42 | 43 | @Override 44 | public boolean equals(Object o) { 45 | if (super.equals(o)) { 46 | return true; 47 | } 48 | return o instanceof ID && id.equals(((ID) o).id); 49 | } 50 | 51 | // like `a.equals(b)` except handles nulls 52 | public static boolean equals(ID a, ID b) { 53 | if (a == null) { 54 | return b == null; 55 | } 56 | return a.equals(b); 57 | } 58 | 59 | @Override 60 | public int hashCode() { 61 | return id.hashCode(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /support/src/main/java/com/shopify/graphql/support/Input.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Shopify 3 | * Copyright 2019 Adobe 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a 6 | * copy of this software and associated documentation files (the "Software"), 7 | * to deal in the Software without restriction, including without limitation 8 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 | * and/or sell copies of the Software, and to permit persons to whom the Software 10 | * is furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in all 13 | * copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 19 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | */ 22 | 23 | package com.shopify.graphql.support; 24 | 25 | import java.io.Serializable; 26 | 27 | /** 28 | * Created by henrytao on 9/7/17. 29 | */ 30 | 31 | public final class Input implements Serializable { 32 | 33 | private final T value; 34 | private final boolean defined; 35 | 36 | public static Input value(@Nullable T value) { 37 | return new Input<>(value, true); 38 | } 39 | 40 | public static Input optional(@Nullable T value) { 41 | return value != null ? value(value) : Input.undefined(); 42 | } 43 | 44 | public static Input undefined() { 45 | return new Input<>(null, false); 46 | } 47 | 48 | private Input(T value, boolean defined) { 49 | this.value = value; 50 | this.defined = defined; 51 | } 52 | 53 | public T getValue() { 54 | return value; 55 | } 56 | 57 | public boolean isDefined() { 58 | return defined; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /support/src/main/java/com/shopify/graphql/support/InvalidGraphQLException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 Shopify 3 | * Copyright 2019 Adobe 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a 6 | * copy of this software and associated documentation files (the "Software"), 7 | * to deal in the Software without restriction, including without limitation 8 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 | * and/or sell copies of the Software, and to permit persons to whom the Software 10 | * is furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in all 13 | * copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 19 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | */ 22 | 23 | package com.shopify.graphql.support; 24 | 25 | /** 26 | * Created by dylansmith on 2015-11-24. 27 | */ 28 | public class InvalidGraphQLException extends Exception { 29 | public InvalidGraphQLException(String message) { 30 | super(message); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /support/src/main/java/com/shopify/graphql/support/Node.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 Shopify 3 | * Copyright 2019 Adobe 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a 6 | * copy of this software and associated documentation files (the "Software"), 7 | * to deal in the Software without restriction, including without limitation 8 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 | * and/or sell copies of the Software, and to permit persons to whom the Software 10 | * is furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in all 13 | * copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 19 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | */ 22 | 23 | package com.shopify.graphql.support; 24 | 25 | // Helpful to have this common base class for Relay-compatible schemas. Unused otherwise. 26 | public interface Node { 27 | String getGraphQlTypeName(); 28 | 29 | ID getId(); 30 | } 31 | -------------------------------------------------------------------------------- /support/src/main/java/com/shopify/graphql/support/Nullable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.shopify.graphql.support; 17 | 18 | import static java.lang.annotation.ElementType.ANNOTATION_TYPE; 19 | import static java.lang.annotation.ElementType.FIELD; 20 | import static java.lang.annotation.ElementType.METHOD; 21 | import static java.lang.annotation.ElementType.PACKAGE; 22 | import static java.lang.annotation.ElementType.PARAMETER; 23 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 24 | 25 | import java.lang.annotation.Documented; 26 | import java.lang.annotation.Retention; 27 | import java.lang.annotation.Target; 28 | 29 | /** 30 | * Denotes that a parameter, field or method return value can be null. 31 | *

32 | * When decorating a method call parameter, this denotes that the parameter can 33 | * legitimately be null and the method will gracefully deal with it. Typically 34 | * used on optional parameters. 35 | *

36 | * When decorating a method, this denotes the method might legitimately return 37 | * null. 38 | *

39 | * This is a marker annotation and it has no specific attributes. 40 | */ 41 | @Documented 42 | @Retention(RUNTIME) 43 | @Target({METHOD, PARAMETER, FIELD, ANNOTATION_TYPE, PACKAGE}) 44 | public @interface Nullable { 45 | } 46 | 47 | -------------------------------------------------------------------------------- /support/src/main/java/com/shopify/graphql/support/SchemaViolationError.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Shopify 3 | * Copyright 2019 Adobe 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a 6 | * copy of this software and associated documentation files (the "Software"), 7 | * to deal in the Software without restriction, including without limitation 8 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 | * and/or sell copies of the Software, and to permit persons to whom the Software 10 | * is furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in all 13 | * copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 19 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | */ 22 | 23 | package com.shopify.graphql.support; 24 | 25 | import com.google.gson.JsonElement; 26 | 27 | /** 28 | * Created by dylansmith on 2016-08-17. 29 | */ 30 | public class SchemaViolationError extends Exception { 31 | private final AbstractResponse object; 32 | private final String field; 33 | private final JsonElement value; 34 | 35 | public SchemaViolationError(AbstractResponse object, String field, JsonElement value) { 36 | super("Invalid value " + value.toString() + " for field " + object.getClass().getSimpleName() + "." + field); 37 | this.object = object; 38 | this.field = field; 39 | this.value = value; 40 | } 41 | 42 | public AbstractResponse getObject() { 43 | return object; 44 | } 45 | 46 | public String getField() { 47 | return field; 48 | } 49 | 50 | public JsonElement getValue() { 51 | return value; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /support/src/main/java/com/shopify/graphql/support/TopLevelResponse.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 Shopify 3 | * Copyright 2019 Adobe 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a 6 | * copy of this software and associated documentation files (the "Software"), 7 | * to deal in the Software without restriction, including without limitation 8 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 | * and/or sell copies of the Software, and to permit persons to whom the Software 10 | * is furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in all 13 | * copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 19 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | */ 22 | 23 | package com.shopify.graphql.support; 24 | 25 | import java.util.ArrayList; 26 | import java.util.List; 27 | 28 | import com.google.gson.JsonElement; 29 | import com.google.gson.JsonObject; 30 | 31 | /** 32 | * Created by eapache on 2015-11-17. 33 | */ 34 | public class TopLevelResponse { 35 | private static final String DATA_KEY = "data"; 36 | private static final String ERRORS_KEY = "errors"; 37 | private JsonObject data = null; 38 | private final List errors = new ArrayList<>(); 39 | 40 | public TopLevelResponse(JsonObject fields) throws InvalidGraphQLException { 41 | JsonElement errorsElement = fields.get(ERRORS_KEY); 42 | JsonElement dataElement = fields.get(DATA_KEY); 43 | if (dataElement != null && dataElement.isJsonNull()) { 44 | dataElement = null; 45 | } 46 | 47 | if (errorsElement == null && dataElement == null) { 48 | throw new InvalidGraphQLException("Response must contain a top-level 'data' or 'errors' entry"); 49 | } 50 | 51 | if (dataElement != null) { 52 | if (!dataElement.isJsonObject()) { 53 | throw new InvalidGraphQLException("'data' entry in response must be a map"); 54 | } 55 | this.data = dataElement.getAsJsonObject(); 56 | } 57 | 58 | if (errorsElement != null) { 59 | if (!errorsElement.isJsonArray()) { 60 | throw new InvalidGraphQLException("'errors' entry in response must be an array"); 61 | } 62 | for (JsonElement error : errorsElement.getAsJsonArray()) { 63 | errors.add(new Error(error.isJsonObject() ? error.getAsJsonObject() : new JsonObject())); 64 | } 65 | } 66 | } 67 | 68 | public JsonObject getData() { 69 | return data; 70 | } 71 | 72 | public List getErrors() { 73 | return errors; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /support/src/test/java/com/shopify/graphql/support/AnnotationTest.java: -------------------------------------------------------------------------------- 1 | package com.shopify.graphql.support; 2 | 3 | import org.junit.Test; 4 | 5 | import java.lang.annotation.Annotation; 6 | import java.lang.reflect.Method; 7 | 8 | import static junit.framework.Assert.assertEquals; 9 | import static org.junit.Assert.assertTrue; 10 | 11 | import com.shopify.graphql.support.Generated; 12 | 13 | public class AnnotationTest { 14 | @Test 15 | public void testAppliesAnnotation() throws Exception { 16 | Class obj = Generated.class; 17 | boolean foundNullable = false; 18 | for (Class klass: obj.getDeclaredClasses()) { 19 | for (Method method : klass.getDeclaredMethods()) { 20 | if (method.isAnnotationPresent(Nullable.class)) { 21 | foundNullable = true; 22 | break; 23 | } 24 | } 25 | } 26 | assertTrue("Should have found a class with @Nullable annotation in Generated.java", foundNullable); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /support/src/test/java/com/shopify/graphql/support/Generated.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * 3 | * Copyright 2020 Adobe. All rights reserved. 4 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. You may obtain a copy 6 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software distributed under 9 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 10 | * OF ANY KIND, either express or implied. See the License for the specific language 11 | * governing permissions and limitations under the License. 12 | * 13 | ******************************************************************************/ 14 | 15 | package com.shopify.graphql.support; 16 | 17 | import com.google.gson.Gson; 18 | import com.google.gson.GsonBuilder; 19 | import com.google.gson.JsonElement; 20 | import com.google.gson.JsonObject; 21 | import com.shopify.graphql.support.AbstractResponse; 22 | import com.shopify.graphql.support.Arguments; 23 | import com.shopify.graphql.support.Error; 24 | import com.shopify.graphql.support.AbstractQuery; 25 | import com.shopify.graphql.support.SchemaViolationError; 26 | import com.shopify.graphql.support.TopLevelResponse; 27 | import com.shopify.graphql.support.Input; 28 | 29 | import com.shopify.graphql.support.ID; 30 | 31 | import com.shopify.graphql.support.Nullable; 32 | 33 | import java.time.LocalDateTime; 34 | 35 | import java.io.Serializable; 36 | import java.util.ArrayList; 37 | import java.util.List; 38 | import java.util.Map; 39 | 40 | public class Generated { 41 | public static QueryRootQuery query(QueryRootQueryDefinition queryDef) { 42 | StringBuilder queryString = new StringBuilder("{"); 43 | QueryRootQuery query = new QueryRootQuery(queryString); 44 | queryDef.define(query); 45 | queryString.append('}'); 46 | return query; 47 | } 48 | 49 | public static class QueryResponse { 50 | private TopLevelResponse response; 51 | private QueryRoot data; 52 | 53 | public QueryResponse(TopLevelResponse response) throws SchemaViolationError { 54 | this.response = response; 55 | this.data = response.getData() != null ? new QueryRoot(response.getData()) : null; 56 | } 57 | 58 | public QueryRoot getData() { 59 | return data; 60 | } 61 | 62 | public List getErrors() { 63 | return response.getErrors(); 64 | } 65 | 66 | public String toJson() { 67 | return new Gson().toJson(response); 68 | } 69 | 70 | public String prettyPrintJson() { 71 | final Gson gson = new GsonBuilder().setPrettyPrinting().create(); 72 | return gson.toJson(response); 73 | } 74 | 75 | public static QueryResponse fromJson(String json) throws SchemaViolationError { 76 | final TopLevelResponse response = new Gson().fromJson(json, TopLevelResponse.class); 77 | return new QueryResponse(response); 78 | } 79 | } 80 | 81 | public static MutationQuery mutation(MutationQueryDefinition queryDef) { 82 | StringBuilder queryString = new StringBuilder("mutation{"); 83 | MutationQuery query = new MutationQuery(queryString); 84 | queryDef.define(query); 85 | queryString.append('}'); 86 | return query; 87 | } 88 | 89 | public static class MutationResponse { 90 | private TopLevelResponse response; 91 | private Mutation data; 92 | 93 | public MutationResponse(TopLevelResponse response) throws SchemaViolationError { 94 | this.response = response; 95 | this.data = response.getData() != null ? new Mutation(response.getData()) : null; 96 | } 97 | 98 | public Mutation getData() { 99 | return data; 100 | } 101 | 102 | public List getErrors() { 103 | return response.getErrors(); 104 | } 105 | 106 | public String toJson() { 107 | return new Gson().toJson(response); 108 | } 109 | 110 | public String prettyPrintJson() { 111 | final Gson gson = new GsonBuilder().setPrettyPrinting().create(); 112 | return gson.toJson(response); 113 | } 114 | 115 | public static MutationResponse fromJson(String json) throws SchemaViolationError { 116 | final TopLevelResponse response = new Gson().fromJson(json, TopLevelResponse.class); 117 | return new MutationResponse(response); 118 | } 119 | } 120 | 121 | public interface EntryQueryDefinition { 122 | void define(EntryQuery _queryBuilder); 123 | } 124 | 125 | public static class EntryQuery extends AbstractQuery { 126 | EntryQuery(StringBuilder _queryBuilder) { 127 | super(_queryBuilder); 128 | 129 | startField("__typename"); 130 | } 131 | 132 | public EntryQuery key() { 133 | startField("key"); 134 | 135 | return this; 136 | } 137 | 138 | public EntryQuery ttl() { 139 | startField("ttl"); 140 | 141 | return this; 142 | } 143 | 144 | public EntryQuery onIntegerEntry(IntegerEntryQueryDefinition queryDef) { 145 | startInlineFragment("IntegerEntry"); 146 | queryDef.define(new IntegerEntryQuery(_queryBuilder)); 147 | _queryBuilder.append('}'); 148 | return this; 149 | } 150 | 151 | public EntryQuery onStringEntry(StringEntryQueryDefinition queryDef) { 152 | startInlineFragment("StringEntry"); 153 | queryDef.define(new StringEntryQuery(_queryBuilder)); 154 | _queryBuilder.append('}'); 155 | return this; 156 | } 157 | } 158 | 159 | public interface Entry { 160 | String getGraphQlTypeName(); 161 | 162 | String getKey(); 163 | 164 | LocalDateTime getTtl(); 165 | } 166 | 167 | public static class UnknownEntry extends AbstractResponse implements Entry { 168 | public UnknownEntry() { 169 | } 170 | 171 | public UnknownEntry(JsonObject fields) throws SchemaViolationError { 172 | for (Map.Entry field : fields.entrySet()) { 173 | String key = field.getKey(); 174 | String fieldName = getFieldName(key); 175 | switch (fieldName) { 176 | case "key": { 177 | responseData.put(key, jsonAsString(field.getValue(), key)); 178 | 179 | break; 180 | } 181 | 182 | case "ttl": { 183 | LocalDateTime optional1 = null; 184 | if (!field.getValue().isJsonNull()) { 185 | optional1 = LocalDateTime.parse(jsonAsString(field.getValue(), key)); 186 | } 187 | 188 | responseData.put(key, optional1); 189 | 190 | break; 191 | } 192 | 193 | case "__typename": { 194 | responseData.put(key, jsonAsString(field.getValue(), key)); 195 | break; 196 | } 197 | default: { 198 | throw new SchemaViolationError(this, key, field.getValue()); 199 | } 200 | } 201 | } 202 | } 203 | 204 | public static Entry create(JsonObject fields) throws SchemaViolationError { 205 | String typeName = fields.getAsJsonPrimitive("__typename").getAsString(); 206 | switch (typeName) { 207 | case "IntegerEntry": { 208 | return new IntegerEntry(fields); 209 | } 210 | 211 | case "StringEntry": { 212 | return new StringEntry(fields); 213 | } 214 | 215 | default: { 216 | return new UnknownEntry(fields); 217 | } 218 | } 219 | } 220 | 221 | public String getGraphQlTypeName() { 222 | return (String) get("__typename"); 223 | } 224 | 225 | public String getKey() { 226 | return (String) get("key"); 227 | } 228 | 229 | public UnknownEntry setKey(String arg) { 230 | optimisticData.put(getKey("key"), arg); 231 | return this; 232 | } 233 | 234 | @Nullable 235 | public LocalDateTime getTtl() { 236 | return (LocalDateTime) get("ttl"); 237 | } 238 | 239 | public UnknownEntry setTtl(LocalDateTime arg) { 240 | optimisticData.put(getKey("ttl"), arg); 241 | return this; 242 | } 243 | 244 | public boolean unwrapsToObject(String key) { 245 | switch (getFieldName(key)) { 246 | case "key": return false; 247 | 248 | case "ttl": return false; 249 | 250 | default: return false; 251 | } 252 | } 253 | } 254 | 255 | public interface EntryUnionQueryDefinition { 256 | void define(EntryUnionQuery _queryBuilder); 257 | } 258 | 259 | public static class EntryUnionQuery extends AbstractQuery { 260 | EntryUnionQuery(StringBuilder _queryBuilder) { 261 | super(_queryBuilder); 262 | 263 | startField("__typename"); 264 | } 265 | 266 | public EntryUnionQuery onIntegerEntry(IntegerEntryQueryDefinition queryDef) { 267 | startInlineFragment("IntegerEntry"); 268 | queryDef.define(new IntegerEntryQuery(_queryBuilder)); 269 | _queryBuilder.append('}'); 270 | return this; 271 | } 272 | 273 | public EntryUnionQuery onStringEntry(StringEntryQueryDefinition queryDef) { 274 | startInlineFragment("StringEntry"); 275 | queryDef.define(new StringEntryQuery(_queryBuilder)); 276 | _queryBuilder.append('}'); 277 | return this; 278 | } 279 | } 280 | 281 | public interface EntryUnion { 282 | String getGraphQlTypeName(); 283 | } 284 | 285 | public static class UnknownEntryUnion extends AbstractResponse implements EntryUnion { 286 | public UnknownEntryUnion() { 287 | } 288 | 289 | public UnknownEntryUnion(JsonObject fields) throws SchemaViolationError { 290 | for (Map.Entry field : fields.entrySet()) { 291 | String key = field.getKey(); 292 | String fieldName = getFieldName(key); 293 | switch (fieldName) { 294 | case "__typename": { 295 | responseData.put(key, jsonAsString(field.getValue(), key)); 296 | break; 297 | } 298 | default: { 299 | throw new SchemaViolationError(this, key, field.getValue()); 300 | } 301 | } 302 | } 303 | } 304 | 305 | public static EntryUnion create(JsonObject fields) throws SchemaViolationError { 306 | String typeName = fields.getAsJsonPrimitive("__typename").getAsString(); 307 | switch (typeName) { 308 | case "IntegerEntry": { 309 | return new IntegerEntry(fields); 310 | } 311 | 312 | case "StringEntry": { 313 | return new StringEntry(fields); 314 | } 315 | 316 | default: { 317 | return new UnknownEntryUnion(fields); 318 | } 319 | } 320 | } 321 | 322 | public String getGraphQlTypeName() { 323 | return (String) get("__typename"); 324 | } 325 | 326 | public boolean unwrapsToObject(String key) { 327 | switch (getFieldName(key)) { 328 | default: return false; 329 | } 330 | } 331 | } 332 | 333 | public interface IntegerEntryQueryDefinition { 334 | void define(IntegerEntryQuery _queryBuilder); 335 | } 336 | 337 | public static class IntegerEntryQuery extends AbstractQuery { 338 | IntegerEntryQuery(StringBuilder _queryBuilder) { 339 | super(_queryBuilder); 340 | } 341 | 342 | public IntegerEntryQuery key() { 343 | startField("key"); 344 | 345 | return this; 346 | } 347 | 348 | public IntegerEntryQuery ttl() { 349 | startField("ttl"); 350 | 351 | return this; 352 | } 353 | 354 | public IntegerEntryQuery value() { 355 | startField("value"); 356 | 357 | return this; 358 | } 359 | } 360 | 361 | public static class IntegerEntry extends AbstractResponse implements Entry, EntryUnion { 362 | public IntegerEntry() { 363 | } 364 | 365 | public IntegerEntry(JsonObject fields) throws SchemaViolationError { 366 | for (Map.Entry field : fields.entrySet()) { 367 | String key = field.getKey(); 368 | String fieldName = getFieldName(key); 369 | switch (fieldName) { 370 | case "key": { 371 | responseData.put(key, jsonAsString(field.getValue(), key)); 372 | 373 | break; 374 | } 375 | 376 | case "ttl": { 377 | LocalDateTime optional1 = null; 378 | if (!field.getValue().isJsonNull()) { 379 | optional1 = LocalDateTime.parse(jsonAsString(field.getValue(), key)); 380 | } 381 | 382 | responseData.put(key, optional1); 383 | 384 | break; 385 | } 386 | 387 | case "value": { 388 | responseData.put(key, jsonAsInteger(field.getValue(), key)); 389 | 390 | break; 391 | } 392 | 393 | case "__typename": { 394 | responseData.put(key, jsonAsString(field.getValue(), key)); 395 | break; 396 | } 397 | default: { 398 | throw new SchemaViolationError(this, key, field.getValue()); 399 | } 400 | } 401 | } 402 | } 403 | 404 | public String getGraphQlTypeName() { 405 | return "IntegerEntry"; 406 | } 407 | 408 | public String getKey() { 409 | return (String) get("key"); 410 | } 411 | 412 | public IntegerEntry setKey(String arg) { 413 | optimisticData.put(getKey("key"), arg); 414 | return this; 415 | } 416 | 417 | @Nullable 418 | public LocalDateTime getTtl() { 419 | return (LocalDateTime) get("ttl"); 420 | } 421 | 422 | public IntegerEntry setTtl(LocalDateTime arg) { 423 | optimisticData.put(getKey("ttl"), arg); 424 | return this; 425 | } 426 | 427 | public Integer getValue() { 428 | return (Integer) get("value"); 429 | } 430 | 431 | public IntegerEntry setValue(Integer arg) { 432 | optimisticData.put(getKey("value"), arg); 433 | return this; 434 | } 435 | 436 | public boolean unwrapsToObject(String key) { 437 | switch (getFieldName(key)) { 438 | case "key": return false; 439 | 440 | case "ttl": return false; 441 | 442 | case "value": return false; 443 | 444 | default: return false; 445 | } 446 | } 447 | } 448 | 449 | /** 450 | * Types of values that can be stored in a key 451 | */ 452 | public enum KeyType { 453 | INTEGER, 454 | 455 | STRING, 456 | 457 | UNKNOWN_VALUE; 458 | 459 | public static KeyType fromGraphQl(String value) { 460 | if (value == null) { 461 | return null; 462 | } 463 | 464 | switch (value) { 465 | case "INTEGER": { 466 | return INTEGER; 467 | } 468 | 469 | case "STRING": { 470 | return STRING; 471 | } 472 | 473 | default: { 474 | return UNKNOWN_VALUE; 475 | } 476 | } 477 | } 478 | public String toString() { 479 | switch (this) { 480 | case INTEGER: { 481 | return "INTEGER"; 482 | } 483 | 484 | case STRING: { 485 | return "STRING"; 486 | } 487 | 488 | default: { 489 | return ""; 490 | } 491 | } 492 | } 493 | } 494 | 495 | public interface MutationQueryDefinition { 496 | void define(MutationQuery _queryBuilder); 497 | } 498 | 499 | public static class MutationQuery extends AbstractQuery { 500 | MutationQuery(StringBuilder _queryBuilder) { 501 | super(_queryBuilder); 502 | } 503 | 504 | public MutationQuery setInteger(SetIntegerInput input) { 505 | startField("set_integer"); 506 | 507 | _queryBuilder.append("(input:"); 508 | input.appendTo(_queryBuilder); 509 | 510 | _queryBuilder.append(')'); 511 | 512 | return this; 513 | } 514 | 515 | public MutationQuery setString(String key, String value) { 516 | startField("set_string"); 517 | 518 | _queryBuilder.append("(key:"); 519 | AbstractQuery.appendQuotedString(_queryBuilder, key.toString()); 520 | 521 | _queryBuilder.append(",value:"); 522 | AbstractQuery.appendQuotedString(_queryBuilder, value.toString()); 523 | 524 | _queryBuilder.append(')'); 525 | 526 | return this; 527 | } 528 | 529 | public class SetStringWithDefaultArguments extends Arguments { 530 | SetStringWithDefaultArguments(StringBuilder _queryBuilder) { 531 | super(_queryBuilder, false); 532 | } 533 | 534 | public SetStringWithDefaultArguments value(String value) { 535 | if (value != null) { 536 | startArgument("value"); 537 | AbstractQuery.appendQuotedString(_queryBuilder, value.toString()); 538 | } 539 | return this; 540 | } 541 | } 542 | 543 | public interface SetStringWithDefaultArgumentsDefinition { 544 | void define(SetStringWithDefaultArguments args); 545 | } 546 | 547 | public MutationQuery setStringWithDefault(String key) { 548 | return setStringWithDefault(key, args -> {}); 549 | } 550 | 551 | public MutationQuery setStringWithDefault(String key, SetStringWithDefaultArgumentsDefinition argsDef) { 552 | startField("set_string_with_default"); 553 | 554 | _queryBuilder.append("(key:"); 555 | AbstractQuery.appendQuotedString(_queryBuilder, key.toString()); 556 | 557 | argsDef.define(new SetStringWithDefaultArguments(_queryBuilder)); 558 | 559 | _queryBuilder.append(')'); 560 | 561 | return this; 562 | } 563 | 564 | public String toString() { 565 | return _queryBuilder.toString(); 566 | } 567 | } 568 | 569 | public static class Mutation extends AbstractResponse { 570 | public Mutation() { 571 | } 572 | 573 | public Mutation(JsonObject fields) throws SchemaViolationError { 574 | for (Map.Entry field : fields.entrySet()) { 575 | String key = field.getKey(); 576 | String fieldName = getFieldName(key); 577 | switch (fieldName) { 578 | case "set_integer": { 579 | responseData.put(key, jsonAsBoolean(field.getValue(), key)); 580 | 581 | break; 582 | } 583 | 584 | case "set_string": { 585 | responseData.put(key, jsonAsBoolean(field.getValue(), key)); 586 | 587 | break; 588 | } 589 | 590 | case "set_string_with_default": { 591 | responseData.put(key, jsonAsBoolean(field.getValue(), key)); 592 | 593 | break; 594 | } 595 | 596 | case "__typename": { 597 | responseData.put(key, jsonAsString(field.getValue(), key)); 598 | break; 599 | } 600 | default: { 601 | throw new SchemaViolationError(this, key, field.getValue()); 602 | } 603 | } 604 | } 605 | } 606 | 607 | public String getGraphQlTypeName() { 608 | return "Mutation"; 609 | } 610 | 611 | public Boolean getSetInteger() { 612 | return (Boolean) get("set_integer"); 613 | } 614 | 615 | public Mutation setSetInteger(Boolean arg) { 616 | optimisticData.put(getKey("set_integer"), arg); 617 | return this; 618 | } 619 | 620 | public Boolean getSetString() { 621 | return (Boolean) get("set_string"); 622 | } 623 | 624 | public Mutation setSetString(Boolean arg) { 625 | optimisticData.put(getKey("set_string"), arg); 626 | return this; 627 | } 628 | 629 | public Boolean getSetStringWithDefault() { 630 | return (Boolean) get("set_string_with_default"); 631 | } 632 | 633 | public Mutation setSetStringWithDefault(Boolean arg) { 634 | optimisticData.put(getKey("set_string_with_default"), arg); 635 | return this; 636 | } 637 | 638 | public boolean unwrapsToObject(String key) { 639 | switch (getFieldName(key)) { 640 | case "set_integer": return false; 641 | 642 | case "set_string": return false; 643 | 644 | case "set_string_with_default": return false; 645 | 646 | default: return false; 647 | } 648 | } 649 | } 650 | 651 | public interface QueryRootQueryDefinition { 652 | void define(QueryRootQuery _queryBuilder); 653 | } 654 | 655 | public static class QueryRootQuery extends AbstractQuery { 656 | QueryRootQuery(StringBuilder _queryBuilder) { 657 | super(_queryBuilder); 658 | } 659 | 660 | public class EntriesArguments extends Arguments { 661 | EntriesArguments(StringBuilder _queryBuilder) { 662 | super(_queryBuilder, false); 663 | } 664 | 665 | public EntriesArguments after(String value) { 666 | if (value != null) { 667 | startArgument("after"); 668 | AbstractQuery.appendQuotedString(_queryBuilder, value.toString()); 669 | } 670 | return this; 671 | } 672 | } 673 | 674 | public interface EntriesArgumentsDefinition { 675 | void define(EntriesArguments args); 676 | } 677 | 678 | public QueryRootQuery entries(int first, EntryQueryDefinition queryDef) { 679 | return entries(first, args -> {}, queryDef); 680 | } 681 | 682 | public QueryRootQuery entries(int first, EntriesArgumentsDefinition argsDef, EntryQueryDefinition queryDef) { 683 | startField("entries"); 684 | 685 | _queryBuilder.append("(first:"); 686 | _queryBuilder.append(first); 687 | 688 | argsDef.define(new EntriesArguments(_queryBuilder)); 689 | 690 | _queryBuilder.append(')'); 691 | 692 | _queryBuilder.append('{'); 693 | queryDef.define(new EntryQuery(_queryBuilder)); 694 | _queryBuilder.append('}'); 695 | 696 | return this; 697 | } 698 | 699 | /** 700 | * Get an entry of any type with the given key 701 | */ 702 | public QueryRootQuery entry(String key, EntryQueryDefinition queryDef) { 703 | startField("entry"); 704 | 705 | _queryBuilder.append("(key:"); 706 | AbstractQuery.appendQuotedString(_queryBuilder, key.toString()); 707 | 708 | _queryBuilder.append(')'); 709 | 710 | _queryBuilder.append('{'); 711 | queryDef.define(new EntryQuery(_queryBuilder)); 712 | _queryBuilder.append('}'); 713 | 714 | return this; 715 | } 716 | 717 | /** 718 | * Get an entry of any type with the given key as a union 719 | */ 720 | public QueryRootQuery entryUnion(String key, EntryUnionQueryDefinition queryDef) { 721 | startField("entry_union"); 722 | 723 | _queryBuilder.append("(key:"); 724 | AbstractQuery.appendQuotedString(_queryBuilder, key.toString()); 725 | 726 | _queryBuilder.append(')'); 727 | 728 | _queryBuilder.append('{'); 729 | queryDef.define(new EntryUnionQuery(_queryBuilder)); 730 | _queryBuilder.append('}'); 731 | 732 | return this; 733 | } 734 | 735 | /** 736 | * Get a integer value with the given key 737 | */ 738 | public QueryRootQuery integer(String key) { 739 | startField("integer"); 740 | 741 | _queryBuilder.append("(key:"); 742 | AbstractQuery.appendQuotedString(_queryBuilder, key.toString()); 743 | 744 | _queryBuilder.append(')'); 745 | 746 | return this; 747 | } 748 | 749 | public class KeysArguments extends Arguments { 750 | KeysArguments(StringBuilder _queryBuilder) { 751 | super(_queryBuilder, false); 752 | } 753 | 754 | public KeysArguments after(String value) { 755 | if (value != null) { 756 | startArgument("after"); 757 | AbstractQuery.appendQuotedString(_queryBuilder, value.toString()); 758 | } 759 | return this; 760 | } 761 | 762 | public KeysArguments type(KeyType value) { 763 | if (value != null) { 764 | startArgument("type"); 765 | _queryBuilder.append(value.toString()); 766 | } 767 | return this; 768 | } 769 | } 770 | 771 | public interface KeysArgumentsDefinition { 772 | void define(KeysArguments args); 773 | } 774 | 775 | public QueryRootQuery keys(int first) { 776 | return keys(first, args -> {}); 777 | } 778 | 779 | public QueryRootQuery keys(int first, KeysArgumentsDefinition argsDef) { 780 | startField("keys"); 781 | 782 | _queryBuilder.append("(first:"); 783 | _queryBuilder.append(first); 784 | 785 | argsDef.define(new KeysArguments(_queryBuilder)); 786 | 787 | _queryBuilder.append(')'); 788 | 789 | return this; 790 | } 791 | 792 | /** 793 | * Get a string value with the given key 794 | */ 795 | public QueryRootQuery string(String key) { 796 | startField("string"); 797 | 798 | _queryBuilder.append("(key:"); 799 | AbstractQuery.appendQuotedString(_queryBuilder, key.toString()); 800 | 801 | _queryBuilder.append(')'); 802 | 803 | return this; 804 | } 805 | 806 | public QueryRootQuery ttl(String key) { 807 | startField("ttl"); 808 | 809 | _queryBuilder.append("(key:"); 810 | AbstractQuery.appendQuotedString(_queryBuilder, key.toString()); 811 | 812 | _queryBuilder.append(')'); 813 | 814 | return this; 815 | } 816 | 817 | public QueryRootQuery type(String key) { 818 | startField("type"); 819 | 820 | _queryBuilder.append("(key:"); 821 | AbstractQuery.appendQuotedString(_queryBuilder, key.toString()); 822 | 823 | _queryBuilder.append(')'); 824 | 825 | return this; 826 | } 827 | 828 | public QueryRootQuery version() { 829 | startField("version"); 830 | 831 | return this; 832 | } 833 | 834 | public String toString() { 835 | return _queryBuilder.toString(); 836 | } 837 | } 838 | 839 | public static class QueryRoot extends AbstractResponse { 840 | public QueryRoot() { 841 | } 842 | 843 | public QueryRoot(JsonObject fields) throws SchemaViolationError { 844 | for (Map.Entry field : fields.entrySet()) { 845 | String key = field.getKey(); 846 | String fieldName = getFieldName(key); 847 | switch (fieldName) { 848 | case "entries": { 849 | List list1 = new ArrayList<>(); 850 | for (JsonElement element1 : jsonAsArray(field.getValue(), key)) { 851 | list1.add(UnknownEntry.create(jsonAsObject(element1, key))); 852 | } 853 | 854 | responseData.put(key, list1); 855 | 856 | break; 857 | } 858 | 859 | case "entry": { 860 | Entry optional1 = null; 861 | if (!field.getValue().isJsonNull()) { 862 | optional1 = UnknownEntry.create(jsonAsObject(field.getValue(), key)); 863 | } 864 | 865 | responseData.put(key, optional1); 866 | 867 | break; 868 | } 869 | 870 | case "entry_union": { 871 | EntryUnion optional1 = null; 872 | if (!field.getValue().isJsonNull()) { 873 | optional1 = UnknownEntryUnion.create(jsonAsObject(field.getValue(), key)); 874 | } 875 | 876 | responseData.put(key, optional1); 877 | 878 | break; 879 | } 880 | 881 | case "integer": { 882 | Integer optional1 = null; 883 | if (!field.getValue().isJsonNull()) { 884 | optional1 = jsonAsInteger(field.getValue(), key); 885 | } 886 | 887 | responseData.put(key, optional1); 888 | 889 | break; 890 | } 891 | 892 | case "keys": { 893 | List list1 = new ArrayList<>(); 894 | for (JsonElement element1 : jsonAsArray(field.getValue(), key)) { 895 | list1.add(jsonAsString(element1, key)); 896 | } 897 | 898 | responseData.put(key, list1); 899 | 900 | break; 901 | } 902 | 903 | case "string": { 904 | String optional1 = null; 905 | if (!field.getValue().isJsonNull()) { 906 | optional1 = jsonAsString(field.getValue(), key); 907 | } 908 | 909 | responseData.put(key, optional1); 910 | 911 | break; 912 | } 913 | 914 | case "ttl": { 915 | LocalDateTime optional1 = null; 916 | if (!field.getValue().isJsonNull()) { 917 | optional1 = LocalDateTime.parse(jsonAsString(field.getValue(), key)); 918 | } 919 | 920 | responseData.put(key, optional1); 921 | 922 | break; 923 | } 924 | 925 | case "type": { 926 | KeyType optional1 = null; 927 | if (!field.getValue().isJsonNull()) { 928 | optional1 = KeyType.fromGraphQl(jsonAsString(field.getValue(), key)); 929 | } 930 | 931 | responseData.put(key, optional1); 932 | 933 | break; 934 | } 935 | 936 | case "version": { 937 | String optional1 = null; 938 | if (!field.getValue().isJsonNull()) { 939 | optional1 = jsonAsString(field.getValue(), key); 940 | } 941 | 942 | responseData.put(key, optional1); 943 | 944 | break; 945 | } 946 | 947 | case "__typename": { 948 | responseData.put(key, jsonAsString(field.getValue(), key)); 949 | break; 950 | } 951 | default: { 952 | throw new SchemaViolationError(this, key, field.getValue()); 953 | } 954 | } 955 | } 956 | } 957 | 958 | public String getGraphQlTypeName() { 959 | return "QueryRoot"; 960 | } 961 | 962 | public List getEntries() { 963 | return (List) get("entries"); 964 | } 965 | 966 | public QueryRoot setEntries(List arg) { 967 | optimisticData.put(getKey("entries"), arg); 968 | return this; 969 | } 970 | 971 | /** 972 | * Get an entry of any type with the given key 973 | */ 974 | @Nullable 975 | public Entry getEntry() { 976 | return (Entry) get("entry"); 977 | } 978 | 979 | public QueryRoot setEntry(Entry arg) { 980 | optimisticData.put(getKey("entry"), arg); 981 | return this; 982 | } 983 | 984 | /** 985 | * Get an entry of any type with the given key as a union 986 | */ 987 | @Nullable 988 | public EntryUnion getEntryUnion() { 989 | return (EntryUnion) get("entry_union"); 990 | } 991 | 992 | public QueryRoot setEntryUnion(EntryUnion arg) { 993 | optimisticData.put(getKey("entry_union"), arg); 994 | return this; 995 | } 996 | 997 | /** 998 | * Get a integer value with the given key 999 | */ 1000 | @Nullable 1001 | public Integer getInteger() { 1002 | return (Integer) get("integer"); 1003 | } 1004 | 1005 | public QueryRoot setInteger(Integer arg) { 1006 | optimisticData.put(getKey("integer"), arg); 1007 | return this; 1008 | } 1009 | 1010 | public List getKeys() { 1011 | return (List) get("keys"); 1012 | } 1013 | 1014 | public QueryRoot setKeys(List arg) { 1015 | optimisticData.put(getKey("keys"), arg); 1016 | return this; 1017 | } 1018 | 1019 | /** 1020 | * Get a string value with the given key 1021 | */ 1022 | @Nullable 1023 | public String getString() { 1024 | return (String) get("string"); 1025 | } 1026 | 1027 | public QueryRoot setString(String arg) { 1028 | optimisticData.put(getKey("string"), arg); 1029 | return this; 1030 | } 1031 | 1032 | @Nullable 1033 | public LocalDateTime getTtl() { 1034 | return (LocalDateTime) get("ttl"); 1035 | } 1036 | 1037 | public QueryRoot setTtl(LocalDateTime arg) { 1038 | optimisticData.put(getKey("ttl"), arg); 1039 | return this; 1040 | } 1041 | 1042 | @Nullable 1043 | public KeyType getType() { 1044 | return (KeyType) get("type"); 1045 | } 1046 | 1047 | public QueryRoot setType(KeyType arg) { 1048 | optimisticData.put(getKey("type"), arg); 1049 | return this; 1050 | } 1051 | 1052 | @Nullable 1053 | public String getVersion() { 1054 | return (String) get("version"); 1055 | } 1056 | 1057 | public QueryRoot setVersion(String arg) { 1058 | optimisticData.put(getKey("version"), arg); 1059 | return this; 1060 | } 1061 | 1062 | public boolean unwrapsToObject(String key) { 1063 | switch (getFieldName(key)) { 1064 | case "entries": return false; 1065 | 1066 | case "entry": return false; 1067 | 1068 | case "entry_union": return false; 1069 | 1070 | case "integer": return false; 1071 | 1072 | case "keys": return false; 1073 | 1074 | case "string": return false; 1075 | 1076 | case "ttl": return false; 1077 | 1078 | case "type": return false; 1079 | 1080 | case "version": return false; 1081 | 1082 | default: return false; 1083 | } 1084 | } 1085 | } 1086 | 1087 | public static class SetIntegerInput implements Serializable { 1088 | private String key; 1089 | 1090 | private int value; 1091 | 1092 | private Input ttl = Input.undefined(); 1093 | 1094 | private Input negate = Input.undefined(); 1095 | 1096 | private Input apiClient = Input.undefined(); 1097 | 1098 | public SetIntegerInput(String key, int value) { 1099 | this.key = key; 1100 | 1101 | this.value = value; 1102 | } 1103 | 1104 | public String getKey() { 1105 | return key; 1106 | } 1107 | 1108 | public SetIntegerInput setKey(String key) { 1109 | this.key = key; 1110 | return this; 1111 | } 1112 | 1113 | public int getValue() { 1114 | return value; 1115 | } 1116 | 1117 | public SetIntegerInput setValue(int value) { 1118 | this.value = value; 1119 | return this; 1120 | } 1121 | 1122 | @Nullable 1123 | public LocalDateTime getTtl() { 1124 | return ttl.getValue(); 1125 | } 1126 | 1127 | public Input getTtlInput() { 1128 | return ttl; 1129 | } 1130 | 1131 | public SetIntegerInput setTtl(@Nullable LocalDateTime ttl) { 1132 | this.ttl = Input.optional(ttl); 1133 | return this; 1134 | } 1135 | 1136 | public SetIntegerInput setTtlInput(Input ttl) { 1137 | if (ttl == null) { 1138 | throw new IllegalArgumentException("Input can not be null"); 1139 | } 1140 | this.ttl = ttl; 1141 | return this; 1142 | } 1143 | 1144 | @Nullable 1145 | public Boolean getNegate() { 1146 | return negate.getValue(); 1147 | } 1148 | 1149 | public Input getNegateInput() { 1150 | return negate; 1151 | } 1152 | 1153 | public SetIntegerInput setNegate(@Nullable Boolean negate) { 1154 | this.negate = Input.optional(negate); 1155 | return this; 1156 | } 1157 | 1158 | public SetIntegerInput setNegateInput(Input negate) { 1159 | if (negate == null) { 1160 | throw new IllegalArgumentException("Input can not be null"); 1161 | } 1162 | this.negate = negate; 1163 | return this; 1164 | } 1165 | 1166 | @Nullable 1167 | public String getApiClient() { 1168 | return apiClient.getValue(); 1169 | } 1170 | 1171 | public Input getApiClientInput() { 1172 | return apiClient; 1173 | } 1174 | 1175 | public SetIntegerInput setApiClient(@Nullable String apiClient) { 1176 | this.apiClient = Input.optional(apiClient); 1177 | return this; 1178 | } 1179 | 1180 | public SetIntegerInput setApiClientInput(Input apiClient) { 1181 | if (apiClient == null) { 1182 | throw new IllegalArgumentException("Input can not be null"); 1183 | } 1184 | this.apiClient = apiClient; 1185 | return this; 1186 | } 1187 | 1188 | public void appendTo(StringBuilder _queryBuilder) { 1189 | String separator = ""; 1190 | _queryBuilder.append('{'); 1191 | 1192 | _queryBuilder.append(separator); 1193 | separator = ","; 1194 | _queryBuilder.append("key:"); 1195 | AbstractQuery.appendQuotedString(_queryBuilder, key.toString()); 1196 | 1197 | _queryBuilder.append(separator); 1198 | separator = ","; 1199 | _queryBuilder.append("value:"); 1200 | _queryBuilder.append(value); 1201 | 1202 | if (this.ttl.isDefined()) { 1203 | _queryBuilder.append(separator); 1204 | separator = ","; 1205 | _queryBuilder.append("ttl:"); 1206 | if (ttl.getValue() != null) { 1207 | AbstractQuery.appendQuotedString(_queryBuilder, ttl.getValue().toString()); 1208 | } else { 1209 | _queryBuilder.append("null"); 1210 | } 1211 | } 1212 | 1213 | if (this.negate.isDefined()) { 1214 | _queryBuilder.append(separator); 1215 | separator = ","; 1216 | _queryBuilder.append("negate:"); 1217 | if (negate.getValue() != null) { 1218 | _queryBuilder.append(negate.getValue()); 1219 | } else { 1220 | _queryBuilder.append("null"); 1221 | } 1222 | } 1223 | 1224 | if (this.apiClient.isDefined()) { 1225 | _queryBuilder.append(separator); 1226 | separator = ","; 1227 | _queryBuilder.append("api_client:"); 1228 | if (apiClient.getValue() != null) { 1229 | AbstractQuery.appendQuotedString(_queryBuilder, apiClient.getValue().toString()); 1230 | } else { 1231 | _queryBuilder.append("null"); 1232 | } 1233 | } 1234 | 1235 | _queryBuilder.append('}'); 1236 | } 1237 | } 1238 | 1239 | public interface StringEntryQueryDefinition { 1240 | void define(StringEntryQuery _queryBuilder); 1241 | } 1242 | 1243 | public static class StringEntryQuery extends AbstractQuery { 1244 | StringEntryQuery(StringBuilder _queryBuilder) { 1245 | super(_queryBuilder); 1246 | } 1247 | 1248 | public StringEntryQuery key() { 1249 | startField("key"); 1250 | 1251 | return this; 1252 | } 1253 | 1254 | public StringEntryQuery ttl() { 1255 | startField("ttl"); 1256 | 1257 | return this; 1258 | } 1259 | 1260 | public StringEntryQuery value() { 1261 | startField("value"); 1262 | 1263 | return this; 1264 | } 1265 | } 1266 | 1267 | public static class StringEntry extends AbstractResponse implements Entry, EntryUnion { 1268 | public StringEntry() { 1269 | } 1270 | 1271 | public StringEntry(JsonObject fields) throws SchemaViolationError { 1272 | for (Map.Entry field : fields.entrySet()) { 1273 | String key = field.getKey(); 1274 | String fieldName = getFieldName(key); 1275 | switch (fieldName) { 1276 | case "key": { 1277 | responseData.put(key, jsonAsString(field.getValue(), key)); 1278 | 1279 | break; 1280 | } 1281 | 1282 | case "ttl": { 1283 | LocalDateTime optional1 = null; 1284 | if (!field.getValue().isJsonNull()) { 1285 | optional1 = LocalDateTime.parse(jsonAsString(field.getValue(), key)); 1286 | } 1287 | 1288 | responseData.put(key, optional1); 1289 | 1290 | break; 1291 | } 1292 | 1293 | case "value": { 1294 | responseData.put(key, jsonAsString(field.getValue(), key)); 1295 | 1296 | break; 1297 | } 1298 | 1299 | case "__typename": { 1300 | responseData.put(key, jsonAsString(field.getValue(), key)); 1301 | break; 1302 | } 1303 | default: { 1304 | throw new SchemaViolationError(this, key, field.getValue()); 1305 | } 1306 | } 1307 | } 1308 | } 1309 | 1310 | public String getGraphQlTypeName() { 1311 | return "StringEntry"; 1312 | } 1313 | 1314 | public String getKey() { 1315 | return (String) get("key"); 1316 | } 1317 | 1318 | public StringEntry setKey(String arg) { 1319 | optimisticData.put(getKey("key"), arg); 1320 | return this; 1321 | } 1322 | 1323 | @Nullable 1324 | public LocalDateTime getTtl() { 1325 | return (LocalDateTime) get("ttl"); 1326 | } 1327 | 1328 | public StringEntry setTtl(LocalDateTime arg) { 1329 | optimisticData.put(getKey("ttl"), arg); 1330 | return this; 1331 | } 1332 | 1333 | public String getValue() { 1334 | return (String) get("value"); 1335 | } 1336 | 1337 | public StringEntry setValue(String arg) { 1338 | optimisticData.put(getKey("value"), arg); 1339 | return this; 1340 | } 1341 | 1342 | public boolean unwrapsToObject(String key) { 1343 | switch (getFieldName(key)) { 1344 | case "key": return false; 1345 | 1346 | case "ttl": return false; 1347 | 1348 | case "value": return false; 1349 | 1350 | default: return false; 1351 | } 1352 | } 1353 | } 1354 | } 1355 | -------------------------------------------------------------------------------- /support/src/test/java/com/shopify/graphql/support/GeneratedMinimal.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * 3 | * Copyright 2020 Adobe. All rights reserved. 4 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. You may obtain a copy 6 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software distributed under 9 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 10 | * OF ANY KIND, either express or implied. See the License for the specific language 11 | * governing permissions and limitations under the License. 12 | * 13 | ******************************************************************************/ 14 | 15 | package com.shopify.graphql.support; 16 | 17 | import com.google.gson.Gson; 18 | import com.google.gson.GsonBuilder; 19 | import com.google.gson.JsonElement; 20 | import com.google.gson.JsonObject; 21 | import com.shopify.graphql.support.AbstractResponse; 22 | import com.shopify.graphql.support.Arguments; 23 | import com.shopify.graphql.support.Error; 24 | import com.shopify.graphql.support.AbstractQuery; 25 | import com.shopify.graphql.support.SchemaViolationError; 26 | import com.shopify.graphql.support.TopLevelResponse; 27 | import com.shopify.graphql.support.Input; 28 | 29 | import com.shopify.graphql.support.ID; 30 | 31 | import java.io.Serializable; 32 | import java.util.ArrayList; 33 | import java.util.List; 34 | import java.util.Map; 35 | 36 | public class GeneratedMinimal { 37 | public static QueryRootQuery query(QueryRootQueryDefinition queryDef) { 38 | StringBuilder queryString = new StringBuilder("{"); 39 | QueryRootQuery query = new QueryRootQuery(queryString); 40 | queryDef.define(query); 41 | queryString.append('}'); 42 | return query; 43 | } 44 | 45 | public static class QueryResponse { 46 | private TopLevelResponse response; 47 | private QueryRoot data; 48 | 49 | public QueryResponse(TopLevelResponse response) throws SchemaViolationError { 50 | this.response = response; 51 | this.data = response.getData() != null ? new QueryRoot(response.getData()) : null; 52 | } 53 | 54 | public QueryRoot getData() { 55 | return data; 56 | } 57 | 58 | public List getErrors() { 59 | return response.getErrors(); 60 | } 61 | 62 | public String toJson() { 63 | return new Gson().toJson(response); 64 | } 65 | 66 | public String prettyPrintJson() { 67 | final Gson gson = new GsonBuilder().setPrettyPrinting().create(); 68 | return gson.toJson(response); 69 | } 70 | 71 | public static QueryResponse fromJson(String json) throws SchemaViolationError { 72 | final TopLevelResponse response = new Gson().fromJson(json, TopLevelResponse.class); 73 | return new QueryResponse(response); 74 | } 75 | } 76 | 77 | public interface QueryRootQueryDefinition { 78 | void define(QueryRootQuery _queryBuilder); 79 | } 80 | 81 | public static class QueryRootQuery extends AbstractQuery { 82 | QueryRootQuery(StringBuilder _queryBuilder) { 83 | super(_queryBuilder); 84 | } 85 | 86 | public QueryRootQuery version() { 87 | startField("version"); 88 | 89 | return this; 90 | } 91 | 92 | public String toString() { 93 | return _queryBuilder.toString(); 94 | } 95 | } 96 | 97 | public static class QueryRoot extends AbstractResponse { 98 | public QueryRoot() { 99 | } 100 | 101 | public QueryRoot(JsonObject fields) throws SchemaViolationError { 102 | for (Map.Entry field : fields.entrySet()) { 103 | String key = field.getKey(); 104 | String fieldName = getFieldName(key); 105 | switch (fieldName) { 106 | case "version": { 107 | String optional1 = null; 108 | if (!field.getValue().isJsonNull()) { 109 | optional1 = jsonAsString(field.getValue(), key); 110 | } 111 | 112 | responseData.put(key, optional1); 113 | 114 | break; 115 | } 116 | 117 | case "__typename": { 118 | responseData.put(key, jsonAsString(field.getValue(), key)); 119 | break; 120 | } 121 | default: { 122 | throw new SchemaViolationError(this, key, field.getValue()); 123 | } 124 | } 125 | } 126 | } 127 | 128 | public String getGraphQlTypeName() { 129 | return "QueryRoot"; 130 | } 131 | 132 | public String getVersion() { 133 | return (String) get("version"); 134 | } 135 | 136 | public QueryRoot setVersion(String arg) { 137 | optimisticData.put(getKey("version"), arg); 138 | return this; 139 | } 140 | 141 | public boolean unwrapsToObject(String key) { 142 | switch (getFieldName(key)) { 143 | case "version": return false; 144 | 145 | default: return false; 146 | } 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /support/src/test/java/com/shopify/graphql/support/IntegrationTest.java: -------------------------------------------------------------------------------- 1 | package com.shopify.graphql.support; 2 | 3 | import java.time.LocalDate; 4 | import java.time.LocalDateTime; 5 | import java.util.Arrays; 6 | import java.util.List; 7 | 8 | import org.junit.Test; 9 | 10 | import static org.junit.Assert.assertEquals; 11 | import static org.junit.Assert.assertTrue; 12 | 13 | public class IntegrationTest { 14 | @Test 15 | public void testStringFieldQuery() throws Exception { 16 | String queryString = GeneratedMinimal.query(query -> query.version()).toString(); 17 | assertEquals("{version}", queryString); 18 | } 19 | 20 | @Test 21 | public void testRequiredArgQuery() throws Exception { 22 | String queryString = Generated.query(query -> query.string("user:1:name")).toString(); 23 | assertEquals("{string(key:\"user:1:name\")}", queryString); 24 | } 25 | 26 | @Test 27 | public void testOptionalArgQuery() throws Exception { 28 | String queryString = Generated.query(query -> query.keys(10, args -> args.after("cursor"))).toString(); 29 | assertEquals("{keys(first:10,after:\"cursor\")}", queryString); 30 | } 31 | 32 | @Test 33 | public void testInterfaceQuery() throws Exception { 34 | String queryString = Generated.query(query -> 35 | query.entry("user:1", entry -> entry 36 | .ttl() 37 | .onStringEntry(strEntry -> strEntry.value()) 38 | ) 39 | ).toString(); 40 | assertEquals("{entry(key:\"user:1\"){__typename,ttl,... on StringEntry{value}}}", queryString); 41 | } 42 | 43 | @Test 44 | public void testUnionQuery() throws Exception { 45 | String queryString = Generated.query(query -> 46 | query.entryUnion("user:1", entry -> entry 47 | .onStringEntry(strEntry -> strEntry.value()) 48 | ) 49 | ).toString(); 50 | assertEquals("{entry_union(key:\"user:1\"){__typename,... on StringEntry{value}}}", queryString); 51 | } 52 | 53 | @Test 54 | public void testEnumInput() throws Exception { 55 | String queryString = Generated.query(query -> query 56 | .keys(10, args -> args.type(Generated.KeyType.INTEGER)) 57 | ).toString(); 58 | assertEquals("{keys(first:10,type:INTEGER)}", queryString); 59 | } 60 | 61 | @Test 62 | public void testMutation() throws Exception { 63 | String queryString = Generated.mutation(mutation -> mutation.setString("foo", "bar")).toString(); 64 | assertEquals("mutation{set_string(key:\"foo\",value:\"bar\")}", queryString); 65 | } 66 | 67 | @Test 68 | public void testInputObject() throws Exception { 69 | String queryString = Generated.mutation(mutation -> mutation 70 | .setInteger(new Generated.SetIntegerInput("answer", 42).setNegate(true)) 71 | ).toString(); 72 | assertEquals("mutation{set_integer(input:{key:\"answer\",value:42,negate:true})}", queryString); 73 | } 74 | 75 | @Test 76 | public void testScalarInput() throws Exception { 77 | LocalDateTime ttl = LocalDateTime.of(2017, 1, 31, 10, 9, 48); 78 | String queryString = Generated.mutation(mutation -> mutation 79 | .setInteger(new Generated.SetIntegerInput("answer", 42).setTtl(ttl)) 80 | ).toString(); 81 | assertEquals("mutation{set_integer(input:{key:\"answer\",value:42,ttl:\"2017-01-31T10:09:48\"})}", queryString); 82 | } 83 | 84 | @Test 85 | public void testStringFieldResponse() throws Exception { 86 | String json = "{\"data\":{\"version\":\"1.2.3\"}}"; 87 | GeneratedMinimal.QueryRoot data = GeneratedMinimal.QueryResponse.fromJson(json).getData(); 88 | assertEquals("1.2.3", data.getVersion()); 89 | } 90 | 91 | @Test 92 | public void testStringListResponse() throws Exception { 93 | String json = "{\"data\":{\"keys\":[\"one\", \"two\"]}}"; 94 | Generated.QueryRoot data = Generated.QueryResponse.fromJson(json).getData(); 95 | assertEquals(Arrays.asList("one", "two"), data.getKeys()); 96 | } 97 | 98 | @Test 99 | public void testEnumFieldResponse() throws Exception { 100 | String json = "{\"data\":{\"type\":\"STRING\"}}"; 101 | Generated.QueryRoot data = Generated.QueryResponse.fromJson(json).getData(); 102 | assertEquals(Generated.KeyType.STRING, data.getType()); 103 | } 104 | 105 | @Test 106 | public void testUnknownEnumResponse() throws Exception { 107 | String json = "{\"data\":{\"type\":\"FUTURE\"}}"; 108 | Generated.QueryRoot data = Generated.QueryResponse.fromJson(json).getData(); 109 | assertEquals(Generated.KeyType.UNKNOWN_VALUE, data.getType()); 110 | } 111 | 112 | @Test 113 | public void testScalarFieldResponse() throws Exception { 114 | String json = "{\"data\":{\"ttl\":\"2017-01-31T10:09:48\"}}"; 115 | Generated.QueryRoot data = Generated.QueryResponse.fromJson(json).getData(); 116 | assertEquals(LocalDateTime.of(2017, 1, 31, 10, 9, 48), data.getTtl()); 117 | } 118 | 119 | @Test 120 | public void testInterfaceResponse() throws Exception { 121 | String json = "{\"data\":{\"entry\":{\"__typename\":\"IntegerEntry\",\"value\":42}}}"; 122 | Generated.Entry entry = Generated.QueryResponse.fromJson(json).getData().getEntry(); 123 | assertEquals("IntegerEntry", entry.getGraphQlTypeName()); 124 | assertTrue(entry instanceof Generated.IntegerEntry); 125 | assertEquals(42, ((Generated.IntegerEntry) entry).getValue().intValue()); 126 | } 127 | 128 | @Test 129 | public void testInterfaceUnknownTypeResponse() throws Exception { 130 | String json = "{\"data\":{\"entry\":{\"__typename\":\"FutureEntry\",\"key\":\"foo\"}}}"; 131 | Generated.Entry entry = Generated.QueryResponse.fromJson(json).getData().getEntry(); 132 | assertEquals("FutureEntry", entry.getGraphQlTypeName()); 133 | assertTrue(entry instanceof Generated.UnknownEntry); 134 | assertEquals("foo", entry.getKey()); 135 | } 136 | 137 | @Test 138 | public void testUnionResponse() throws Exception { 139 | String json = "{\"data\":{\"entry_union\":{\"__typename\":\"IntegerEntry\",\"value\":42}}}"; 140 | Generated.EntryUnion entry = Generated.QueryResponse.fromJson(json).getData().getEntryUnion(); 141 | assertEquals("IntegerEntry", entry.getGraphQlTypeName()); 142 | assertTrue(entry instanceof Generated.IntegerEntry); 143 | assertEquals(42, ((Generated.IntegerEntry) entry).getValue().intValue()); 144 | } 145 | 146 | @Test 147 | public void testUnionUnknownTypeResponse() throws Exception { 148 | String json = "{\"data\":{\"entry_union\":{\"__typename\":\"FutureEntry\"}}}"; 149 | Generated.EntryUnion entry = Generated.QueryResponse.fromJson(json).getData().getEntryUnion(); 150 | assertEquals("FutureEntry", entry.getGraphQlTypeName()); 151 | assertTrue(entry instanceof Generated.UnknownEntryUnion); 152 | } 153 | 154 | @Test 155 | public void testMutationResponse() throws Exception { 156 | String json = "{\"data\":{\"set_string\":true}}"; 157 | Generated.Mutation data = Generated.MutationResponse.fromJson(json).getData(); 158 | assertEquals(true, data.getSetString().booleanValue()); 159 | } 160 | 161 | @Test 162 | public void testOptionalFieldOnInput() throws Exception { 163 | String queryString = Generated.mutation(mutation -> mutation 164 | .setInteger(new Generated.SetIntegerInput("answer", 42).setTtl(null)) 165 | ).toString(); 166 | assertEquals("mutation{set_integer(input:{key:\"answer\",value:42})}", queryString); 167 | } 168 | 169 | @Test 170 | public void testOptionalFieldOnInputAsUndefined() throws Exception { 171 | String queryString = Generated.mutation(mutation -> mutation 172 | .setInteger(new Generated.SetIntegerInput("answer", 42).setTtlInput(Input.undefined())) 173 | ).toString(); 174 | assertEquals("mutation{set_integer(input:{key:\"answer\",value:42})}", queryString); 175 | } 176 | 177 | @Test 178 | public void testOptionalFieldOnInputAsExplicitNull() throws Exception { 179 | String queryString = Generated.mutation(mutation -> mutation 180 | .setInteger(new Generated.SetIntegerInput("answer", 42).setTtlInput(Input.value(null))) 181 | ).toString(); 182 | assertEquals("mutation{set_integer(input:{key:\"answer\",value:42,ttl:null})}", queryString); 183 | } 184 | 185 | @Test 186 | public void testOptionalFieldOnInputAsInputValue() throws Exception { 187 | String queryString = Generated.mutation(mutation -> mutation 188 | .setInteger(new Generated.SetIntegerInput("answer", 42).setTtlInput(Input.value(LocalDateTime.of(2017, 1, 31, 10, 9, 48)))) 189 | ).toString(); 190 | assertEquals("mutation{set_integer(input:{key:\"answer\",value:42,ttl:\"2017-01-31T10:09:48\"})}", queryString); 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /support/src/test/java/com/shopify/graphql/support/QueryTest.java: -------------------------------------------------------------------------------- 1 | package com.shopify.graphql.support; 2 | 3 | import org.junit.Test; 4 | 5 | import static junit.framework.Assert.assertEquals; 6 | 7 | public class QueryTest { 8 | @Test 9 | public void testStringEscaping() throws Exception { 10 | StringBuilder result = new StringBuilder(); 11 | AbstractQuery.appendQuotedString(result, "\0 \r \n \\ \" c ꝏ"); 12 | assertEquals("\"\\u0000 \\r \\n \\\\ \\\" c ꝏ\"", result.toString()); 13 | } 14 | 15 | @Test(expected = IllegalArgumentException.class) 16 | public void testInvalidAliasWithUnderscore() { 17 | new AbstractQuery(null) {}.withAlias("invalid__alias"); 18 | } 19 | 20 | @Test(expected = IllegalArgumentException.class) 21 | public void testInvalidAliasWithDashes() { 22 | new AbstractQuery(null) {}.withAlias("invalid-alias"); 23 | } 24 | 25 | @Test(expected = IllegalArgumentException.class) 26 | public void testBlankAlias() { 27 | new AbstractQuery(null) {}.withAlias(""); 28 | } 29 | } 30 | --------------------------------------------------------------------------------