├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── bin └── graphql_to_dart.dart ├── example ├── .gitignore ├── CHANGELOG.md ├── bin │ └── main.dart ├── graphql_config.yaml ├── lib │ └── graphql │ │ └── models │ │ ├── attack.dart │ │ ├── pokemon.dart │ │ ├── pokemon_attack.dart │ │ ├── pokemon_dimension.dart │ │ └── pokemon_evolution_requirement.dart └── pubspec.yaml ├── lib ├── graphql_to_dart.dart └── src │ ├── builders │ └── type_builder.dart │ ├── constants │ ├── files.dart │ └── type_converters.dart │ ├── graphql_to_dart_base.dart │ ├── introspection_api_client │ ├── client.dart │ └── queries.dart │ ├── models │ ├── config.dart │ └── graphql_types.dart │ ├── parsers │ └── config_parser.dart │ └── utils │ └── helper_function.dart └── pubspec.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | # Files and directories created by pub 2 | .dart_tool/ 3 | .packages 4 | # Remove the following pattern if you wish to check in your lock file 5 | pubspec.lock 6 | 7 | # Conventional directory for build outputs 8 | build/ 9 | 10 | # Directory created by dartdoc 11 | graphql_to_dart.iml 12 | doc/api/ 13 | 14 | analysis_options.yaml 15 | .idea/ 16 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.0.0 2 | 3 | - Initial version 4 | 5 | ## 1.0.1 6 | 7 | - Formatting 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GraphQL To Dart 2 | 3 | Generate Dart models from an GraphQL endpoint by running a simple command. 4 | ## Usage 5 | 6 | Add `graphql_to_dart` as a dev dependency. 7 | 8 | ```yaml 9 | graphql_to_dart: 1.0.0 10 | ``` 11 | 12 | Create a `graphql_config.yaml` file in your project root directory. 13 | 14 | A simple `graphql_config.yaml` file will look something like this: 15 | 16 | ```yaml 17 | package_name: my_awesome_app 18 | graphql_endpoint: https://example.com/graphql 19 | models_directory_path: lib/graphql/models/ 20 | type_override: 21 | id: int 22 | ``` 23 | 24 | #### Note: All options except `type_override` and `dynamic_import_path` are required. 25 | 26 | Run the command `flutter packages pub run graphql_to_dart` if you are using Flutter, otherwise simply `pub run graphql_to_dart`. 27 | 28 | You can find the generated models in the path you provided to `models_directory_path` argument in `graphql_config.yaml`. 29 | 30 | When you set `models_directory_path` outside of `lib/`, `import 'package:packagename/path/filename.dart';` is not applicable. 31 | Add option `dynamic_import_path: false` for `import 'filename.dart'`. 32 | 33 | 34 | ### Milestones 35 | 36 | - [x] Generate Models 37 | - [ ] Support Enum 38 | - [ ] Generate query, mutations and subscriptions strings compatible with [graphql](https://pub.dev/packages/graphql) package. 39 | -------------------------------------------------------------------------------- /bin/graphql_to_dart.dart: -------------------------------------------------------------------------------- 1 | import 'package:graphql_to_dart/graphql_to_dart.dart'; 2 | 3 | void main() { 4 | GraphQlToDart graphQlToDart = GraphQlToDart("graphql_config.yaml"); 5 | graphQlToDart.init(); 6 | } 7 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Files and directories created by pub 2 | .dart_tool/ 3 | .packages 4 | # Remove the following pattern if you wish to check in your lock file 5 | pubspec.lock 6 | 7 | # Conventional directory for build outputs 8 | build/ 9 | 10 | # Directory created by dartdoc 11 | doc/api/ 12 | -------------------------------------------------------------------------------- /example/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.0.0 2 | 3 | - Initial version, created by Stagehand 4 | -------------------------------------------------------------------------------- /example/bin/main.dart: -------------------------------------------------------------------------------- 1 | void main(List arguments) { 2 | print('Hello world!'); 3 | } 4 | -------------------------------------------------------------------------------- /example/graphql_config.yaml: -------------------------------------------------------------------------------- 1 | package_name: example 2 | graphql_endpoint: https://graphql-pokemon.now.sh/ 3 | models_directory_path: lib/graphql/models/ 4 | type_override: 5 | id: int -------------------------------------------------------------------------------- /example/lib/graphql/models/attack.dart: -------------------------------------------------------------------------------- 1 | class Attack { 2 | String name; 3 | String type; 4 | int damage; 5 | Attack({this.name, this.type, this.damage}); 6 | 7 | Attack.fromJson(Map json) { 8 | name = json['name']; 9 | type = json['type']; 10 | damage = json['damage']; 11 | } 12 | 13 | Map toJson() { 14 | Map data = {}; 15 | data['name'] = name; 16 | data['type'] = type; 17 | data['damage'] = damage; 18 | return data; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /example/lib/graphql/models/pokemon.dart: -------------------------------------------------------------------------------- 1 | import 'package:example/graphql/models/pokemon_dimension.dart'; 2 | import 'package:example/graphql/models/pokemon_attack.dart'; 3 | import 'package:example/graphql/models/pokemon.dart'; 4 | import 'package:example/graphql/models/pokemon_evolution_requirement.dart'; 5 | 6 | class Pokemon { 7 | int id; 8 | String number; 9 | String name; 10 | PokemonDimension weight; 11 | PokemonDimension height; 12 | String classification; 13 | List types; 14 | List resistant; 15 | PokemonAttack attacks; 16 | List weaknesses; 17 | double fleeRate; 18 | int maxCP; 19 | List evolutions; 20 | PokemonEvolutionRequirement evolutionRequirements; 21 | int maxHP; 22 | String image; 23 | Pokemon( 24 | {this.id, 25 | this.number, 26 | this.name, 27 | this.weight, 28 | this.height, 29 | this.classification, 30 | this.types, 31 | this.resistant, 32 | this.attacks, 33 | this.weaknesses, 34 | this.fleeRate, 35 | this.maxCP, 36 | this.evolutions, 37 | this.evolutionRequirements, 38 | this.maxHP, 39 | this.image}); 40 | 41 | Pokemon.fromJson(Map json) { 42 | id = json['id']; 43 | number = json['number']; 44 | name = json['name']; 45 | weight = json['weight'] != null 46 | ? PokemonDimension.fromJson(json['weight']) 47 | : null; 48 | height = json['height'] != null 49 | ? PokemonDimension.fromJson(json['height']) 50 | : null; 51 | classification = json['classification']; 52 | types = json['types'] != null ? json['types'] : null; 53 | resistant = json['resistant'] != null ? json['resistant'] : null; 54 | attacks = json['attacks'] != null 55 | ? PokemonAttack.fromJson(json['attacks']) 56 | : null; 57 | weaknesses = json['weaknesses'] != null ? json['weaknesses'] : null; 58 | fleeRate = json['fleeRate']; 59 | maxCP = json['maxCP']; 60 | evolutions = json['evolutions'] != null 61 | ? List.generate(json['evolutions'].length, 62 | (index) => Pokemon.fromJson(json['evolutions'][index])) 63 | : null; 64 | evolutionRequirements = json['evolutionRequirements'] != null 65 | ? PokemonEvolutionRequirement.fromJson(json['evolutionRequirements']) 66 | : null; 67 | maxHP = json['maxHP']; 68 | image = json['image']; 69 | } 70 | 71 | Map toJson() { 72 | Map data = {}; 73 | data['id'] = id; 74 | data['number'] = number; 75 | data['name'] = name; 76 | data['weight'] = weight?.toJson(); 77 | data['height'] = height?.toJson(); 78 | data['classification'] = classification; 79 | data['types'] = types; 80 | data['resistant'] = resistant; 81 | data['attacks'] = attacks?.toJson(); 82 | data['weaknesses'] = weaknesses; 83 | data['fleeRate'] = fleeRate; 84 | data['maxCP'] = maxCP; 85 | data['evolutions'] = List.generate( 86 | evolutions?.length ?? 0, (index) => evolutions[index].toJson()); 87 | data['evolutionRequirements'] = evolutionRequirements?.toJson(); 88 | data['maxHP'] = maxHP; 89 | data['image'] = image; 90 | return data; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /example/lib/graphql/models/pokemon_attack.dart: -------------------------------------------------------------------------------- 1 | import 'package:example/graphql/models/attack.dart'; 2 | 3 | class PokemonAttack { 4 | List fast; 5 | List special; 6 | PokemonAttack({this.fast, this.special}); 7 | 8 | PokemonAttack.fromJson(Map json) { 9 | fast = json['fast'] != null 10 | ? List.generate(json['fast'].length, 11 | (index) => Attack.fromJson(json['fast'][index])) 12 | : null; 13 | special = json['special'] != null 14 | ? List.generate(json['special'].length, 15 | (index) => Attack.fromJson(json['special'][index])) 16 | : null; 17 | } 18 | 19 | Map toJson() { 20 | Map data = {}; 21 | data['fast'] = 22 | List.generate(fast?.length ?? 0, (index) => fast[index].toJson()); 23 | data['special'] = 24 | List.generate(special?.length ?? 0, (index) => special[index].toJson()); 25 | return data; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /example/lib/graphql/models/pokemon_dimension.dart: -------------------------------------------------------------------------------- 1 | class PokemonDimension { 2 | String minimum; 3 | String maximum; 4 | PokemonDimension({this.minimum, this.maximum}); 5 | 6 | PokemonDimension.fromJson(Map json) { 7 | minimum = json['minimum']; 8 | maximum = json['maximum']; 9 | } 10 | 11 | Map toJson() { 12 | Map data = {}; 13 | data['minimum'] = minimum; 14 | data['maximum'] = maximum; 15 | return data; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /example/lib/graphql/models/pokemon_evolution_requirement.dart: -------------------------------------------------------------------------------- 1 | class PokemonEvolutionRequirement { 2 | int amount; 3 | String name; 4 | PokemonEvolutionRequirement({this.amount, this.name}); 5 | 6 | PokemonEvolutionRequirement.fromJson(Map json) { 7 | amount = json['amount']; 8 | name = json['name']; 9 | } 10 | 11 | Map toJson() { 12 | Map data = {}; 13 | data['amount'] = amount; 14 | data['name'] = name; 15 | return data; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: example 2 | description: A simple command-line application. 3 | # version: 1.0.0 4 | # homepage: https://www.example.com 5 | 6 | environment: 7 | sdk: '>=2.7.0 <3.0.0' 8 | 9 | #dependencies: 10 | # path: ^1.6.0 11 | 12 | dev_dependencies: 13 | pedantic: ^1.8.0 14 | graphql_to_dart: 15 | path: ../ 16 | -------------------------------------------------------------------------------- /lib/graphql_to_dart.dart: -------------------------------------------------------------------------------- 1 | /// Support for doing something awesome. 2 | /// 3 | /// More dartdocs go here. 4 | library graphql_to_dart; 5 | 6 | export 'src/graphql_to_dart_base.dart'; 7 | 8 | // TODO: Export any libraries intended for clients of this package. 9 | -------------------------------------------------------------------------------- /lib/src/builders/type_builder.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:graphql_to_dart/src/constants/files.dart'; 4 | import 'package:graphql_to_dart/src/constants/type_converters.dart'; 5 | import 'package:graphql_to_dart/src/models/config.dart'; 6 | import 'package:graphql_to_dart/src/models/graphql_types.dart'; 7 | import 'package:recase/recase.dart'; 8 | import 'package:graphql_to_dart/src/utils/helper_function.dart'; 9 | 10 | class TypeBuilder { 11 | static const String nonNull = "NON_NULL"; 12 | static const String scalar = "SCALAR"; 13 | static const String object = "OBJECT"; 14 | final Types type; 15 | final Config config; 16 | final StringBuffer stringBuffer = StringBuffer(); 17 | List localFields = []; 18 | 19 | TypeBuilder(this.type, this.config); 20 | 21 | Future build() async { 22 | _addFields(); 23 | _addConstructor(); 24 | _addFromJson(); 25 | _addToJson(); 26 | String current = stringBuffer.toString(); 27 | stringBuffer.clear(); 28 | current = _wrapWith(current, "class ${type.name}{", "}"); 29 | stringBuffer.write(current.toString()); 30 | _addImports(); 31 | await _saveToFile(); 32 | } 33 | 34 | _addImports() { 35 | StringBuffer importBuffer = StringBuffer(); 36 | localFields.unique((field) => field.type).forEach((field) { 37 | if (field.object == true) { 38 | if(config.dynamicImportPath){ 39 | importBuffer.writeln( 40 | "import 'package:${config.packageName}/${config.modelsDirectoryPath.replaceAll(r"lib/", "")}/${pascalToSnake(field.type)}.dart';" 41 | .replaceAll(r"//", r"/")); 42 | } 43 | else{ 44 | importBuffer.writeln( 45 | "import '${pascalToSnake(field.type)}.dart';" 46 | .replaceAll(r"//", r"/")); 47 | } 48 | } 49 | }); 50 | String current = stringBuffer.toString(); 51 | current = _wrapWith(current, importBuffer.toString() + "\n", ""); 52 | stringBuffer.clear(); 53 | stringBuffer.write(current); 54 | } 55 | 56 | _addToJson() { 57 | StringBuffer toJsonBuilder = StringBuffer(); 58 | toJsonBuilder.writeln("Map _data = {};"); 59 | localFields.forEach((field) { 60 | if (field.list == true) { 61 | if (field.type == "DateTime") { 62 | toJsonBuilder.writeln( 63 | "_data['${field.name}'] = List.generate(${field.name}?.length ?? 0, (index)=> ${field.name}[index].toString());"); 64 | } else if (field.object == true) { 65 | toJsonBuilder.writeln( 66 | "_data['${field.name}'] = List.generate(${field.name}?.length ?? 0, (index)=> ${field.name}[index].toJson());"); 67 | } else { 68 | toJsonBuilder.writeln("_data['${field.name}'] = ${field.name};"); 69 | } 70 | } else if (field.object == true) { 71 | toJsonBuilder 72 | .writeln("_data['${field.name}'] = ${field.name}?.toJson();"); 73 | } else if (field.type == "DateTime") { 74 | toJsonBuilder 75 | .writeln("_data['${field.name}'] = ${field.name}?.toString();"); 76 | } else { 77 | toJsonBuilder.writeln("_data['${field.name}'] = ${field.name};"); 78 | } 79 | }); 80 | stringBuffer.writeln(); 81 | toJsonBuilder.writeln("return _data;"); 82 | stringBuffer.writeln(); 83 | stringBuffer 84 | .write(_wrapWith(toJsonBuilder.toString(), "Map toJson(){", "}")); 85 | } 86 | 87 | _addFromJson() { 88 | StringBuffer fromJsonBuilder = StringBuffer(); 89 | localFields.forEach((field) { 90 | if (field.list == true) { 91 | fromJsonBuilder.write(""" 92 | ${field.name} = json['${field.name}']!=null ? 93 | ${field.object == true ? "List.generate(json['${field.name}'].length, (index)=> ${field.type}.fromJson(json['${field.name}'][index]))" : field.type == "DateTime" ? "List.generate(json['${field.name}'].length, (index)=> DateTime.parse(json['${field.name}'][index]))" : "json['${field.name}']"}: null; 94 | """); 95 | } else if (field.object == true) { 96 | fromJsonBuilder.writeln( 97 | "${field.name} = json['${field.name}']!=null ? ${field.type}.fromJson(json['${field.name}']) : null;"); 98 | } else if (field.type == "DateTime") { 99 | fromJsonBuilder.writeln( 100 | "${field.name} = json['${field.name}']!=null ? DateTime.parse(json['${field.name}']) : null;"); 101 | } else { 102 | fromJsonBuilder.writeln("${field.name} = json['${field.name}'];"); 103 | } 104 | }); 105 | stringBuffer.writeln(); 106 | stringBuffer.writeln(); 107 | stringBuffer.write(_wrapWith(fromJsonBuilder.toString(), 108 | "${type.name}.fromJson(Map json){", "}")); 109 | } 110 | 111 | _saveToFile() async { 112 | File file = File(FileConstants().modelsDirectory.path + 113 | "/${pascalToSnake(type.name)}.dart".replaceAll(r"//", r"/")); 114 | if (!(await file.exists())) { 115 | await file.create(); 116 | } 117 | await file.writeAsString(stringBuffer.toString()); 118 | return null; 119 | } 120 | 121 | _addFields() { 122 | type.fields.forEach((field) { 123 | _typeOrdering(field.type, field.name); 124 | }); 125 | } 126 | 127 | _addConstructor() { 128 | StringBuffer constructorBuffer = StringBuffer(); 129 | for (int i = 0; i < localFields.length; i++) { 130 | constructorBuffer.write("this.${localFields[i].name}"); 131 | if (i < localFields.length - 1) { 132 | constructorBuffer.write(","); 133 | } 134 | } 135 | stringBuffer.writeln( 136 | _wrapWith(constructorBuffer.toString(), "${type.name}({", "});")); 137 | } 138 | 139 | _typeOrdering(Type type, String fieldName) { 140 | bool list = false; 141 | LocalField localField; 142 | if (type.kind == "NON_NULL") { 143 | type = type.ofType; 144 | } 145 | if (type.kind == "LIST") { 146 | list = true; 147 | type = type.ofType; 148 | } 149 | if (type.kind == "NON_NULL") { 150 | type = type.ofType; 151 | } 152 | if (type.kind == scalar) { 153 | localField = LocalField( 154 | name: fieldName, 155 | list: list, 156 | type: TypeConverters().nonObjectTypes[type.name.toLowerCase()], 157 | object: false); 158 | localFields.add(localField); 159 | } else { 160 | localField = 161 | LocalField(name: fieldName, list: list, type: type.name, object: true); 162 | localFields.add(localField); 163 | } 164 | stringBuffer.writeln(localField.toDeclarationStatement()); 165 | } 166 | 167 | String _wrapWith(String input, String start, String end) { 168 | String updated = start + "\n" + input + "\n" + end; 169 | return updated; 170 | } 171 | 172 | String pascalToSnake(String pascalCasedString) { 173 | return ReCase(pascalCasedString).snakeCase; 174 | } 175 | } 176 | 177 | class LocalField { 178 | final String name; 179 | final bool list; 180 | final String type; 181 | final bool object; 182 | 183 | LocalField({this.name, this.list, this.type, this.object}); 184 | 185 | String toDeclarationStatement() { 186 | return "${list ? "List<" : ""}${type ?? "var"}${list ? ">" : ""} $name;"; 187 | } 188 | 189 | @override 190 | String toString() { 191 | // TODO: implement toString 192 | return type; 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /lib/src/constants/files.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | class FileConstants { 4 | FileConstants._(); 5 | static FileConstants _instance = FileConstants._(); 6 | factory FileConstants() => _instance; 7 | 8 | // File queriesFile; 9 | // File mutationsFile; 10 | // File subscriptionsFile; 11 | Directory modelsDirectory; 12 | } 13 | -------------------------------------------------------------------------------- /lib/src/constants/type_converters.dart: -------------------------------------------------------------------------------- 1 | class TypeConverters { 2 | TypeConverters._(); 3 | static TypeConverters _instance = TypeConverters._(); 4 | factory TypeConverters() => _instance; 5 | Map nonObjectTypes = { 6 | "string": "String", 7 | "int": "int", 8 | "float": "double", 9 | "double": "double", 10 | "id": "String", 11 | "datetime": "DateTime", 12 | "date": "DateTime", 13 | "boolean": "bool", 14 | "bool": "bool", 15 | "uuid": "String" 16 | }; 17 | 18 | void overrideTypes(Map newTypes) { 19 | newTypes?.forEach((key, value) { 20 | nonObjectTypes[key.toString().toLowerCase()] = value; 21 | }); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/src/graphql_to_dart_base.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | 4 | import 'package:graphql_to_dart/src/builders/type_builder.dart'; 5 | import 'package:graphql_to_dart/src/constants/files.dart'; 6 | import 'package:graphql_to_dart/src/constants/type_converters.dart'; 7 | import 'package:graphql_to_dart/src/introspection_api_client/client.dart'; 8 | import 'package:graphql_to_dart/src/models/config.dart'; 9 | import 'package:graphql_to_dart/src/models/graphql_types.dart'; 10 | import 'package:graphql_to_dart/src/parsers/config_parser.dart'; 11 | 12 | class GraphQlToDart { 13 | final String yamlFilePath; 14 | GraphQlToDart(this.yamlFilePath); 15 | static const List ignoreFields = [ 16 | "rootquerytype", 17 | "rootsubscriptiontype", 18 | "rootmutationtype", 19 | "mutation", 20 | "query", 21 | "subscription" 22 | ]; 23 | 24 | init() async { 25 | Config config = await ConfigParser.parse(yamlFilePath); 26 | ValidationResult result = await config.validate(); 27 | if (result.hasError) { 28 | throw result.errorMessage; 29 | } 30 | LocalGraphQLClient localGraphQLClient = LocalGraphQLClient(); 31 | localGraphQLClient.init(config); 32 | final schema = await localGraphQLClient.fetchTypes(); 33 | TypeConverters converters = TypeConverters(); 34 | converters.overrideTypes(config.typeOverride); 35 | await Future.forEach(schema.types, (Types type) async { 36 | if (type.fields != null && 37 | type.inputFields == null && 38 | !type.name.startsWith("__") && 39 | !ignoreFields.contains(type.name?.toLowerCase())) { 40 | print("Creating model from: ${type.name}"); 41 | TypeBuilder builder = TypeBuilder(type, config); 42 | await builder.build(); 43 | } 44 | }); 45 | print("Formatting Generated Files"); 46 | await runFlutterFormat(); 47 | return; 48 | } 49 | 50 | Future runFlutterFormat() async { 51 | Process.runSync( 52 | "flutter", 53 | ["format", FileConstants().modelsDirectory.path], 54 | runInShell: true, 55 | ); 56 | print("Formatted Generated Files"); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /lib/src/introspection_api_client/client.dart: -------------------------------------------------------------------------------- 1 | import 'package:graphql/client.dart'; 2 | import 'package:graphql_to_dart/src/introspection_api_client/queries.dart'; 3 | import 'package:graphql_to_dart/src/models/config.dart'; 4 | import 'package:graphql_to_dart/src/models/graphql_types.dart'; 5 | 6 | class LocalGraphQLClient { 7 | GraphQLClient client; 8 | 9 | init(Config config) { 10 | final HttpLink _httpLink = HttpLink( 11 | uri: config.graphQLEndpoint, 12 | ); 13 | client = GraphQLClient( 14 | cache: InMemoryCache(), 15 | link: _httpLink, 16 | ); 17 | } 18 | 19 | Future fetchTypes() async { 20 | final queryResult = 21 | await client.query(QueryOptions(documentNode: gql(Queries.types))); 22 | if (queryResult.hasException) throw queryResult.exception.toString(); 23 | return GraphQLSchema.fromJson(queryResult.data["__schema"]); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/src/introspection_api_client/queries.dart: -------------------------------------------------------------------------------- 1 | class Queries { 2 | static const String types = """ 3 | { 4 | __schema{ 5 | types{ 6 | name 7 | kind 8 | ofType{ 9 | name 10 | kind 11 | ofType{ 12 | name 13 | kind 14 | ofType{ 15 | name 16 | kind 17 | } 18 | } 19 | } 20 | fields{ 21 | name 22 | description 23 | type{ 24 | name 25 | kind 26 | ofType{ 27 | name 28 | kind 29 | ofType{ 30 | name 31 | kind 32 | ofType{ 33 | name 34 | kind 35 | } 36 | } 37 | } 38 | } 39 | } 40 | } 41 | } 42 | } 43 | """; 44 | } 45 | -------------------------------------------------------------------------------- /lib/src/models/config.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:graphql_to_dart/src/constants/files.dart'; 4 | import 'package:yaml/yaml.dart'; 5 | 6 | class Config { 7 | String graphQLEndpoint; 8 | // String queriesFilePath; 9 | // String mutationsFilePath; 10 | // String subscriptionsFilePath; 11 | String packageName; 12 | String modelsDirectoryPath; 13 | bool dynamicImportPath; 14 | YamlMap typeOverride; 15 | Config({this.modelsDirectoryPath}); 16 | Config.fromJson(Map map) { 17 | graphQLEndpoint = map['graphql_endpoint']?.toString(); 18 | // queriesFilePath = map['queries_file_path']?.toString(); 19 | // mutationsFilePath = map['mutations_file_path']?.toString(); 20 | // subscriptionsFilePath = map['subscriptions_file_path']?.toString(); 21 | modelsDirectoryPath = map['models_directory_path']?.toString(); 22 | dynamicImportPath = map['dynamic_import_path']?.toString() == 'false' ? false : true; 23 | 24 | packageName = map['package_name']; 25 | typeOverride = map['type_override']; 26 | } 27 | Future validate() async { 28 | // File queriesFile = File(queriesFilePath); 29 | // File mutationsFile = File(mutationsFilePath); 30 | // File subscriptionsFile = File(subscriptionsFilePath); 31 | Directory modelsDirectory = Directory(modelsDirectoryPath); 32 | try { 33 | // if(!(await queriesFile.exists())) 34 | // await createRecursive(queriesFile); 35 | // if(!(await mutationsFile.exists())) 36 | // await createRecursive(mutationsFile); 37 | // if(!(await subscriptionsFile.exists())) 38 | // print(await createRecursive(subscriptionsFile)); 39 | if (!(await modelsDirectory.exists())) { 40 | print(await createRecursive(modelsDirectory)); 41 | } 42 | if (packageName == null) { 43 | throw "Package Name Can't Be Empty"; 44 | } 45 | FileConstants().modelsDirectory = modelsDirectory; 46 | // FileConstants().queriesFile = queriesFile; 47 | // FileConstants().mutationsFile = mutationsFile; 48 | // FileConstants().subscriptionsFile = subscriptionsFile; 49 | return ValidationResult(hasError: false); 50 | } catch (e) { 51 | return ValidationResult(hasError: true, errorMessage: e.toString()); 52 | } 53 | } 54 | 55 | Future createRecursive(FileSystemEntity file) { 56 | if (file is File) { 57 | return file.create(recursive: true); 58 | } else { 59 | if (file is Directory) return file.create(recursive: true); 60 | } 61 | return null; 62 | } 63 | } 64 | 65 | class ValidationResult { 66 | bool hasError; 67 | String errorMessage; 68 | 69 | ValidationResult({this.hasError, this.errorMessage}); 70 | } 71 | -------------------------------------------------------------------------------- /lib/src/models/graphql_types.dart: -------------------------------------------------------------------------------- 1 | class GraphQLSchema { 2 | List types; 3 | 4 | GraphQLSchema({this.types}); 5 | 6 | GraphQLSchema.fromJson(Map json) { 7 | if (json['types'] != null) { 8 | types = List(); 9 | json['types'].forEach((v) { 10 | types.add(Types.fromJson(v)); 11 | }); 12 | } 13 | } 14 | 15 | Map toJson() { 16 | final Map data = Map(); 17 | if (this.types != null) { 18 | data['types'] = this.types.map((v) => v.toJson()).toList(); 19 | } 20 | return data; 21 | } 22 | } 23 | 24 | class Types { 25 | List fields; 26 | String kind; 27 | String name; 28 | Type ofType; 29 | var inputFields; 30 | Types({this.fields, this.kind, this.name, this.ofType}); 31 | 32 | Types.fromJson(Map json) { 33 | if (json['fields'] != null) { 34 | fields = List(); 35 | json['fields'].forEach((v) { 36 | fields.add(Fields.fromJson(v)); 37 | }); 38 | } 39 | inputFields = json['inputFields']; 40 | kind = json['kind']; 41 | name = json['name']; 42 | ofType = json['ofType'] != null ? Type.fromJson(json['ofType']) : null; 43 | } 44 | 45 | Map toJson() { 46 | final Map data = Map(); 47 | if (this.fields != null) { 48 | data['fields'] = this.fields.map((v) => v.toJson()).toList(); 49 | } 50 | data['kind'] = this.kind; 51 | data['name'] = this.name; 52 | data['ofType'] = this.ofType; 53 | return data; 54 | } 55 | } 56 | 57 | class Fields { 58 | String description; 59 | String name; 60 | Type type; 61 | 62 | Fields({this.description, this.name, this.type}); 63 | 64 | Fields.fromJson(Map json) { 65 | description = json['description']; 66 | name = json['name']; 67 | type = json['type'] != null ? Type.fromJson(json['type']) : null; 68 | } 69 | 70 | Map toJson() { 71 | final Map data = Map(); 72 | data['description'] = this.description; 73 | data['name'] = this.name; 74 | if (this.type != null) { 75 | data['type'] = this.type.toJson(); 76 | } 77 | return data; 78 | } 79 | } 80 | 81 | class Type { 82 | String kind; 83 | String name; 84 | Type ofType; 85 | 86 | Type({this.kind, this.name, this.ofType}); 87 | 88 | Type.fromJson(Map json) { 89 | kind = json['kind']; 90 | name = json['name']; 91 | ofType = json['ofType'] != null ? Type.fromJson(json['ofType']) : null; 92 | } 93 | 94 | Map toJson() { 95 | final Map data = Map(); 96 | data['kind'] = this.kind; 97 | data['name'] = this.name; 98 | if (this.ofType != null) { 99 | data['ofType'] = this.ofType.toJson(); 100 | } 101 | return data; 102 | } 103 | } 104 | 105 | class OfType { 106 | String kind; 107 | String name; 108 | 109 | OfType({this.kind, this.name}); 110 | 111 | OfType.fromJson(Map json) { 112 | kind = json['kind']; 113 | name = json['name']; 114 | } 115 | 116 | Map toJson() { 117 | final Map data = Map(); 118 | data['kind'] = this.kind; 119 | data['name'] = this.name; 120 | return data; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /lib/src/parsers/config_parser.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:graphql_to_dart/src/models/config.dart'; 4 | import 'package:yaml/yaml.dart'; 5 | 6 | class ConfigParser { 7 | static Future parse(String rawYamlPath) async { 8 | print("here"); 9 | File file = File(rawYamlPath); 10 | if (!(await file.exists())) { 11 | throw "Config Yaml file doesn't exist"; 12 | } 13 | final YamlMap yaml = loadYaml(await file.readAsString()) as YamlMap; 14 | if (yaml == null) { 15 | throw "YAML can't be parsed"; 16 | } 17 | return Config.fromJson(yaml); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/src/utils/helper_function.dart: -------------------------------------------------------------------------------- 1 | import 'dart:collection'; 2 | 3 | extension Unique on List { 4 | List unique(S Function(T) uniqueArg) { 5 | HashSet hashSet = HashSet(); 6 | List outputList = []; 7 | this.forEach((item) { 8 | S uniqueTrait = uniqueArg(item); 9 | if (!hashSet.contains(uniqueTrait)) { 10 | hashSet.add(uniqueTrait); 11 | outputList.add(item); 12 | } 13 | }); 14 | print(hashSet); 15 | return outputList; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: graphql_to_dart 2 | description: A simple dart package to generate complete Dart models from a GraphQL endpoint by leveraging GraphQL introspection API. 3 | version: 1.1.0 4 | homepage: https://github.com/vikram25897/graphql_to_dart 5 | environment: 6 | sdk: '>=2.6.0 <3.0.0' 7 | 8 | dependencies: 9 | yaml: ^2.2.1 10 | graphql: ^3.0.1 11 | recase: ^3.0.0 12 | 13 | --------------------------------------------------------------------------------