├── .gitignore ├── .idea ├── graphql_parser.iml ├── modules.xml ├── runConfigurations │ ├── main_dart.xml │ ├── objects_in_equality_test_dart.xml │ ├── server_dart.xml │ ├── tests_in_comment_test_dart.xml │ ├── tests_in_graphql_parser.xml │ ├── tests_in_graphql_schema.xml │ ├── tests_in_mirrors_test_dart.xml │ ├── tests_in_query_test_dart.xml │ ├── tests_in_validation_test_dart.xml │ └── tests_in_value_test_dart.xml └── vcs.xml ├── .travis.yml ├── LICENSE ├── README.md ├── angel_graphql ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── angel_graphql.iml ├── example │ ├── main.dart │ └── subscription.dart ├── lib │ ├── angel_graphql.dart │ └── src │ │ ├── graphiql.dart │ │ ├── graphql_http.dart │ │ ├── graphql_ws.dart │ │ └── resolvers.dart ├── mono_pkg.yaml └── pubspec.yaml ├── data_loader ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── example │ └── main.dart ├── lib │ └── data_loader.dart ├── mono_pkg.yaml ├── pubspec.yaml └── test │ └── all_test.dart ├── example_star_wars ├── .gitignore ├── LICENSE ├── analysis_options.yaml ├── bin │ └── server.dart ├── example_star_wars.iml ├── lib │ ├── src │ │ ├── models │ │ │ ├── character.dart │ │ │ ├── character.g.dart │ │ │ ├── droid.dart │ │ │ ├── droid.g.dart │ │ │ ├── episode.dart │ │ │ ├── episode.g.dart │ │ │ ├── human.dart │ │ │ ├── human.g.dart │ │ │ ├── models.dart │ │ │ ├── starship.dart │ │ │ └── starship.g.dart │ │ └── pretty_logging.dart │ └── star_wars.dart ├── mono_pkg.yaml └── pubspec.yaml ├── graphql.iml ├── graphql_generator ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── build.yaml ├── example │ ├── main.dart │ └── main.g.dart ├── lib │ └── graphql_generator.dart ├── mono_pkg.yaml └── pubspec.yaml ├── graphql_parser ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── example │ └── example.dart ├── graphql_parser.iml ├── lib │ ├── graphql_parser.dart │ └── src │ │ └── language │ │ ├── ast │ │ ├── alias.dart │ │ ├── argument.dart │ │ ├── array_value.dart │ │ ├── ast.dart │ │ ├── boolean_value.dart │ │ ├── default_value.dart │ │ ├── definition.dart │ │ ├── deprecated_value.dart │ │ ├── directive.dart │ │ ├── document.dart │ │ ├── field.dart │ │ ├── field_name.dart │ │ ├── fragment_definition.dart │ │ ├── fragment_spread.dart │ │ ├── inline_fragment.dart │ │ ├── input_value.dart │ │ ├── list_type.dart │ │ ├── misc_value.dart │ │ ├── node.dart │ │ ├── number_value.dart │ │ ├── operation_definition.dart │ │ ├── selection.dart │ │ ├── selection_set.dart │ │ ├── string_value.dart │ │ ├── type.dart │ │ ├── type_condition.dart │ │ ├── type_name.dart │ │ ├── variable.dart │ │ ├── variable_definition.dart │ │ └── variable_definitions.dart │ │ ├── language.dart │ │ ├── lexer.dart │ │ ├── parser.dart │ │ ├── syntax_error.dart │ │ ├── token.dart │ │ └── token_type.dart ├── mono_pkg.yaml ├── pubspec.yaml └── test │ ├── argument_test.dart │ ├── comment_test.dart │ ├── common.dart │ ├── directive_test.dart │ ├── document_test.dart │ ├── field_test.dart │ ├── fragment_spread_test.dart │ ├── inline_fragment_test.dart │ ├── issue23_test.dart │ ├── next_name_test.dart │ ├── selection_set_test.dart │ ├── type_test.dart │ ├── value_test.dart │ ├── variable_definition_test.dart │ └── variable_test.dart ├── graphql_schema ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── example │ └── example.dart ├── graphql_schema.iml ├── lib │ ├── graphql_schema.dart │ └── src │ │ ├── argument.dart │ │ ├── enum.dart │ │ ├── field.dart │ │ ├── gen.dart │ │ ├── object_type.dart │ │ ├── scalar.dart │ │ ├── schema.dart │ │ ├── type.dart │ │ ├── union.dart │ │ └── validation_result.dart ├── mono_pkg.yaml ├── pubspec.yaml └── test │ ├── common.dart │ ├── equality_test.dart │ ├── inheritance_test.dart │ ├── serialize_test.dart │ └── validation_test.dart ├── graphql_server ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── example │ └── main.dart ├── graphql_server.iml ├── lib │ ├── graphql_server.dart │ ├── introspection.dart │ ├── mirrors.dart │ ├── src │ │ └── apollo │ │ │ ├── remote_client.dart │ │ │ ├── server.dart │ │ │ └── transport.dart │ └── subscriptions_transport_ws.dart ├── mono_pkg.yaml ├── pubspec.yaml └── test │ ├── common.dart │ ├── mirrors_test.dart │ ├── query_test.dart │ └── subscription_test.dart ├── img ├── angel_graphql.png ├── angel_logo.png └── angel_logo.xcf ├── travis.sh └── video.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### JetBrains template 3 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 4 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 5 | 6 | # User-specific stuff 7 | .idea/**/workspace.xml 8 | .idea/**/tasks.xml 9 | .idea/**/dictionaries 10 | .idea/**/shelf 11 | 12 | # Sensitive or high-churn files 13 | .idea/**/dataSources/ 14 | .idea/**/dataSources.ids 15 | .idea/**/dataSources.local.xml 16 | .idea/**/sqlDataSources.xml 17 | .idea/**/dynamic.xml 18 | .idea/**/uiDesigner.xml 19 | .idea/**/dbnavigator.xml 20 | 21 | # Gradle 22 | .idea/**/gradle.xml 23 | .idea/**/libraries 24 | 25 | # CMake 26 | cmake-build-debug/ 27 | cmake-build-release/ 28 | 29 | # Mongo Explorer plugin 30 | .idea/**/mongoSettings.xml 31 | 32 | # File-based project format 33 | *.iws 34 | 35 | # IntelliJ 36 | out/ 37 | 38 | # mpeltonen/sbt-idea plugin 39 | .idea_modules/ 40 | 41 | # JIRA plugin 42 | atlassian-ide-plugin.xml 43 | 44 | # Cursive Clojure plugin 45 | .idea/replstate.xml 46 | 47 | # Crashlytics plugin (for Android Studio and IntelliJ) 48 | com_crashlytics_export_strings.xml 49 | crashlytics.properties 50 | crashlytics-build.properties 51 | fabric.properties 52 | 53 | # Editor-based Rest Client 54 | .idea/httpRequests 55 | 56 | .dart_tool -------------------------------------------------------------------------------- /.idea/graphql_parser.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/runConfigurations/main_dart.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /.idea/runConfigurations/objects_in_equality_test_dart.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations/server_dart.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | -------------------------------------------------------------------------------- /.idea/runConfigurations/tests_in_comment_test_dart.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/runConfigurations/tests_in_graphql_parser.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | -------------------------------------------------------------------------------- /.idea/runConfigurations/tests_in_graphql_schema.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | -------------------------------------------------------------------------------- /.idea/runConfigurations/tests_in_mirrors_test_dart.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /.idea/runConfigurations/tests_in_query_test_dart.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /.idea/runConfigurations/tests_in_validation_test_dart.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /.idea/runConfigurations/tests_in_value_test_dart.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: dart 2 | dart: 3 | - dev 4 | - stable 5 | script: 6 | - bash -ex travis.sh -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 The Angel Framework 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | 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 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Logo](https://github.com/angel-dart/graphql/raw/master/img/angel_logo.png) 2 | 3 |
4 |
5 | Pub 6 | Pub 7 |
8 | 9 | 10 | A complete implementation of the official 11 | [GraphQL specification](https://graphql.github.io/graphql-spec/June2018/), 12 | in the Dart programming language. 13 | 14 | The goal of this project is to provide to server-side 15 | users of Dart an alternative to REST API's. 16 | 17 | Included is also 18 | `package:angel_graphql`, which, when combined with the 19 | [Angel](https://github.com/angel-dart) framework, allows 20 | server-side Dart users to build backends with GraphQL and 21 | virtually any database imaginable. 22 | 23 | ## Tutorial Demo (click to watch) 24 | [![Youtube thumbnail](video.png)](https://youtu.be/5x6S4kDODa8) 25 | 26 | ## Projects 27 | This mono repo is split into several sub-projects, 28 | each with its own detailed documentation and examples: 29 | * `angel_graphql` - Support for handling GraphQL via HTTP and 30 | WebSockets in the [Angel](https://angel-dart.dev) framework. Also serves as the `package:graphql_server` reference implementation. 31 | * `data_loader` - A Dart port of [`graphql/data_loader`](https://github.com/graphql/dataloader). 32 | * `example_star_wars`: An example GraphQL API built using 33 | `package:angel_graphql`. 34 | * `graphql_generator`: Generates `package:graphql_schema` object types from concrete Dart classes. 35 | * `graphql_parser`: A recursive descent parser for the GraphQL language. 36 | * `graphql_schema`: An implementation of GraphQL's type system. This, combined with `package:graphql_parser`, 37 | powers `package:graphql_server`. 38 | * `graphql_server`: Base functionality for implementing GraphQL servers in Dart. Has no dependency on any 39 | framework. -------------------------------------------------------------------------------- /angel_graphql/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://www.dartlang.org/tools/private-files.html 2 | 3 | # Files and directories created by pub 4 | .buildlog 5 | .packages 6 | .project 7 | .pub/ 8 | .scripts-bin/ 9 | build/ 10 | **/packages/ 11 | posts.json 12 | 13 | # Files created by dart2js 14 | # (Most Dart developers will use pub build to compile Dart, use/modify these 15 | # rules if you intend to use dart2js directly 16 | # Convention is to use extension '.dart.js' for Dart compiled to Javascript to 17 | # differentiate from explicit Javascript files) 18 | *.dart.js 19 | *.part.js 20 | *.js.deps 21 | *.js.map 22 | *.info.json 23 | 24 | # Directory created by dartdoc 25 | doc/api/ 26 | 27 | # Don't commit pubspec lock file 28 | # (Library packages only! Remove pattern if developing an application package) 29 | pubspec.lock 30 | ### Dart template 31 | # See https://www.dartlang.org/tools/private-files.html 32 | 33 | # Files and directories created by pub 34 | 35 | # SDK 1.20 and later (no longer creates packages directories) 36 | 37 | # Older SDK versions 38 | # (Include if the minimum SDK version specified in pubsepc.yaml is earlier than 1.20) 39 | 40 | 41 | # Files created by dart2js 42 | # (Most Dart developers will use pub build to compile Dart, use/modify these 43 | # rules if you intend to use dart2js directly 44 | # Convention is to use extension '.dart.js' for Dart compiled to Javascript to 45 | # differentiate from explicit Javascript files) 46 | 47 | # Directory created by dartdoc 48 | 49 | # Don't commit pubspec lock file 50 | # (Library packages only! Remove pattern if developing an application package) 51 | ### JetBrains template 52 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 53 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 54 | 55 | # User-specific stuff: 56 | ../.idea/workspace.xml 57 | .idea/**/tasks.xml 58 | .idea/dictionaries 59 | 60 | # Sensitive or high-churn files: 61 | .idea/**/dataSources/ 62 | .idea/**/dataSources.ids 63 | .idea/**/dataSources.xml 64 | .idea/**/dataSources.local.xml 65 | .idea/**/sqlDataSources.xml 66 | .idea/**/dynamic.xml 67 | .idea/**/uiDesigner.xml 68 | 69 | # Gradle: 70 | .idea/**/gradle.xml 71 | .idea/**/libraries 72 | 73 | # Mongo Explorer plugin: 74 | .idea/**/mongoSettings.xml 75 | 76 | ## File-based project format: 77 | *.iws 78 | 79 | ## Plugin-specific files: 80 | 81 | # IntelliJ 82 | /out/ 83 | 84 | # mpeltonen/sbt-idea plugin 85 | .idea_modules/ 86 | 87 | # JIRA plugin 88 | atlassian-ide-plugin.xml 89 | 90 | # Crashlytics plugin (for Android Studio and IntelliJ) 91 | com_crashlytics_export_strings.xml 92 | crashlytics.properties 93 | crashlytics-build.properties 94 | fabric.properties 95 | -------------------------------------------------------------------------------- /angel_graphql/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 1.1.0 2 | * Support the GraphQL multipart spec: https://github.com/jaydenseric/graphql-multipart-request-spec 3 | 4 | # 1.0.0 5 | * Apply `package:pedantic`. 6 | 7 | # 1.0.0-rc.0 8 | * Finish `graphQLWS`. 9 | 10 | # 1.0.0-beta.1 11 | * Add `graphQLWS` handler, and support subscriptions. 12 | 13 | # 1.0.0-beta 14 | * Angel RC updates. 15 | 16 | # 1.0.0-alpha 17 | * First official release. -------------------------------------------------------------------------------- /angel_graphql/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 The Angel Framework 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | 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 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /angel_graphql/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:pedantic/analysis_options.yaml 2 | analyzer: 3 | strong-mode: 4 | implicit-casts: false -------------------------------------------------------------------------------- /angel_graphql/angel_graphql.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /angel_graphql/example/main.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: deprecated_member_use 2 | import 'package:angel_framework/angel_framework.dart'; 3 | import 'package:angel_framework/http.dart'; 4 | import 'package:angel_graphql/angel_graphql.dart'; 5 | import 'package:angel_serialize/angel_serialize.dart'; 6 | import 'package:graphql_schema/graphql_schema.dart'; 7 | import 'package:graphql_server/graphql_server.dart'; 8 | import 'package:graphql_server/mirrors.dart'; 9 | import 'package:logging/logging.dart'; 10 | 11 | main() async { 12 | var logger = Logger('angel_graphql'); 13 | var app = Angel( 14 | logger: logger 15 | ..onRecord.listen((rec) { 16 | print(rec); 17 | if (rec.error != null) print(rec.error); 18 | if (rec.stackTrace != null) print(rec.stackTrace); 19 | })); 20 | var http = AngelHttp(app); 21 | 22 | var todoService = app.use('api/todos', MapService()); 23 | 24 | var queryType = objectType( 25 | 'Query', 26 | description: 'A simple API that manages your to-do list.', 27 | fields: [ 28 | field( 29 | 'todos', 30 | listOf(convertDartType(Todo).nonNullable()), 31 | resolve: resolveViaServiceIndex(todoService), 32 | ), 33 | field( 34 | 'todo', 35 | convertDartType(Todo), 36 | resolve: resolveViaServiceRead(todoService), 37 | inputs: [ 38 | GraphQLFieldInput('id', graphQLId.nonNullable()), 39 | ], 40 | ), 41 | ], 42 | ); 43 | 44 | var mutationType = objectType( 45 | 'Mutation', 46 | description: 'Modify the to-do list.', 47 | fields: [ 48 | field( 49 | 'createTodo', 50 | convertDartType(Todo), 51 | inputs: [ 52 | GraphQLFieldInput( 53 | 'data', convertDartType(Todo).coerceToInputObject()), 54 | ], 55 | resolve: resolveViaServiceCreate(todoService), 56 | ), 57 | ], 58 | ); 59 | 60 | var schema = graphQLSchema( 61 | queryType: queryType, 62 | mutationType: mutationType, 63 | ); 64 | 65 | app.all('/graphql', graphQLHttp(GraphQL(schema))); 66 | app.get('/graphiql', graphiQL()); 67 | 68 | await todoService 69 | .create({'text': 'Clean your room!', 'completion_status': 'COMPLETE'}); 70 | await todoService.create( 71 | {'text': 'Take out the trash', 'completion_status': 'INCOMPLETE'}); 72 | await todoService.create({ 73 | 'text': 'Become a billionaire at the age of 5', 74 | 'completion_status': 'INCOMPLETE' 75 | }); 76 | 77 | var server = await http.startServer('127.0.0.1', 3000); 78 | var uri = 79 | Uri(scheme: 'http', host: server.address.address, port: server.port); 80 | var graphiqlUri = uri.replace(path: 'graphiql'); 81 | print('Listening at $uri'); 82 | print('Access graphiql at $graphiqlUri'); 83 | } 84 | 85 | @GraphQLDocumentation(description: 'Any object with a .text (String) property.') 86 | abstract class HasText { 87 | String get text; 88 | } 89 | 90 | @serializable 91 | @GraphQLDocumentation( 92 | description: 'A task that might not be completed yet. **Yay! Markdown!**') 93 | class Todo extends Model implements HasText { 94 | String text; 95 | 96 | @GraphQLDocumentation(deprecationReason: 'Use `completion_status` instead.') 97 | bool completed; 98 | 99 | CompletionStatus completionStatus; 100 | 101 | Todo({this.text, this.completed, this.completionStatus}); 102 | } 103 | 104 | @GraphQLDocumentation(description: 'The completion status of a to-do item.') 105 | enum CompletionStatus { COMPLETE, INCOMPLETE } 106 | -------------------------------------------------------------------------------- /angel_graphql/example/subscription.dart: -------------------------------------------------------------------------------- 1 | // Inspired by: 2 | // https://www.apollographql.com/docs/apollo-server/features/subscriptions/#subscriptions-example 3 | 4 | import 'package:angel_file_service/angel_file_service.dart'; 5 | import 'package:angel_framework/angel_framework.dart'; 6 | import 'package:angel_framework/http.dart'; 7 | import 'package:angel_graphql/angel_graphql.dart'; 8 | import 'package:file/local.dart'; 9 | import 'package:graphql_schema/graphql_schema.dart'; 10 | import 'package:graphql_server/graphql_server.dart'; 11 | import 'package:logging/logging.dart'; 12 | 13 | main() async { 14 | var logger = Logger('angel_graphql'); 15 | var app = Angel(logger: logger); 16 | var http = AngelHttp(app); 17 | app.logger.onRecord.listen((rec) { 18 | print(rec); 19 | if (rec.error != null) print(rec.error); 20 | if (rec.stackTrace != null) print(rec.stackTrace); 21 | }); 22 | 23 | // Create an in-memory service. 24 | var fs = LocalFileSystem(); 25 | var postService = 26 | app.use('/api/posts', JsonFileService(fs.file('posts.json'))); 27 | 28 | // Also get a [Stream] of item creation events. 29 | var postAdded = postService.afterCreated 30 | .asStream() 31 | .map((e) => {'postAdded': e.result}) 32 | .asBroadcastStream(); 33 | 34 | // GraphQL setup. 35 | var postType = objectType('Post', fields: [ 36 | field('author', graphQLString), 37 | field('comment', graphQLString), 38 | ]); 39 | 40 | var schema = graphQLSchema( 41 | // Hooked up to the postService: 42 | // type Query { posts: [Post] } 43 | queryType: objectType( 44 | 'Query', 45 | fields: [ 46 | field( 47 | 'posts', 48 | listOf(postType), 49 | resolve: resolveViaServiceIndex(postService), 50 | ), 51 | ], 52 | ), 53 | 54 | // Hooked up to the postService: 55 | // type Mutation { 56 | // addPost(author: String!, comment: String!): Post 57 | // } 58 | mutationType: objectType( 59 | 'Mutation', 60 | fields: [ 61 | field( 62 | 'addPost', 63 | postType, 64 | inputs: [ 65 | GraphQLFieldInput( 66 | 'data', postType.toInputObject('PostInput').nonNullable()), 67 | ], 68 | resolve: resolveViaServiceCreate(postService), 69 | ), 70 | ], 71 | ), 72 | 73 | // Hooked up to `postAdded`: 74 | // type Subscription { postAdded: Post } 75 | subscriptionType: objectType( 76 | 'Subscription', 77 | fields: [ 78 | field('postAdded', postType, resolve: (_, __) => postAdded), 79 | ], 80 | ), 81 | ); 82 | 83 | // Mount GraphQL routes; we'll support HTTP and WebSockets transports. 84 | app.all('/graphql', graphQLHttp(GraphQL(schema))); 85 | app.get('/subscriptions', 86 | graphQLWS(GraphQL(schema), keepAliveInterval: Duration(seconds: 3))); 87 | app.get('/graphiql', 88 | graphiQL(subscriptionsEndpoint: 'ws://localhost:3000/subscriptions')); 89 | 90 | var server = await http.startServer('127.0.0.1', 3000); 91 | var uri = 92 | Uri(scheme: 'http', host: server.address.address, port: server.port); 93 | var graphiqlUri = uri.replace(path: 'graphiql'); 94 | var postsUri = uri.replace(pathSegments: ['api', 'posts']); 95 | print('Listening at $uri'); 96 | print('Access graphiql at $graphiqlUri'); 97 | print('Access posts service at $postsUri'); 98 | } 99 | -------------------------------------------------------------------------------- /angel_graphql/lib/angel_graphql.dart: -------------------------------------------------------------------------------- 1 | import 'package:angel_framework/angel_framework.dart'; 2 | import 'package:graphql_schema/graphql_schema.dart'; 3 | export 'src/graphiql.dart'; 4 | export 'src/graphql_http.dart'; 5 | export 'src/graphql_ws.dart'; 6 | export 'src/resolvers.dart'; 7 | 8 | /// The canonical [GraphQLUploadType] instance. 9 | final GraphQLUploadType graphQLUpload = GraphQLUploadType(); 10 | 11 | /// A [GraphQLScalarType] that is used to read uploaded files from 12 | /// `multipart/form-data` requests. 13 | class GraphQLUploadType extends GraphQLScalarType { 14 | @override 15 | String get name => 'Upload'; 16 | 17 | @override 18 | String get description => 19 | 'Represents a file that has been uploaded to the server.'; 20 | 21 | @override 22 | GraphQLType coerceToInputObject() => this; 23 | 24 | @override 25 | UploadedFile deserialize(UploadedFile serialized) => serialized; 26 | 27 | @override 28 | UploadedFile serialize(UploadedFile value) => value; 29 | 30 | @override 31 | ValidationResult validate(String key, UploadedFile input) { 32 | if (input != null && input is! UploadedFile) { 33 | return _Vr(false, errors: ['Expected "$key" to be a boolean.']); 34 | } 35 | return _Vr(true, value: input); 36 | } 37 | } 38 | 39 | // TODO: Really need to make the validation result constructors *public* 40 | class _Vr implements ValidationResult { 41 | final bool successful; 42 | final List errors; 43 | final T value; 44 | 45 | _Vr(this.successful, {this.errors, this.value}); 46 | } 47 | -------------------------------------------------------------------------------- /angel_graphql/lib/src/graphiql.dart: -------------------------------------------------------------------------------- 1 | import 'package:angel_framework/angel_framework.dart'; 2 | import 'package:http_parser/http_parser.dart'; 3 | 4 | /// Returns a simple [RequestHandler] that renders the GraphiQL visual interface for GraphQL. 5 | /// 6 | /// By default, the interface expects your backend to be mounted at `/graphql`; this is configurable 7 | /// via [graphQLEndpoint]. 8 | RequestHandler graphiQL( 9 | {String graphQLEndpoint = '/graphql', String subscriptionsEndpoint}) { 10 | return (req, res) { 11 | res 12 | ..contentType = MediaType('text', 'html') 13 | ..write(renderGraphiql( 14 | graphqlEndpoint: graphQLEndpoint, 15 | subscriptionsEndpoint: subscriptionsEndpoint)) 16 | ..close(); 17 | }; 18 | } 19 | 20 | String renderGraphiql( 21 | {String graphqlEndpoint = '/graphql', String subscriptionsEndpoint}) { 22 | var subscriptionsScripts = '', 23 | subscriptionsFetcher = '', 24 | fetcherName = 'graphQLFetcher'; 25 | 26 | if (subscriptionsEndpoint != null) { 27 | fetcherName = 'subscriptionsFetcher'; 28 | subscriptionsScripts = ''' 29 | 30 | 31 | '''; 32 | subscriptionsFetcher = ''' 33 | let subscriptionsClient = window.SubscriptionsTransportWs.SubscriptionClient('$subscriptionsEndpoint', { 34 | reconnect: true 35 | }); 36 | let $fetcherName = window.GraphiQLSubscriptionsFetcher.graphQLFetcher(subscriptionsClient, graphQLFetcher); 37 | '''; 38 | } 39 | 40 | return ''' 41 | 42 | 43 | 44 | 45 | Angel GraphQL 46 | 47 | 56 | 57 | 58 |
59 | 60 | 61 | 62 | 63 | $subscriptionsScripts 64 | 85 | 86 | 87 | ''' 88 | .trim(); 89 | } 90 | -------------------------------------------------------------------------------- /angel_graphql/lib/src/graphql_ws.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | import 'package:angel_framework/angel_framework.dart'; 4 | import 'package:angel_framework/http.dart'; 5 | import 'package:graphql_schema/graphql_schema.dart'; 6 | import 'package:graphql_server/graphql_server.dart'; 7 | import 'package:graphql_server/subscriptions_transport_ws.dart' as stw; 8 | import 'package:web_socket_channel/io.dart'; 9 | 10 | /// A [RequestHandler] that serves a spec-compliant GraphQL backend, over WebSockets. 11 | /// This endpoint only supports WebSockets, and can be used to deliver subscription events. 12 | /// 13 | /// `graphQLWS` uses the Apollo WebSocket protocol, for the sake of compatibility with 14 | /// existing tooling. 15 | /// 16 | /// See: 17 | /// * https://github.com/apollographql/subscriptions-transport-ws 18 | RequestHandler graphQLWS(GraphQL graphQL, {Duration keepAliveInterval}) { 19 | return (req, res) async { 20 | if (req is HttpRequestContext) { 21 | if (WebSocketTransformer.isUpgradeRequest(req.rawRequest)) { 22 | await res.detach(); 23 | var socket = await WebSocketTransformer.upgrade(req.rawRequest, 24 | protocolSelector: (protocols) { 25 | if (protocols.contains('graphql-ws')) { 26 | return 'graphql-ws'; 27 | } else { 28 | throw AngelHttpException.badRequest( 29 | message: 'Only the "graphql-ws" protocol is allowed.'); 30 | } 31 | }); 32 | var channel = IOWebSocketChannel(socket); 33 | var client = stw.RemoteClient(channel.cast()); 34 | var server = 35 | _GraphQLWSServer(client, graphQL, req, res, keepAliveInterval); 36 | await server.done; 37 | } else { 38 | throw AngelHttpException.badRequest( 39 | message: 'The `graphQLWS` endpoint only accepts WebSockets.'); 40 | } 41 | } else { 42 | throw AngelHttpException.badRequest( 43 | message: 'The `graphQLWS` endpoint only accepts HTTP/1.1 requests.'); 44 | } 45 | }; 46 | } 47 | 48 | class _GraphQLWSServer extends stw.Server { 49 | final GraphQL graphQL; 50 | final RequestContext req; 51 | final ResponseContext res; 52 | 53 | _GraphQLWSServer(stw.RemoteClient client, this.graphQL, this.req, this.res, 54 | Duration keepAliveInterval) 55 | : super(client, keepAliveInterval: keepAliveInterval); 56 | 57 | @override 58 | bool onConnect(stw.RemoteClient client, [Map connectionParams]) => true; 59 | 60 | @override 61 | Future onOperation(String id, String query, 62 | [Map variables, String operationName]) async { 63 | try { 64 | var globalVariables = { 65 | '__requestctx': req, 66 | '__responsectx': res, 67 | }; 68 | var data = await graphQL.parseAndExecute( 69 | query, 70 | operationName: operationName, 71 | sourceUrl: 'input', 72 | globalVariables: globalVariables, 73 | variableValues: variables, 74 | ); 75 | return stw.GraphQLResult(data); 76 | } on GraphQLException catch (e) { 77 | return stw.GraphQLResult(null, errors: e.errors); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /angel_graphql/mono_pkg.yaml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angel-dart-archive/graphql/33e2f86ba73d559197b6270df036256104726aca/angel_graphql/mono_pkg.yaml -------------------------------------------------------------------------------- /angel_graphql/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: angel_graphql 2 | version: 1.1.0 3 | description: The fastest + easiest way to get a GraphQL backend in Dart, using Angel. 4 | homepage: https://github.com/angel-dart/graphql 5 | author: Tobe O 6 | environment: 7 | sdk: ">=2.0.0-dev <3.0.0" 8 | dependencies: 9 | angel_file_service: ^2.0.0 10 | angel_framework: ^2.0.0 11 | angel_websocket: ^2.0.0 12 | angel_validate: ^2.0.0 13 | graphql_parser: ^1.0.0 14 | graphql_schema: ^1.0.0 15 | graphql_server: ^1.0.0 16 | http_parser: ^3.0.0 17 | web_socket_channel: ^1.0.0 18 | dev_dependencies: 19 | angel_serialize: ^2.0.0 20 | file: ^5.0.0 21 | logging: ^0.11.0 22 | pedantic: ^1.0.0 -------------------------------------------------------------------------------- /data_loader/.gitignore: -------------------------------------------------------------------------------- 1 | .packages 2 | pubspec.lock 3 | .dart_tool 4 | doc/api -------------------------------------------------------------------------------- /data_loader/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 1.0.0 2 | * Initial version. -------------------------------------------------------------------------------- /data_loader/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 The Angel Framework 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | 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 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /data_loader/README.md: -------------------------------------------------------------------------------- 1 | # data_loader 2 | [![Pub](https://img.shields.io/pub/v/data_loader.svg)](https://pub.dartlang.org/packages/data_loader) 3 | [![build status](https://travis-ci.org/angel-dart/graphql.svg)](https://travis-ci.org/angel-dart/graphql) 4 | 5 | 6 | Batch and cache database lookups. Works well with GraphQL. 7 | Ported from the original JS version: 8 | https://github.com/graphql/dataloader 9 | 10 | ## Installation 11 | In your pubspec.yaml: 12 | 13 | ```yaml 14 | dependencies: 15 | data_loader: ^1.0.0 16 | ``` 17 | 18 | ## Usage 19 | Complete example: 20 | https://github.com/angel-dart/graphql/blob/master/data_loader/example/main.dart 21 | 22 | ```dart 23 | var userLoader = new DataLoader((key) => myBatchGetUsers(keys)); 24 | var invitedBy = await userLoader.load(1)then(user => userLoader.load(user.invitedByID)) 25 | print('User 1 was invited by $invitedBy')); 26 | ``` -------------------------------------------------------------------------------- /data_loader/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:pedantic/analysis_options.yaml 2 | analyzer: 3 | strong-mode: 4 | implicit-casts: false -------------------------------------------------------------------------------- /data_loader/example/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:data_loader/data_loader.dart'; 3 | import 'package:graphql_schema/graphql_schema.dart'; 4 | 5 | external Future> fetchTodos(Iterable ids); 6 | 7 | main() async { 8 | // Create a DataLoader. By default, it caches lookups. 9 | var todoLoader = DataLoader(fetchTodos); // DataLoader 10 | 11 | // type Todo { id: Int, text: String, is_complete: Boolean } 12 | var todoType = objectType( 13 | 'Todo', 14 | fields: [ 15 | field('id', graphQLInt), 16 | field('text', graphQLString), 17 | field('is_complete', graphQLBoolean), 18 | ], 19 | ); 20 | 21 | // type Query { todo($id: Int!) Todo } 22 | // ignore: unused_local_variable 23 | var schema = graphQLSchema( 24 | queryType: objectType( 25 | 'Query', 26 | fields: [ 27 | field( 28 | 'todo', 29 | listOf(todoType), 30 | inputs: [GraphQLFieldInput('id', graphQLInt.nonNullable())], 31 | resolve: (_, args) => todoLoader.load(args['id'] as int), 32 | ), 33 | ], 34 | ), 35 | ); 36 | 37 | // Do something with your schema... 38 | } 39 | 40 | abstract class Todo { 41 | int get id; 42 | String get text; 43 | bool get isComplete; 44 | } 45 | -------------------------------------------------------------------------------- /data_loader/lib/data_loader.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:collection'; 3 | 4 | /// A utility for batching multiple requests together, to improve application performance. 5 | /// 6 | /// Enqueues batches of requests until the next tick, when they are processed in bulk. 7 | /// 8 | /// Port of Facebook's `DataLoader`: 9 | /// https://github.com/graphql/dataloader 10 | class DataLoader { 11 | /// Invoked to fetch a batch of keys simultaneously. 12 | final FutureOr> Function(Iterable) loadMany; 13 | 14 | /// Whether to use a memoization cache to store the results of past lookups. 15 | final bool cache; 16 | 17 | var _cache = {}; 18 | var _queue = Queue<_QueueItem>(); 19 | bool _started = false; 20 | 21 | DataLoader(this.loadMany, {this.cache = true}); 22 | 23 | Future _onTick() async { 24 | if (_queue.isNotEmpty) { 25 | var current = _queue.toList(); 26 | _queue.clear(); 27 | 28 | List loadIds = 29 | current.map((i) => i.id).toSet().toList(growable: false); 30 | 31 | var data = await loadMany( 32 | loadIds, 33 | ); 34 | 35 | for (int i = 0; i < loadIds.length; i++) { 36 | var id = loadIds[i]; 37 | var value = data.elementAt(i); 38 | 39 | if (cache) _cache[id] = value; 40 | 41 | current 42 | .where((item) => item.id == id) 43 | .forEach((item) => item.completer.complete(value)); 44 | } 45 | } 46 | 47 | _started = false; 48 | // if (!_closed) scheduleMicrotask(_onTick); 49 | } 50 | 51 | /// Clears the value at [key], if it exists. 52 | void clear(Id key) => _cache.remove(key); 53 | 54 | /// Clears the entire cache. 55 | void clearAll() => _cache.clear(); 56 | 57 | /// Primes the cache with the provided key and value. If the key already exists, no change is made. 58 | /// 59 | /// To forcefully prime the cache, clear the key first with 60 | /// `loader..clear(key)..prime(key, value)`. 61 | void prime(Id key, Data value) => _cache.putIfAbsent(key, () => value); 62 | 63 | /// Closes this [DataLoader], cancelling all pending requests. 64 | void close() { 65 | while (_queue.isNotEmpty) { 66 | _queue.removeFirst().completer.completeError( 67 | StateError('The DataLoader was closed before the item was loaded.')); 68 | } 69 | 70 | _queue.clear(); 71 | } 72 | 73 | /// Returns a [Future] that completes when the next batch of requests completes. 74 | Future load(Id id) { 75 | if (cache && _cache.containsKey(id)) { 76 | return Future.value(_cache[id]); 77 | } else { 78 | var item = _QueueItem(id); 79 | _queue.add(item); 80 | if (!_started) { 81 | _started = true; 82 | scheduleMicrotask(_onTick); 83 | } 84 | return item.completer.future; 85 | } 86 | } 87 | } 88 | 89 | class _QueueItem { 90 | final Id id; 91 | final Completer completer = Completer(); 92 | 93 | _QueueItem(this.id); 94 | } 95 | -------------------------------------------------------------------------------- /data_loader/mono_pkg.yaml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angel-dart-archive/graphql/33e2f86ba73d559197b6270df036256104726aca/data_loader/mono_pkg.yaml -------------------------------------------------------------------------------- /data_loader/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: data_loader 2 | version: 1.0.0 3 | author: Tobe O 4 | description: Batch and cache database lookups. Works well with GraphQL. Ported from JS. 5 | homepage: https://github.com/angel-dart/graphql 6 | environment: 7 | sdk: ">=2.0.0-dev <3.0.0" 8 | dev_dependencies: 9 | graphql_schema: ^1.0.0 10 | pedantic: ^1.0.0 11 | test: ">=0.12.0 <2.0.0" -------------------------------------------------------------------------------- /data_loader/test/all_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:data_loader/data_loader.dart'; 4 | import 'package:test/test.dart'; 5 | 6 | void main() { 7 | var numbers = List.generate(10, (i) => i.toStringAsFixed(2)); 8 | var numberLoader = DataLoader((ids) { 9 | print('ID batch: $ids'); 10 | return ids.map((i) => numbers[i]); 11 | }); 12 | 13 | test('batch', () async { 14 | var zero = numberLoader.load(0); 15 | var one = numberLoader.load(1); 16 | var two = numberLoader.load(2); 17 | var batch = await Future.wait([zero, one, two]); 18 | print('Fetched result: $batch'); 19 | expect(batch, ['0.00', '1.00', '2.00']); 20 | }); 21 | 22 | test('dedupe', () async { 23 | var loader = DataLoader>>((ids) { 24 | return ids.map( 25 | (i) => {i: ids.toList()}, 26 | ); 27 | }); 28 | 29 | var zero = loader.load(0); 30 | var one = loader.load(1); 31 | var two = loader.load(2); 32 | var anotherZero = loader.load(0); 33 | var batch = await Future.wait([zero, one, two, anotherZero]); 34 | 35 | expect( 36 | batch, 37 | [ 38 | { 0: [0, 1, 2]}, 39 | { 1: [0, 1, 2]}, 40 | { 2: [0, 1, 2]}, 41 | { 0: [0, 1, 2]}, 42 | ], 43 | ); 44 | }); 45 | 46 | group('cache', () { 47 | DataLoader uniqueLoader, noCache; 48 | 49 | setUp(() { 50 | uniqueLoader = DataLoader((ids) async { 51 | var numbers = await numberLoader.loadMany(ids); 52 | return numbers.map((s) => _Unique(s)); 53 | }); 54 | noCache = DataLoader(uniqueLoader.loadMany, cache: false); 55 | }); 56 | 57 | tearDown(() { 58 | uniqueLoader.close(); 59 | noCache.close(); 60 | }); 61 | 62 | test('only lookup once', () async { 63 | var a = await uniqueLoader.load(3); 64 | var b = await uniqueLoader.load(3); 65 | expect(a, b); 66 | }); 67 | 68 | test('can be disabled', () async { 69 | var a = await noCache.load(3); 70 | var b = await noCache.load(3); 71 | expect(a, isNot(b)); 72 | }); 73 | 74 | test('clear', () async { 75 | var a = await uniqueLoader.load(3); 76 | uniqueLoader.clear(3); 77 | var b = await uniqueLoader.load(3); 78 | expect(a, isNot(b)); 79 | }); 80 | 81 | test('clearAll', () async { 82 | var a = await uniqueLoader.load(3); 83 | uniqueLoader.clearAll(); 84 | var b = await uniqueLoader.load(3); 85 | expect(a, isNot(b)); 86 | }); 87 | 88 | test('prime', () async { 89 | uniqueLoader.prime(3, _Unique('hey')); 90 | var a = await uniqueLoader.load(3); 91 | expect(a.value, 'hey'); 92 | }); 93 | }); 94 | } 95 | 96 | class _Unique { 97 | final String value; 98 | 99 | _Unique(this.value); 100 | } 101 | -------------------------------------------------------------------------------- /example_star_wars/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://www.dartlang.org/tools/private-files.html 2 | 3 | # Files and directories created by pub 4 | .buildlog 5 | .packages 6 | .project 7 | .pub/ 8 | .scripts-bin/ 9 | build/ 10 | **/packages/ 11 | 12 | # Files created by dart2js 13 | # (Most Dart developers will use pub build to compile Dart, use/modify these 14 | # rules if you intend to use dart2js directly 15 | # Convention is to use extension '.dart.js' for Dart compiled to Javascript to 16 | # differentiate from explicit Javascript files) 17 | *.dart.js 18 | *.part.js 19 | *.js.deps 20 | *.js.map 21 | *.info.json 22 | 23 | # Directory created by dartdoc 24 | doc/api/ 25 | 26 | # Don't commit pubspec lock file 27 | # (Library packages only! Remove pattern if developing an application package) 28 | pubspec.lock 29 | ### Dart template 30 | # See https://www.dartlang.org/tools/private-files.html 31 | 32 | # Files and directories created by pub 33 | 34 | # SDK 1.20 and later (no longer creates packages directories) 35 | 36 | # Older SDK versions 37 | # (Include if the minimum SDK version specified in pubsepc.yaml is earlier than 1.20) 38 | 39 | 40 | # Files created by dart2js 41 | # (Most Dart developers will use pub build to compile Dart, use/modify these 42 | # rules if you intend to use dart2js directly 43 | # Convention is to use extension '.dart.js' for Dart compiled to Javascript to 44 | # differentiate from explicit Javascript files) 45 | 46 | # Directory created by dartdoc 47 | 48 | # Don't commit pubspec lock file 49 | # (Library packages only! Remove pattern if developing an application package) 50 | ### JetBrains template 51 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 52 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 53 | 54 | # User-specific stuff: 55 | ../.idea/workspace.xml 56 | .idea/**/tasks.xml 57 | .idea/dictionaries 58 | 59 | # Sensitive or high-churn files: 60 | .idea/**/dataSources/ 61 | .idea/**/dataSources.ids 62 | .idea/**/dataSources.xml 63 | .idea/**/dataSources.local.xml 64 | .idea/**/sqlDataSources.xml 65 | .idea/**/dynamic.xml 66 | .idea/**/uiDesigner.xml 67 | 68 | # Gradle: 69 | .idea/**/gradle.xml 70 | .idea/**/libraries 71 | 72 | # Mongo Explorer plugin: 73 | .idea/**/mongoSettings.xml 74 | 75 | ## File-based project format: 76 | *.iws 77 | 78 | ## Plugin-specific files: 79 | 80 | # IntelliJ 81 | /out/ 82 | 83 | # mpeltonen/sbt-idea plugin 84 | .idea_modules/ 85 | 86 | # JIRA plugin 87 | atlassian-ide-plugin.xml 88 | 89 | # Crashlytics plugin (for Android Studio and IntelliJ) 90 | com_crashlytics_export_strings.xml 91 | crashlytics.properties 92 | crashlytics-build.properties 93 | fabric.properties 94 | -------------------------------------------------------------------------------- /example_star_wars/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 The Angel Framework 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | 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 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /example_star_wars/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | analyzer: 2 | strong-mode: 3 | implicit-casts: false -------------------------------------------------------------------------------- /example_star_wars/bin/server.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | 4 | import 'package:angel_framework/angel_framework.dart'; 5 | import 'package:angel_hot/angel_hot.dart'; 6 | import 'package:logging/logging.dart'; 7 | import 'package:star_wars/src/pretty_logging.dart' as star_wars; 8 | import 'package:star_wars/star_wars.dart' as star_wars; 9 | 10 | main() async { 11 | Future createServer() async { 12 | hierarchicalLoggingEnabled = true; 13 | var logger = Logger.detached('star_wars') 14 | ..onRecord.listen(star_wars.prettyLog); 15 | var app = Angel(logger: logger); 16 | await app.configure(star_wars.configureServer); 17 | return app; 18 | } 19 | 20 | var hot = HotReloader(createServer, [Directory('lib')]); 21 | 22 | var server = await hot.startServer('127.0.0.1', 3000); 23 | var serverUrl = 24 | Uri(scheme: 'http', host: server.address.address, port: server.port); 25 | var graphiQLUrl = serverUrl.replace(path: '/graphiql'); 26 | print('Listening at $serverUrl'); 27 | print('GraphiQL endpoint: $graphiQLUrl'); 28 | } 29 | -------------------------------------------------------------------------------- /example_star_wars/example_star_wars.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /example_star_wars/lib/src/models/character.dart: -------------------------------------------------------------------------------- 1 | import 'package:graphql_schema/graphql_schema.dart'; 2 | import 'episode.dart'; 3 | part 'character.g.dart'; 4 | 5 | @graphQLClass 6 | abstract class Character { 7 | String get id; 8 | 9 | String get name; 10 | 11 | // List get appearsIn; 12 | } 13 | -------------------------------------------------------------------------------- /example_star_wars/lib/src/models/character.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'character.dart'; 4 | 5 | // ************************************************************************** 6 | // _GraphQLGenerator 7 | // ************************************************************************** 8 | 9 | /// Auto-generated from [Character]. 10 | final GraphQLObjectType characterGraphQLType = objectType('Character', 11 | isInterface: true, 12 | interfaces: [], 13 | fields: [field('id', graphQLString), field('name', graphQLString)]); 14 | -------------------------------------------------------------------------------- /example_star_wars/lib/src/models/droid.dart: -------------------------------------------------------------------------------- 1 | import 'package:angel_model/angel_model.dart'; 2 | import 'package:angel_serialize/angel_serialize.dart'; 3 | import 'package:collection/collection.dart'; 4 | import 'package:graphql_schema/graphql_schema.dart'; 5 | import 'character.dart'; 6 | import 'episode.dart'; 7 | part 'droid.g.dart'; 8 | 9 | @serializable 10 | @graphQLClass 11 | @GraphQLDocumentation(description: 'Beep! Boop!') 12 | abstract class _Droid extends Model implements Character { 13 | String get id; 14 | 15 | String get name; 16 | 17 | @GraphQLDocumentation( 18 | description: 'The list of episodes this droid appears in.') 19 | List get appearsIn; 20 | 21 | /// Doc comments automatically become GraphQL descriptions. 22 | List get friends; 23 | } 24 | -------------------------------------------------------------------------------- /example_star_wars/lib/src/models/episode.dart: -------------------------------------------------------------------------------- 1 | import 'package:graphql_schema/graphql_schema.dart'; 2 | part 'episode.g.dart'; 3 | 4 | @GraphQLDocumentation( 5 | description: 'The episodes of the Star Wars original trilogy.') 6 | @graphQLClass 7 | enum Episode { 8 | NEWHOPE, 9 | EMPIRE, 10 | JEDI, 11 | } 12 | -------------------------------------------------------------------------------- /example_star_wars/lib/src/models/episode.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'episode.dart'; 4 | 5 | // ************************************************************************** 6 | // _GraphQLGenerator 7 | // ************************************************************************** 8 | 9 | /// Auto-generated from [Episode]. 10 | final GraphQLEnumType episodeGraphQLType = enumTypeFromStrings( 11 | 'Episode', const ['NEWHOPE', 'EMPIRE', 'JEDI'], 12 | description: 'The episodes of the Star Wars original trilogy.'); 13 | -------------------------------------------------------------------------------- /example_star_wars/lib/src/models/human.dart: -------------------------------------------------------------------------------- 1 | import 'package:angel_model/angel_model.dart'; 2 | import 'package:angel_serialize/angel_serialize.dart'; 3 | import 'package:collection/collection.dart'; 4 | import 'package:graphql_schema/graphql_schema.dart'; 5 | import 'character.dart'; 6 | import 'episode.dart'; 7 | part 'human.g.dart'; 8 | 9 | @serializable 10 | @graphQLClass 11 | abstract class _Human extends Model implements Character { 12 | // @GraphQLDocumentation(description: "This human's name, of course.") 13 | // String name; 14 | // List friends; 15 | // List appearsIn; 16 | // List starships; 17 | // int totalCredits; 18 | 19 | String get id; 20 | 21 | String get name; 22 | 23 | List get appearsIn; 24 | 25 | List get friends; 26 | 27 | int get totalCredits; 28 | 29 | // Human( 30 | // {this.name, 31 | // this.friends, 32 | // this.appearsIn, 33 | // this.starships, 34 | // this.totalCredits}); 35 | } 36 | -------------------------------------------------------------------------------- /example_star_wars/lib/src/models/models.dart: -------------------------------------------------------------------------------- 1 | export 'character.dart'; 2 | export 'droid.dart'; 3 | export 'episode.dart'; 4 | export 'human.dart'; 5 | export 'starship.dart'; 6 | -------------------------------------------------------------------------------- /example_star_wars/lib/src/models/starship.dart: -------------------------------------------------------------------------------- 1 | import 'package:angel_model/angel_model.dart'; 2 | import 'package:angel_serialize/angel_serialize.dart'; 3 | import 'package:graphql_schema/graphql_schema.dart'; 4 | part 'starship.g.dart'; 5 | 6 | @serializable 7 | @graphQLClass 8 | abstract class _Starship extends Model { 9 | String get name; 10 | int get length; 11 | } 12 | -------------------------------------------------------------------------------- /example_star_wars/lib/src/models/starship.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'starship.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonModelGenerator 7 | // ************************************************************************** 8 | 9 | @generatedSerializable 10 | class Starship extends _Starship { 11 | Starship({this.id, this.name, this.length, this.createdAt, this.updatedAt}); 12 | 13 | @override 14 | final String id; 15 | 16 | @override 17 | final String name; 18 | 19 | @override 20 | final int length; 21 | 22 | @override 23 | final DateTime createdAt; 24 | 25 | @override 26 | final DateTime updatedAt; 27 | 28 | Starship copyWith( 29 | {String id, 30 | String name, 31 | int length, 32 | DateTime createdAt, 33 | DateTime updatedAt}) { 34 | return new Starship( 35 | id: id ?? this.id, 36 | name: name ?? this.name, 37 | length: length ?? this.length, 38 | createdAt: createdAt ?? this.createdAt, 39 | updatedAt: updatedAt ?? this.updatedAt); 40 | } 41 | 42 | bool operator ==(other) { 43 | return other is _Starship && 44 | other.id == id && 45 | other.name == name && 46 | other.length == length && 47 | other.createdAt == createdAt && 48 | other.updatedAt == updatedAt; 49 | } 50 | 51 | @override 52 | int get hashCode { 53 | return hashObjects([id, name, length, createdAt, updatedAt]); 54 | } 55 | 56 | Map toJson() { 57 | return StarshipSerializer.toMap(this); 58 | } 59 | } 60 | 61 | // ************************************************************************** 62 | // SerializerGenerator 63 | // ************************************************************************** 64 | 65 | abstract class StarshipSerializer { 66 | static Starship fromMap(Map map) { 67 | return new Starship( 68 | id: map['id'] as String, 69 | name: map['name'] as String, 70 | length: map['length'] as int, 71 | createdAt: map['created_at'] != null 72 | ? (map['created_at'] is DateTime 73 | ? (map['created_at'] as DateTime) 74 | : DateTime.parse(map['created_at'].toString())) 75 | : null, 76 | updatedAt: map['updated_at'] != null 77 | ? (map['updated_at'] is DateTime 78 | ? (map['updated_at'] as DateTime) 79 | : DateTime.parse(map['updated_at'].toString())) 80 | : null); 81 | } 82 | 83 | static Map toMap(_Starship model) { 84 | if (model == null) { 85 | return null; 86 | } 87 | return { 88 | 'id': model.id, 89 | 'name': model.name, 90 | 'length': model.length, 91 | 'created_at': model.createdAt?.toIso8601String(), 92 | 'updated_at': model.updatedAt?.toIso8601String() 93 | }; 94 | } 95 | } 96 | 97 | abstract class StarshipFields { 98 | static const List allFields = [ 99 | id, 100 | name, 101 | length, 102 | createdAt, 103 | updatedAt 104 | ]; 105 | 106 | static const String id = 'id'; 107 | 108 | static const String name = 'name'; 109 | 110 | static const String length = 'length'; 111 | 112 | static const String createdAt = 'created_at'; 113 | 114 | static const String updatedAt = 'updated_at'; 115 | } 116 | 117 | // ************************************************************************** 118 | // _GraphQLGenerator 119 | // ************************************************************************** 120 | 121 | /// Auto-generated from [Starship]. 122 | final GraphQLObjectType starshipGraphQLType = 123 | objectType('Starship', isInterface: false, interfaces: [], fields: [ 124 | field('id', graphQLString), 125 | field('name', graphQLString), 126 | field('length', graphQLInt), 127 | field('created_at', graphQLDate), 128 | field('updated_at', graphQLDate), 129 | field('idAsInt', graphQLInt) 130 | ]); 131 | -------------------------------------------------------------------------------- /example_star_wars/lib/src/pretty_logging.dart: -------------------------------------------------------------------------------- 1 | import 'package:angel_http_exception/angel_http_exception.dart'; 2 | import 'package:logging/logging.dart'; 3 | import 'package:io/ansi.dart'; 4 | 5 | /// Prints the contents of a [LogRecord] with pretty colors. 6 | void prettyLog(LogRecord record) { 7 | var code = chooseLogColor(record.level); 8 | 9 | if (record.error == null) print(code.wrap(record.toString())); 10 | 11 | if (record.error != null) { 12 | var err = record.error; 13 | if (err is AngelHttpException && err.statusCode != 500) return; 14 | print(code.wrap(record.toString() + '\n')); 15 | print(code.wrap(err.toString())); 16 | 17 | if (record.stackTrace != null) { 18 | print(code.wrap(record.stackTrace.toString())); 19 | } 20 | } 21 | } 22 | 23 | /// Chooses a color based on the logger [level]. 24 | AnsiCode chooseLogColor(Level level) { 25 | if (level == Level.SHOUT) 26 | return backgroundRed; 27 | else if (level == Level.SEVERE) 28 | return red; 29 | else if (level == Level.WARNING) 30 | return yellow; 31 | else if (level == Level.INFO) 32 | return cyan; 33 | else if (level == Level.FINER || level == Level.FINEST) return lightGray; 34 | return resetAll; 35 | } 36 | -------------------------------------------------------------------------------- /example_star_wars/mono_pkg.yaml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angel-dart-archive/graphql/33e2f86ba73d559197b6270df036256104726aca/example_star_wars/mono_pkg.yaml -------------------------------------------------------------------------------- /example_star_wars/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: star_wars 2 | publish_to: none 3 | dependencies: 4 | #angel_file_service: ^1.0.0 5 | angel_graphql: 6 | path: ../angel_graphql 7 | angel_hot: ^2.0.0-alpha 8 | angel_serialize: ^2.0.0 9 | io: ^0.3.2 10 | dev_dependencies: 11 | angel_serialize_generator: ^2.0.0 12 | build_runner: ^1.0.0 13 | graphql_generator: 14 | path: ../graphql_generator 15 | dependency_overrides: 16 | graphql_parser: 17 | path: ../graphql_parser 18 | graphql_schema: 19 | path: ../graphql_schema 20 | graphql_server: 21 | path: ../graphql_server -------------------------------------------------------------------------------- /graphql.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /graphql_generator/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://www.dartlang.org/tools/private-files.html 2 | 3 | # Files and directories created by pub 4 | .buildlog 5 | .packages 6 | .project 7 | .pub/ 8 | .scripts-bin/ 9 | build/ 10 | **/packages/ 11 | 12 | # Files created by dart2js 13 | # (Most Dart developers will use pub build to compile Dart, use/modify these 14 | # rules if you intend to use dart2js directly 15 | # Convention is to use extension '.dart.js' for Dart compiled to Javascript to 16 | # differentiate from explicit Javascript files) 17 | *.dart.js 18 | *.part.js 19 | *.js.deps 20 | *.js.map 21 | *.info.json 22 | 23 | # Directory created by dartdoc 24 | doc/api/ 25 | 26 | # Don't commit pubspec lock file 27 | # (Library packages only! Remove pattern if developing an application package) 28 | pubspec.lock 29 | ### Dart template 30 | # See https://www.dartlang.org/tools/private-files.html 31 | 32 | # Files and directories created by pub 33 | 34 | # SDK 1.20 and later (no longer creates packages directories) 35 | 36 | # Older SDK versions 37 | # (Include if the minimum SDK version specified in pubsepc.yaml is earlier than 1.20) 38 | 39 | 40 | # Files created by dart2js 41 | # (Most Dart developers will use pub build to compile Dart, use/modify these 42 | # rules if you intend to use dart2js directly 43 | # Convention is to use extension '.dart.js' for Dart compiled to Javascript to 44 | # differentiate from explicit Javascript files) 45 | 46 | # Directory created by dartdoc 47 | 48 | # Don't commit pubspec lock file 49 | # (Library packages only! Remove pattern if developing an application package) 50 | ### JetBrains template 51 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 52 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 53 | 54 | # User-specific stuff: 55 | ../.idea/workspace.xml 56 | .idea/**/tasks.xml 57 | .idea/dictionaries 58 | 59 | # Sensitive or high-churn files: 60 | .idea/**/dataSources/ 61 | .idea/**/dataSources.ids 62 | .idea/**/dataSources.xml 63 | .idea/**/dataSources.local.xml 64 | .idea/**/sqlDataSources.xml 65 | .idea/**/dynamic.xml 66 | .idea/**/uiDesigner.xml 67 | 68 | # Gradle: 69 | .idea/**/gradle.xml 70 | .idea/**/libraries 71 | 72 | # Mongo Explorer plugin: 73 | .idea/**/mongoSettings.xml 74 | 75 | ## File-based project format: 76 | *.iws 77 | 78 | ## Plugin-specific files: 79 | 80 | # IntelliJ 81 | /out/ 82 | 83 | # mpeltonen/sbt-idea plugin 84 | .idea_modules/ 85 | 86 | # JIRA plugin 87 | atlassian-ide-plugin.xml 88 | 89 | # Crashlytics plugin (for Android Studio and IntelliJ) 90 | com_crashlytics_export_strings.xml 91 | crashlytics.properties 92 | crashlytics-build.properties 93 | fabric.properties 94 | -------------------------------------------------------------------------------- /graphql_generator/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 1.0.0+1 2 | * Replace `snakeCase` with `camelCase`. 3 | 4 | # 1.0.0 5 | * Apply `package:pedantic`. 6 | 7 | # 1.0.0-rc.1 8 | * Add `CHANGELOG.md`, `example/main.dart`. 9 | * Add documentation to `README.md`. -------------------------------------------------------------------------------- /graphql_generator/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 The Angel Framework 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | 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 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /graphql_generator/README.md: -------------------------------------------------------------------------------- 1 | # graphql_generator 2 | [![Pub](https://img.shields.io/pub/v/graphql_generator.svg)](https://pub.dartlang.org/packages/graphql_generator) 3 | [![build status](https://travis-ci.org/angel-dart/graphql.svg)](https://travis-ci.org/angel-dart/graphql) 4 | 5 | Generates `package:graphql_schema` schemas for 6 | annotated class. 7 | 8 | Replaces `convertDartType` from `package:graphql_server`. 9 | 10 | ## Usage 11 | Usage is very simple. You just need a `@graphQLClass` or `@GraphQLClass()` annotation 12 | on any class you want to generate an object type for. 13 | 14 | Individual fields can have a `@GraphQLDocumentation()` annotation, to provide information 15 | like descriptions, deprecation reasons, etc. 16 | 17 | ```dart 18 | @graphQLClass 19 | class Todo { 20 | String text; 21 | 22 | @GraphQLDocumentation(description: 'Whether this item is complete.') 23 | bool isComplete; 24 | } 25 | 26 | void main() { 27 | print(todoGraphQLType.fields.map((f) => f.name)); 28 | } 29 | ``` 30 | 31 | The following is generated (as of April 18th, 2019): 32 | 33 | ```dart 34 | // GENERATED CODE - DO NOT MODIFY BY HAND 35 | 36 | part of 'main.dart'; 37 | 38 | // ************************************************************************** 39 | // _GraphQLGenerator 40 | // ************************************************************************** 41 | 42 | /// Auto-generated from [Todo]. 43 | final GraphQLObjectType todoGraphQLType = objectType('Todo', 44 | isInterface: false, 45 | interfaces: [], 46 | fields: [ 47 | field('text', graphQLString), 48 | field('isComplete', graphQLBoolean) 49 | ]); 50 | ``` -------------------------------------------------------------------------------- /graphql_generator/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:pedantic/analysis_options.yaml 2 | analyzer: 3 | strong-mode: 4 | implicit-casts: false -------------------------------------------------------------------------------- /graphql_generator/build.yaml: -------------------------------------------------------------------------------- 1 | builders: 2 | graphql: 3 | import: "package:graphql_generator/graphql_generator.dart" 4 | builder_factories: 5 | - graphQLBuilder 6 | auto_apply: root_package 7 | build_to: cache 8 | build_extensions: 9 | .dart: 10 | - graphql_generator.g.part 11 | required_inputs: 12 | - angel_serialize.g.part 13 | applies_builders: 14 | - source_gen|combining_builder -------------------------------------------------------------------------------- /graphql_generator/example/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:graphql_schema/graphql_schema.dart'; 2 | part 'main.g.dart'; 3 | 4 | @graphQLClass 5 | class TodoItem { 6 | String text; 7 | 8 | @GraphQLDocumentation(description: 'Whether this item is complete.') 9 | bool isComplete; 10 | } 11 | 12 | void main() { 13 | print(todoItemGraphQLType.fields.map((f) => f.name)); 14 | } 15 | -------------------------------------------------------------------------------- /graphql_generator/example/main.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'main.dart'; 4 | 5 | // ************************************************************************** 6 | // _GraphQLGenerator 7 | // ************************************************************************** 8 | 9 | /// Auto-generated from [TodoItem]. 10 | final GraphQLObjectType todoItemGraphQLType = objectType('TodoItem', 11 | isInterface: false, 12 | interfaces: [], 13 | fields: [ 14 | field('text', graphQLString), 15 | field('isComplete', graphQLBoolean) 16 | ]); 17 | -------------------------------------------------------------------------------- /graphql_generator/mono_pkg.yaml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angel-dart-archive/graphql/33e2f86ba73d559197b6270df036256104726aca/graphql_generator/mono_pkg.yaml -------------------------------------------------------------------------------- /graphql_generator/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: graphql_generator 2 | version: 1.0.0+1 3 | description: Generates GraphQL schemas from Dart classes, for use with pkg:graphql_server. 4 | author: Tobe O 5 | homepage: https://github.com/angel-dart/graphql 6 | environment: 7 | sdk: ">=2.0.0 <3.0.0" 8 | dependencies: 9 | analyzer: ">=0.27.1 <2.0.0" 10 | angel_model: ^1.0.0 11 | angel_serialize_generator: ^2.0.0 12 | build: ^1.0.0 13 | build_config: ^0.3.0 14 | code_builder: ^3.0.0 15 | graphql_schema: ^1.0.2 16 | recase: ^2.0.0 17 | source_gen: ^0.9.4 18 | dev_dependencies: 19 | build_runner: ^1.0.0 20 | pedantic: ^1.0.0 -------------------------------------------------------------------------------- /graphql_parser/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://www.dartlang.org/tools/private-files.html 2 | 3 | # Files and directories created by pub 4 | .buildlog 5 | .packages 6 | .project 7 | .pub/ 8 | .scripts-bin/ 9 | build/ 10 | **/packages/ 11 | 12 | # Files created by dart2js 13 | # (Most Dart developers will use pub build to compile Dart, use/modify these 14 | # rules if you intend to use dart2js directly 15 | # Convention is to use extension '.dart.js' for Dart compiled to Javascript to 16 | # differentiate from explicit Javascript files) 17 | *.dart.js 18 | *.part.js 19 | *.js.deps 20 | *.js.map 21 | *.info.json 22 | 23 | # Directory created by dartdoc 24 | doc/api/ 25 | 26 | # Don't commit pubspec lock file 27 | # (Library packages only! Remove pattern if developing an application package) 28 | pubspec.lock 29 | ### Dart template 30 | # See https://www.dartlang.org/tools/private-files.html 31 | 32 | # Files and directories created by pub 33 | 34 | # SDK 1.20 and later (no longer creates packages directories) 35 | 36 | # Older SDK versions 37 | # (Include if the minimum SDK version specified in pubsepc.yaml is earlier than 1.20) 38 | 39 | 40 | # Files created by dart2js 41 | # (Most Dart developers will use pub build to compile Dart, use/modify these 42 | # rules if you intend to use dart2js directly 43 | # Convention is to use extension '.dart.js' for Dart compiled to Javascript to 44 | # differentiate from explicit Javascript files) 45 | 46 | # Directory created by dartdoc 47 | 48 | # Don't commit pubspec lock file 49 | # (Library packages only! Remove pattern if developing an application package) 50 | ### JetBrains template 51 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 52 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 53 | 54 | # User-specific stuff: 55 | ../.idea/workspace.xml 56 | .idea/**/tasks.xml 57 | .idea/dictionaries 58 | 59 | # Sensitive or high-churn files: 60 | .idea/**/dataSources/ 61 | .idea/**/dataSources.ids 62 | .idea/**/dataSources.xml 63 | .idea/**/dataSources.local.xml 64 | .idea/**/sqlDataSources.xml 65 | .idea/**/dynamic.xml 66 | .idea/**/uiDesigner.xml 67 | 68 | # Gradle: 69 | .idea/**/gradle.xml 70 | .idea/**/libraries 71 | 72 | # Mongo Explorer plugin: 73 | .idea/**/mongoSettings.xml 74 | 75 | ## File-based project format: 76 | *.iws 77 | 78 | ## Plugin-specific files: 79 | 80 | # IntelliJ 81 | /out/ 82 | 83 | # mpeltonen/sbt-idea plugin 84 | .idea_modules/ 85 | 86 | # JIRA plugin 87 | atlassian-ide-plugin.xml 88 | 89 | # Crashlytics plugin (for Android Studio and IntelliJ) 90 | com_crashlytics_export_strings.xml 91 | crashlytics.properties 92 | crashlytics-build.properties 93 | fabric.properties 94 | 95 | .dart_tool -------------------------------------------------------------------------------- /graphql_parser/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 1.2.0 2 | * Combine `ValueContext` and `VariableContext` into a single `InputValueContext` supertype. 3 | * Add `T computeValue(Map variables);` 4 | * Resolve [#23](https://github.com/angel-dart/graphql/issues/23). 5 | * Deprecate old `ValueOrVariable` class, and parser/AST methods related to it. 6 | 7 | # 1.1.4 8 | * Fix broken int variable parsing - https://github.com/angel-dart/graphql/pull/32 9 | 10 | # 1.1.3 11 | * Add `Parser.nextName`, and remove all formerly-reserved words from the lexer. 12 | Resolves [#19](https://github.com/angel-dart/graphql/issues/19). 13 | 14 | # 1.1.2 15 | * Parse the `subscription` keyword. 16 | 17 | # 1.1.1 18 | * Pubspec updates for Dart 2. 19 | 20 | # 1.1.0 21 | * Removed `GraphQLVisitor`. 22 | * Enable parsing operations without an explicit 23 | name. 24 | * Parse `null`. 25 | * Completely ignore commas. 26 | * Ignore Unicode BOM, as per the spec. 27 | * Parse object values. 28 | * Parse enum values. 29 | -------------------------------------------------------------------------------- /graphql_parser/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 The Angel Framework 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | 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 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /graphql_parser/README.md: -------------------------------------------------------------------------------- 1 | # graphql_parser 2 | [![Pub](https://img.shields.io/pub/v/graphql_parser.svg)](https://pub.dartlang.org/packages/graphql_parser) 3 | [![build status](https://travis-ci.org/angel-dart/graphql.svg)](https://travis-ci.org/angel-dart/graphql) 4 | 5 | Parses GraphQL queries and schemas. 6 | 7 | *This library is merely a parser/visitor*. Any sort of actual GraphQL API functionality must be implemented by you, 8 | or by a third-party package. 9 | 10 | [Angel framework](https://angel-dart.github.io) 11 | users should consider 12 | [`package:angel_graphql`](https://pub.dartlang.org/packages/angel_graphql) 13 | as a dead-simple way to add GraphQL functionality to their servers. 14 | 15 | # Installation 16 | Add `graphql_parser` as a dependency in your `pubspec.yaml` file: 17 | 18 | ```yaml 19 | dependencies: 20 | graphql_parser: ^1.0.0 21 | ``` 22 | 23 | # Usage 24 | The AST featured in this library was originally directly based off this ANTLR4 grammar created by Joseph T. McBride: 25 | https://github.com/antlr/grammars-v4/blob/master/graphql/GraphQL.g4 26 | 27 | It has since been updated to reflect upon the grammar in the official GraphQL 28 | specification ( 29 | [June 2018](https://facebook.github.io/graphql/June2018/)). 30 | 31 | ```dart 32 | import 'package:graphql_parser/graphql_parser.dart'; 33 | 34 | doSomething(String text) { 35 | var tokens = scan(text); 36 | var parser = Parser(tokens); 37 | 38 | if (parser.errors.isNotEmpty) { 39 | // Handle errors... 40 | } 41 | 42 | // Parse the GraphQL document using recursive descent 43 | var doc = parser.parseDocument(); 44 | 45 | // Do something with the parsed GraphQL document... 46 | } 47 | ``` 48 | -------------------------------------------------------------------------------- /graphql_parser/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:pedantic/analysis_options.yaml 2 | analyzer: 3 | strong-mode: 4 | implicit-casts: false -------------------------------------------------------------------------------- /graphql_parser/example/example.dart: -------------------------------------------------------------------------------- 1 | import 'package:graphql_parser/graphql_parser.dart'; 2 | 3 | final String text = ''' 4 | { 5 | project(name: "GraphQL") { 6 | tagline 7 | } 8 | } 9 | ''' 10 | .trim(); 11 | 12 | main() { 13 | var tokens = scan(text); 14 | var parser = Parser(tokens); 15 | var doc = parser.parseDocument(); 16 | 17 | var operation = doc.definitions.first as OperationDefinitionContext; 18 | 19 | var projectField = operation.selectionSet.selections.first.field; 20 | print(projectField.fieldName.name); // project 21 | print(projectField.arguments.first.name); // name 22 | print(projectField.arguments.first.value); // GraphQL 23 | 24 | var taglineField = projectField.selectionSet.selections.first.field; 25 | print(taglineField.fieldName.name); // tagline 26 | } 27 | -------------------------------------------------------------------------------- /graphql_parser/graphql_parser.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /graphql_parser/lib/graphql_parser.dart: -------------------------------------------------------------------------------- 1 | export 'src/language/ast/ast.dart'; 2 | export 'src/language/language.dart'; 3 | -------------------------------------------------------------------------------- /graphql_parser/lib/src/language/ast/alias.dart: -------------------------------------------------------------------------------- 1 | import 'package:source_span/source_span.dart'; 2 | import '../token.dart'; 3 | import 'node.dart'; 4 | 5 | /// An alternate name for a field within a [SelectionSet]. 6 | class AliasContext extends Node { 7 | /// The source tokens. 8 | final Token nameToken1, colonToken, nameToken2; 9 | 10 | AliasContext(this.nameToken1, this.colonToken, this.nameToken2); 11 | 12 | /// Use [nameToken1] instead. 13 | @deprecated 14 | Token get NAME1 => nameToken1; 15 | 16 | /// Use [colonToken] instead. 17 | @deprecated 18 | Token get COLON => colonToken; 19 | 20 | /// Use [nameToken2] instead. 21 | @deprecated 22 | Token get NAME2 => nameToken2; 23 | 24 | /// The aliased name of the value. 25 | String get alias => nameToken1.text; 26 | 27 | /// The actual name of the value. 28 | String get name => nameToken2.text; 29 | 30 | @override 31 | FileSpan get span => 32 | nameToken1.span.expand(colonToken.span).expand(nameToken2.span); 33 | } 34 | -------------------------------------------------------------------------------- /graphql_parser/lib/src/language/ast/argument.dart: -------------------------------------------------------------------------------- 1 | import 'package:source_span/source_span.dart'; 2 | import '../token.dart'; 3 | import 'node.dart'; 4 | import 'input_value.dart'; 5 | 6 | /// An argument passed to a [FieldContext]. 7 | class ArgumentContext extends Node { 8 | /// The source tokens. 9 | final Token nameToken, colonToken; 10 | 11 | /// The value of the argument. 12 | final InputValueContext value; 13 | 14 | ArgumentContext(this.nameToken, this.colonToken, this.value); 15 | 16 | /// Use [value] instead. 17 | @deprecated 18 | InputValueContext get valueOrVariable => value; 19 | 20 | /// Use [nameToken] instead. 21 | @deprecated 22 | Token get NAME => nameToken; 23 | 24 | /// Use [colonToken] instead. 25 | @deprecated 26 | Token get COLON => colonToken; 27 | 28 | /// The name of the argument, as a [String]. 29 | String get name => nameToken.text; 30 | 31 | @override 32 | FileSpan get span => 33 | nameToken.span.expand(colonToken.span).expand(value.span); 34 | } 35 | -------------------------------------------------------------------------------- /graphql_parser/lib/src/language/ast/array_value.dart: -------------------------------------------------------------------------------- 1 | import 'package:source_span/source_span.dart'; 2 | import '../token.dart'; 3 | import 'input_value.dart'; 4 | 5 | /// A GraphQL list value literal. 6 | class ListValueContext extends InputValueContext { 7 | /// The source tokens. 8 | final Token lBracketToken, rBracketToken; 9 | 10 | /// The child values. 11 | final List values = []; 12 | 13 | ListValueContext(this.lBracketToken, this.rBracketToken); 14 | 15 | /// Use [lBracketToken] instead. 16 | @deprecated 17 | Token get LBRACKET => lBracketToken; 18 | 19 | /// Use [rBracketToken] instead. 20 | @deprecated 21 | Token get RBRACKET => rBracketToken; 22 | 23 | @override 24 | FileSpan get span { 25 | var out = 26 | values.fold(lBracketToken.span, (o, v) => o.expand(v.span)); 27 | return out.expand(rBracketToken.span); 28 | } 29 | 30 | @override 31 | computeValue(Map variables) { 32 | return values.map((v) => v.computeValue(variables)).toList(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /graphql_parser/lib/src/language/ast/ast.dart: -------------------------------------------------------------------------------- 1 | library graphql_parser.language.ast; 2 | 3 | export 'alias.dart'; 4 | export 'array_value.dart'; 5 | export 'argument.dart'; 6 | export 'boolean_value.dart'; 7 | export 'default_value.dart'; 8 | export 'definition.dart'; 9 | export 'deprecated_value.dart'; 10 | export 'directive.dart'; 11 | export 'document.dart'; 12 | export 'field.dart'; 13 | export 'field_name.dart'; 14 | export 'fragment_definition.dart'; 15 | export 'fragment_spread.dart'; 16 | export 'inline_fragment.dart'; 17 | export 'input_value.dart'; 18 | export 'list_type.dart'; 19 | export 'misc_value.dart'; 20 | export 'node.dart'; 21 | export 'number_value.dart'; 22 | export 'operation_definition.dart'; 23 | export 'selection.dart'; 24 | export 'selection_set.dart'; 25 | export 'string_value.dart'; 26 | export 'type.dart'; 27 | export 'type_condition.dart'; 28 | export 'type_name.dart'; 29 | export 'variable.dart'; 30 | export 'variable_definition.dart'; 31 | export 'variable_definitions.dart'; 32 | -------------------------------------------------------------------------------- /graphql_parser/lib/src/language/ast/boolean_value.dart: -------------------------------------------------------------------------------- 1 | import 'package:source_span/source_span.dart'; 2 | import 'input_value.dart'; 3 | import '../token.dart'; 4 | 5 | /// A GraphQL boolean value literal. 6 | class BooleanValueContext extends InputValueContext { 7 | bool _valueCache; 8 | 9 | /// The source token. 10 | final Token booleanToken; 11 | 12 | BooleanValueContext(this.booleanToken) { 13 | assert(booleanToken?.text == 'true' || booleanToken?.text == 'false'); 14 | } 15 | 16 | /// The [bool] value of this literal. 17 | bool get booleanValue => _valueCache ??= booleanToken.text == 'true'; 18 | 19 | /// Use [booleanToken] instead. 20 | @deprecated 21 | Token get BOOLEAN => booleanToken; 22 | 23 | @override 24 | FileSpan get span => booleanToken.span; 25 | 26 | @override 27 | bool computeValue(Map variables) => booleanValue; 28 | } 29 | -------------------------------------------------------------------------------- /graphql_parser/lib/src/language/ast/default_value.dart: -------------------------------------------------------------------------------- 1 | import 'package:source_span/source_span.dart'; 2 | import '../token.dart'; 3 | import 'input_value.dart'; 4 | import 'node.dart'; 5 | 6 | /// The default value to be passed to an [ArgumentContext]. 7 | class DefaultValueContext extends Node { 8 | /// The source token. 9 | final Token equalsToken; 10 | 11 | /// The default value for the argument. 12 | final InputValueContext value; 13 | 14 | DefaultValueContext(this.equalsToken, this.value); 15 | 16 | /// Use [equalsToken] instead. 17 | @deprecated 18 | Token get EQUALS => equalsToken; 19 | 20 | @override 21 | FileSpan get span => equalsToken.span.expand(value.span); 22 | } 23 | -------------------------------------------------------------------------------- /graphql_parser/lib/src/language/ast/definition.dart: -------------------------------------------------------------------------------- 1 | import 'node.dart'; 2 | 3 | /// The base class for top-level GraphQL definitions. 4 | abstract class DefinitionContext extends Node {} 5 | 6 | /// An executable definition. 7 | abstract class ExecutableDefinitionContext extends DefinitionContext {} 8 | 9 | /// An ad-hoc type system declared in GraphQL. 10 | abstract class TypeSystemDefinitionContext extends DefinitionContext {} 11 | 12 | /// An extension to an existing ad-hoc type system. 13 | abstract class TypeSystemExtensionContext extends DefinitionContext {} 14 | -------------------------------------------------------------------------------- /graphql_parser/lib/src/language/ast/deprecated_value.dart: -------------------------------------------------------------------------------- 1 | import 'input_value.dart'; 2 | 3 | /// Use [ConstantContext] instead. This class remains solely for backwards compatibility. 4 | @deprecated 5 | abstract class ValueContext extends InputValueContext { 6 | /// Return a constant value. 7 | T get value; 8 | 9 | @override 10 | T computeValue(Map variables) => value; 11 | } 12 | -------------------------------------------------------------------------------- /graphql_parser/lib/src/language/ast/directive.dart: -------------------------------------------------------------------------------- 1 | import 'package:source_span/source_span.dart'; 2 | import '../token.dart'; 3 | import 'argument.dart'; 4 | import 'input_value.dart'; 5 | import 'node.dart'; 6 | 7 | /// A GraphQL directive, which may or may not have runtime semantics. 8 | class DirectiveContext extends Node { 9 | /// The source tokens. 10 | final Token arrobaToken, nameToken, colonToken, lParenToken, rParenToken; 11 | 12 | /// The argument being passed as the directive. 13 | final ArgumentContext argument; 14 | 15 | /// The (optional) value being passed with the directive. 16 | final InputValueContext value; 17 | 18 | DirectiveContext(this.arrobaToken, this.nameToken, this.colonToken, 19 | this.lParenToken, this.rParenToken, this.argument, this.value) { 20 | assert(nameToken != null); 21 | } 22 | 23 | /// Use [value] instead. 24 | @deprecated 25 | InputValueContext get valueOrVariable => value; 26 | 27 | /// Use [arrobaToken] instead. 28 | @deprecated 29 | Token get ARROBA => arrobaToken; 30 | 31 | /// Use [nameToken] instead. 32 | @deprecated 33 | Token get NAME => nameToken; 34 | 35 | /// Use [colonToken] instead. 36 | @deprecated 37 | Token get COLON => colonToken; 38 | 39 | /// Use [lParenToken] instead. 40 | @deprecated 41 | Token get LPAREN => lParenToken; 42 | 43 | /// Use [rParenToken] instead. 44 | @deprecated 45 | Token get RPAREN => rParenToken; 46 | 47 | @override 48 | FileSpan get span { 49 | var out = arrobaToken.span.expand(nameToken.span); 50 | 51 | if (colonToken != null) { 52 | out = out.expand(colonToken.span).expand(value.span); 53 | } else if (lParenToken != null) { 54 | out = out 55 | .expand(lParenToken.span) 56 | .expand(argument.span) 57 | .expand(rParenToken.span); 58 | } 59 | 60 | return out; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /graphql_parser/lib/src/language/ast/document.dart: -------------------------------------------------------------------------------- 1 | import 'package:source_span/source_span.dart'; 2 | import 'definition.dart'; 3 | import 'node.dart'; 4 | 5 | /// A GraphQL document. 6 | class DocumentContext extends Node { 7 | /// The top-level definitions in the document. 8 | final List definitions = []; 9 | 10 | @override 11 | FileSpan get span { 12 | if (definitions.isEmpty) return null; 13 | return definitions 14 | .map((d) => d.span) 15 | .reduce((a, b) => a.expand(b)); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /graphql_parser/lib/src/language/ast/field.dart: -------------------------------------------------------------------------------- 1 | import 'package:source_span/source_span.dart'; 2 | import 'argument.dart'; 3 | import 'directive.dart'; 4 | import 'field_name.dart'; 5 | import 'node.dart'; 6 | import 'selection_set.dart'; 7 | 8 | /// A field in a GraphQL [SelectionSet]. 9 | class FieldContext extends Node { 10 | /// The name of this field. 11 | final FieldNameContext fieldName; 12 | 13 | /// Any arguments this field expects. 14 | final List arguments = []; 15 | 16 | /// Any directives affixed to this field. 17 | final List directives = []; 18 | 19 | /// The list of selections to resolve on an object. 20 | final SelectionSetContext selectionSet; 21 | 22 | FieldContext(this.fieldName, [this.selectionSet]); 23 | 24 | @override 25 | FileSpan get span { 26 | if (selectionSet != null) { 27 | return fieldName.span.expand(selectionSet.span); 28 | } else if (directives.isNotEmpty) { 29 | return directives.fold( 30 | fieldName.span, (out, d) => out.expand(d.span)); 31 | } 32 | if (arguments.isNotEmpty) { 33 | return arguments.fold( 34 | fieldName.span, (out, a) => out.expand(a.span)); 35 | } else { 36 | return fieldName.span; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /graphql_parser/lib/src/language/ast/field_name.dart: -------------------------------------------------------------------------------- 1 | import 'package:source_span/source_span.dart'; 2 | import '../token.dart'; 3 | import 'alias.dart'; 4 | import 'node.dart'; 5 | 6 | /// The name of a GraphQL [FieldContext], which may or may not be [alias]ed. 7 | class FieldNameContext extends Node { 8 | /// The source token. 9 | final Token nameToken; 10 | 11 | /// An (optional) alias for the field. 12 | final AliasContext alias; 13 | 14 | FieldNameContext(this.nameToken, [this.alias]) { 15 | assert(nameToken != null || alias != null); 16 | } 17 | 18 | /// Use [nameToken] instead. 19 | @deprecated 20 | Token get NAME => nameToken; 21 | 22 | /// The [String] value of the [nameToken], if any. 23 | String get name => nameToken?.text; 24 | 25 | @override 26 | FileSpan get span => alias?.span ?? nameToken.span; 27 | } 28 | -------------------------------------------------------------------------------- /graphql_parser/lib/src/language/ast/fragment_definition.dart: -------------------------------------------------------------------------------- 1 | import '../token.dart'; 2 | import 'definition.dart'; 3 | import 'directive.dart'; 4 | import 'package:source_span/source_span.dart'; 5 | import 'selection_set.dart'; 6 | import 'type_condition.dart'; 7 | 8 | /// A GraphQL query fragment definition. 9 | class FragmentDefinitionContext extends ExecutableDefinitionContext { 10 | /// The source tokens. 11 | final Token fragmentToken, nameToken, onToken; 12 | 13 | /// The type to which this fragment applies. 14 | final TypeConditionContext typeCondition; 15 | 16 | /// Any directives on the fragment. 17 | final List directives = []; 18 | 19 | /// The selections to apply when the [typeCondition] is met. 20 | final SelectionSetContext selectionSet; 21 | 22 | /// The [String] value of the [nameToken]. 23 | String get name => nameToken.text; 24 | 25 | FragmentDefinitionContext(this.fragmentToken, this.nameToken, this.onToken, 26 | this.typeCondition, this.selectionSet); 27 | 28 | /// Use [fragmentToken] instead. 29 | @deprecated 30 | Token get FRAGMENT => fragmentToken; 31 | 32 | /// Use [nameToken] instead. 33 | @deprecated 34 | Token get NAME => nameToken; 35 | 36 | /// Use [onToken] instead. 37 | @deprecated 38 | Token get ON => onToken; 39 | 40 | @override 41 | FileSpan get span { 42 | var out = fragmentToken.span 43 | .expand(nameToken.span) 44 | .expand(onToken.span) 45 | .expand(typeCondition.span); 46 | out = directives.fold(out, (o, d) => o.expand(d.span)); 47 | return out.expand(selectionSet.span); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /graphql_parser/lib/src/language/ast/fragment_spread.dart: -------------------------------------------------------------------------------- 1 | import '../token.dart'; 2 | import 'directive.dart'; 3 | import 'node.dart'; 4 | import 'package:source_span/source_span.dart'; 5 | 6 | /// A GraphQL fragment spread. 7 | class FragmentSpreadContext extends Node { 8 | /// The source tokens. 9 | final Token ellipsisToken, nameToken; 10 | 11 | /// Any directives affixed to this fragment spread. 12 | final List directives = []; 13 | 14 | FragmentSpreadContext(this.ellipsisToken, this.nameToken); 15 | 16 | /// The [String] value of the [nameToken]. 17 | String get name => nameToken.text; 18 | 19 | /// Use [ellipsisToken] instead. 20 | @deprecated 21 | Token get ELLIPSIS => ellipsisToken; 22 | 23 | /// Use [nameToken] instead. 24 | @deprecated 25 | Token get NAME => nameToken; 26 | 27 | @override 28 | FileSpan get span { 29 | var out = ellipsisToken.span.expand(nameToken.span); 30 | if (directives.isEmpty) return out; 31 | return directives.fold(out, (o, d) => o.expand(d.span)); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /graphql_parser/lib/src/language/ast/inline_fragment.dart: -------------------------------------------------------------------------------- 1 | import '../token.dart'; 2 | import 'directive.dart'; 3 | import 'node.dart'; 4 | import 'package:source_span/source_span.dart'; 5 | import 'selection_set.dart'; 6 | import 'type_condition.dart'; 7 | 8 | /// An inline fragment, which typically appears in a [SelectionSetContext]. 9 | class InlineFragmentContext extends Node { 10 | /// The source tokens. 11 | final Token ellipsisToken, onToken; 12 | 13 | /// The type which this fragment matches. 14 | final TypeConditionContext typeCondition; 15 | 16 | /// Any directives affixed to this inline fragment. 17 | final List directives = []; 18 | 19 | /// The selections applied when the [typeCondition] is met. 20 | final SelectionSetContext selectionSet; 21 | 22 | InlineFragmentContext( 23 | this.ellipsisToken, this.onToken, this.typeCondition, this.selectionSet); 24 | 25 | /// Use [ellipsisToken] instead. 26 | @deprecated 27 | Token get ELLIPSIS => ellipsisToken; 28 | 29 | /// Use [onToken] instead. 30 | @deprecated 31 | Token get ON => onToken; 32 | 33 | @override 34 | FileSpan get span { 35 | var out = 36 | ellipsisToken.span.expand(onToken.span).expand(typeCondition.span); 37 | out = directives.fold(out, (o, d) => o.expand(d.span)); 38 | return out.expand(selectionSet.span); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /graphql_parser/lib/src/language/ast/input_value.dart: -------------------------------------------------------------------------------- 1 | import 'node.dart'; 2 | 3 | /// Represents a value in GraphQL. 4 | abstract class InputValueContext extends Node { 5 | /// Computes the value, relative to some set of [variables]. 6 | T computeValue(Map variables); 7 | } 8 | -------------------------------------------------------------------------------- /graphql_parser/lib/src/language/ast/list_type.dart: -------------------------------------------------------------------------------- 1 | import '../token.dart'; 2 | import 'node.dart'; 3 | import 'package:source_span/source_span.dart'; 4 | import 'type.dart'; 5 | 6 | /// Represents a type that holds a list of another type. 7 | class ListTypeContext extends Node { 8 | /// The source tokens. 9 | final Token lBracketToken, rBracketToken; 10 | 11 | /// The inner type. 12 | final TypeContext innerType; 13 | 14 | ListTypeContext(this.lBracketToken, this.innerType, this.rBracketToken); 15 | 16 | /// Use [innerType] instead. 17 | @deprecated 18 | TypeContext get type => innerType; 19 | 20 | /// Use [lBracketToken] instead. 21 | @deprecated 22 | Token get LBRACKET => lBracketToken; 23 | 24 | /// Use [rBracketToken] instead. 25 | @deprecated 26 | Token get RBRACKET => rBracketToken; 27 | 28 | @override 29 | FileSpan get span => 30 | lBracketToken.span.expand(innerType.span).expand(rBracketToken.span); 31 | } 32 | -------------------------------------------------------------------------------- /graphql_parser/lib/src/language/ast/misc_value.dart: -------------------------------------------------------------------------------- 1 | import 'package:source_span/source_span.dart'; 2 | import '../token.dart'; 3 | import 'input_value.dart'; 4 | import 'node.dart'; 5 | 6 | /// A GraphQL `null` literal. 7 | class NullValueContext extends InputValueContext { 8 | /// The source token. 9 | final Token nullToken; 10 | 11 | NullValueContext(this.nullToken); 12 | 13 | /// Use [nullToken] instead. 14 | @deprecated 15 | Token get NULL => nullToken; 16 | 17 | @override 18 | FileSpan get span => nullToken.span; 19 | 20 | @override 21 | Null computeValue(Map variables) => null; 22 | } 23 | 24 | /// A GraphQL enumeration literal. 25 | class EnumValueContext extends InputValueContext { 26 | /// The source token. 27 | final Token nameToken; 28 | 29 | EnumValueContext(this.nameToken); 30 | 31 | /// Use [nameToken] instead. 32 | @deprecated 33 | Token get NAME => nameToken; 34 | 35 | @override 36 | FileSpan get span => nameToken.span; 37 | 38 | @override 39 | String computeValue(Map variables) => nameToken.span.text; 40 | } 41 | 42 | /// A GraphQL object literal. 43 | class ObjectValueContext extends InputValueContext> { 44 | /// The source tokens. 45 | final Token lBraceToken, rBraceToken; 46 | 47 | /// The fields in the object. 48 | final List fields; 49 | 50 | ObjectValueContext(this.lBraceToken, this.fields, this.rBraceToken); 51 | 52 | /// Use [lBraceToken] instead. 53 | Token get LBRACE => lBraceToken; 54 | 55 | /// Use [rBraceToken] instead. 56 | @deprecated 57 | Token get RBRACE => rBraceToken; 58 | 59 | @override 60 | FileSpan get span { 61 | var left = lBraceToken.span; 62 | 63 | for (var field in fields) { 64 | left = left.expand(field.span); 65 | } 66 | 67 | return left.expand(rBraceToken.span); 68 | } 69 | 70 | @override 71 | Map computeValue(Map variables) { 72 | if (fields.isEmpty) { 73 | return {}; 74 | } else { 75 | return fields.fold>({}, 76 | (map, field) { 77 | return map 78 | ..[field.nameToken.text] = field.value.computeValue(variables); 79 | }); 80 | } 81 | } 82 | } 83 | 84 | /// A field within an [ObjectValueContext]. 85 | class ObjectFieldContext extends Node { 86 | /// The source tokens. 87 | final Token nameToken, colonToken; 88 | 89 | /// The associated value. 90 | final InputValueContext value; 91 | 92 | ObjectFieldContext(this.nameToken, this.colonToken, this.value); 93 | 94 | /// Use [nameToken] instead. 95 | @deprecated 96 | Token get NAME => nameToken; 97 | 98 | /// Use [colonToken] instead. 99 | @deprecated 100 | Token get COLON => colonToken; 101 | 102 | @override 103 | FileSpan get span => 104 | nameToken.span.expand(colonToken.span).expand(value.span); 105 | } 106 | -------------------------------------------------------------------------------- /graphql_parser/lib/src/language/ast/node.dart: -------------------------------------------------------------------------------- 1 | import 'package:source_span/source_span.dart'; 2 | 3 | abstract class Node { 4 | FileSpan get span; 5 | } 6 | -------------------------------------------------------------------------------- /graphql_parser/lib/src/language/ast/number_value.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math' as math; 2 | import 'package:source_span/source_span.dart'; 3 | import '../token.dart'; 4 | import 'input_value.dart'; 5 | 6 | /// A GraphQL number literal. 7 | class NumberValueContext extends InputValueContext { 8 | /// The source token. 9 | final Token numberToken; 10 | 11 | NumberValueContext(this.numberToken); 12 | 13 | /// The [num] value of the [numberToken]. 14 | num get numberValue { 15 | var text = numberToken.text; 16 | if (!text.contains('E') && !text.contains('e')) { 17 | return num.parse(text); 18 | } else { 19 | var split = text.split(text.contains('E') ? 'E' : 'e'); 20 | var base = num.parse(split[0]); 21 | var exp = num.parse(split[1]); 22 | return base * math.pow(10, exp); 23 | } 24 | } 25 | 26 | /// Use [numberToken] instead. 27 | @deprecated 28 | Token get NUMBER => numberToken; 29 | 30 | @override 31 | FileSpan get span => numberToken.span; 32 | 33 | @override 34 | num computeValue(Map variables) => numberValue; 35 | } 36 | -------------------------------------------------------------------------------- /graphql_parser/lib/src/language/ast/operation_definition.dart: -------------------------------------------------------------------------------- 1 | import 'package:source_span/source_span.dart'; 2 | import '../token.dart'; 3 | import 'definition.dart'; 4 | import 'directive.dart'; 5 | import 'selection_set.dart'; 6 | import 'variable_definitions.dart'; 7 | 8 | /// An executable GraphQL operation definition. 9 | class OperationDefinitionContext extends ExecutableDefinitionContext { 10 | /// The source tokens. 11 | final Token typeToken, nameToken; 12 | 13 | /// The variables defined in the operation. 14 | final VariableDefinitionsContext variableDefinitions; 15 | 16 | /// Any directives affixed to this operation. 17 | final List directives = []; 18 | 19 | /// The selections to be applied to an object resolved in this operation. 20 | final SelectionSetContext selectionSet; 21 | 22 | /// Whether this operation is a `mutation`. 23 | bool get isMutation => typeToken?.text == 'mutation'; 24 | 25 | /// Whether this operation is a `subscription`. 26 | bool get isSubscription => typeToken?.text == 'subscription'; 27 | 28 | /// Whether this operation is a `query`. 29 | bool get isQuery => typeToken?.text == 'query' || typeToken == null; 30 | 31 | /// The [String] value of the [nameToken]. 32 | String get name => nameToken?.text; 33 | 34 | /// Use [nameToken] instead. 35 | @deprecated 36 | Token get NAME => nameToken; 37 | 38 | /// Use [typeToken] instead. 39 | @deprecated 40 | Token get TYPE => typeToken; 41 | 42 | OperationDefinitionContext(this.typeToken, this.nameToken, 43 | this.variableDefinitions, this.selectionSet) { 44 | assert(typeToken == null || 45 | typeToken.text == 'query' || 46 | typeToken.text == 'mutation' || 47 | typeToken.text == 'subscription'); 48 | } 49 | 50 | @override 51 | FileSpan get span { 52 | if (typeToken == null) return selectionSet.span; 53 | var out = nameToken == null 54 | ? typeToken.span 55 | : typeToken.span.expand(nameToken.span); 56 | out = directives.fold(out, (o, d) => o.expand(d.span)); 57 | return out.expand(selectionSet.span); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /graphql_parser/lib/src/language/ast/selection.dart: -------------------------------------------------------------------------------- 1 | import 'field.dart'; 2 | import 'fragment_spread.dart'; 3 | import 'inline_fragment.dart'; 4 | import 'node.dart'; 5 | import 'package:source_span/source_span.dart'; 6 | 7 | class SelectionContext extends Node { 8 | final FieldContext field; 9 | final FragmentSpreadContext fragmentSpread; 10 | final InlineFragmentContext inlineFragment; 11 | 12 | SelectionContext(this.field, [this.fragmentSpread, this.inlineFragment]) { 13 | assert(field != null || fragmentSpread != null || inlineFragment != null); 14 | } 15 | 16 | @override 17 | FileSpan get span => 18 | field?.span ?? fragmentSpread?.span ?? inlineFragment?.span; 19 | } 20 | -------------------------------------------------------------------------------- /graphql_parser/lib/src/language/ast/selection_set.dart: -------------------------------------------------------------------------------- 1 | import 'package:source_span/source_span.dart'; 2 | 3 | import '../token.dart'; 4 | import 'node.dart'; 5 | import 'selection.dart'; 6 | 7 | /// A set of GraphQL selections - fields, fragments, or inline fragments. 8 | class SelectionSetContext extends Node { 9 | /// The source tokens. 10 | final Token lBraceToken, rBraceToken; 11 | 12 | /// The selections to be applied. 13 | final List selections = []; 14 | 15 | SelectionSetContext(this.lBraceToken, this.rBraceToken); 16 | 17 | /// A synthetic [SelectionSetContext] produced from a set of [selections]. 18 | factory SelectionSetContext.merged(List selections) = 19 | _MergedSelectionSetContext; 20 | 21 | /// Use [lBraceToken] instead. 22 | @deprecated 23 | Token get LBRACE => lBraceToken; 24 | 25 | /// Use [rBraceToken] instead. 26 | @deprecated 27 | Token get RBRACE => rBraceToken; 28 | 29 | @override 30 | FileSpan get span { 31 | var out = selections.fold( 32 | lBraceToken.span, (out, s) => out.expand(s.span)); 33 | return out.expand(rBraceToken.span); 34 | } 35 | } 36 | 37 | class _MergedSelectionSetContext extends SelectionSetContext { 38 | final List selections; 39 | 40 | _MergedSelectionSetContext(this.selections) : super(null, null); 41 | 42 | @override 43 | FileSpan get span => 44 | selections.map((s) => s.span).reduce((a, b) => a.expand(b)); 45 | } 46 | -------------------------------------------------------------------------------- /graphql_parser/lib/src/language/ast/string_value.dart: -------------------------------------------------------------------------------- 1 | import 'package:charcode/charcode.dart'; 2 | import 'package:source_span/source_span.dart'; 3 | 4 | import '../syntax_error.dart'; 5 | import '../token.dart'; 6 | import 'input_value.dart'; 7 | 8 | /// A GraphQL string value literal. 9 | class StringValueContext extends InputValueContext { 10 | /// The source token. 11 | final Token stringToken; 12 | 13 | /// Whether this is a block string. 14 | final bool isBlockString; 15 | 16 | StringValueContext(this.stringToken, {this.isBlockString = false}); 17 | 18 | @override 19 | FileSpan get span => stringToken.span; 20 | 21 | /// Use [stringToken] instead. 22 | @deprecated 23 | Token get STRING => stringToken; 24 | 25 | /// The [String] value of the [stringToken]. 26 | String get stringValue { 27 | String text; 28 | 29 | if (!isBlockString) { 30 | text = stringToken.text.substring(1, stringToken.text.length - 1); 31 | } else { 32 | text = stringToken.text.substring(3, stringToken.text.length - 3).trim(); 33 | } 34 | 35 | var codeUnits = text.codeUnits; 36 | var buf = StringBuffer(); 37 | 38 | for (int i = 0; i < codeUnits.length; i++) { 39 | var ch = codeUnits[i]; 40 | 41 | if (ch == $backslash) { 42 | if (i < codeUnits.length - 5 && codeUnits[i + 1] == $u) { 43 | var c1 = codeUnits[i += 2], 44 | c2 = codeUnits[++i], 45 | c3 = codeUnits[++i], 46 | c4 = codeUnits[++i]; 47 | var hexString = String.fromCharCodes([c1, c2, c3, c4]); 48 | var hexNumber = int.parse(hexString, radix: 16); 49 | buf.write(String.fromCharCode(hexNumber)); 50 | continue; 51 | } 52 | 53 | if (i < codeUnits.length - 1) { 54 | var next = codeUnits[++i]; 55 | 56 | switch (next) { 57 | case $b: 58 | buf.write('\b'); 59 | break; 60 | case $f: 61 | buf.write('\f'); 62 | break; 63 | case $n: 64 | buf.writeCharCode($lf); 65 | break; 66 | case $r: 67 | buf.writeCharCode($cr); 68 | break; 69 | case $t: 70 | buf.writeCharCode($tab); 71 | break; 72 | default: 73 | buf.writeCharCode(next); 74 | } 75 | } else { 76 | throw SyntaxError('Unexpected "\\" in string literal.', span); 77 | } 78 | } else { 79 | buf.writeCharCode(ch); 80 | } 81 | } 82 | 83 | return buf.toString(); 84 | } 85 | 86 | @override 87 | String computeValue(Map variables) => stringValue; 88 | } 89 | -------------------------------------------------------------------------------- /graphql_parser/lib/src/language/ast/type.dart: -------------------------------------------------------------------------------- 1 | import 'package:source_span/source_span.dart'; 2 | import '../token.dart'; 3 | import 'list_type.dart'; 4 | import 'node.dart'; 5 | import 'type_name.dart'; 6 | 7 | /// A GraphQL type node. 8 | class TypeContext extends Node { 9 | /// A source token, present in a nullable type literal. 10 | final Token exclamationToken; 11 | 12 | /// The name of the referenced type. 13 | final TypeNameContext typeName; 14 | 15 | /// A list type that is being referenced. 16 | final ListTypeContext listType; 17 | 18 | /// Whether the type is nullable. 19 | bool get isNullable => exclamationToken == null; 20 | 21 | TypeContext(this.typeName, this.listType, [this.exclamationToken]) { 22 | assert(typeName != null || listType != null); 23 | } 24 | 25 | /// Use [exclamationToken] instead. 26 | @deprecated 27 | Token get EXCLAMATION => exclamationToken; 28 | 29 | @override 30 | FileSpan get span { 31 | var out = typeName?.span ?? listType.span; 32 | return exclamationToken != null ? out.expand(exclamationToken.span) : out; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /graphql_parser/lib/src/language/ast/type_condition.dart: -------------------------------------------------------------------------------- 1 | import 'node.dart'; 2 | import 'package:source_span/source_span.dart'; 3 | import 'type_name.dart'; 4 | 5 | class TypeConditionContext extends Node { 6 | final TypeNameContext typeName; 7 | 8 | TypeConditionContext(this.typeName); 9 | 10 | @override 11 | FileSpan get span => typeName.span; 12 | } 13 | -------------------------------------------------------------------------------- /graphql_parser/lib/src/language/ast/type_name.dart: -------------------------------------------------------------------------------- 1 | import 'node.dart'; 2 | import 'package:source_span/source_span.dart'; 3 | import '../token.dart'; 4 | 5 | /// The name of a GraphQL type. 6 | class TypeNameContext extends Node { 7 | /// The source token. 8 | final Token nameToken; 9 | 10 | TypeNameContext(this.nameToken); 11 | 12 | /// Use [nameToken] instead. 13 | @deprecated 14 | Token get NAME => nameToken; 15 | 16 | /// The [String] value of the [nameToken]. 17 | String get name => nameToken.text; 18 | 19 | @override 20 | FileSpan get span => nameToken.span; 21 | } 22 | -------------------------------------------------------------------------------- /graphql_parser/lib/src/language/ast/variable.dart: -------------------------------------------------------------------------------- 1 | import 'package:source_span/source_span.dart'; 2 | import '../token.dart'; 3 | import 'input_value.dart'; 4 | 5 | /// A variable reference in GraphQL. 6 | class VariableContext extends InputValueContext { 7 | /// The source tokens. 8 | final Token dollarToken, nameToken; 9 | 10 | VariableContext(this.dollarToken, this.nameToken); 11 | 12 | /// The [String] value of the [nameToken]. 13 | String get name => nameToken.text; 14 | 15 | /// Use [dollarToken] instead. 16 | @deprecated 17 | Token get DOLLAR => dollarToken; 18 | 19 | /// Use [nameToken] instead. 20 | @deprecated 21 | Token get NAME => nameToken; 22 | 23 | @override 24 | FileSpan get span => dollarToken.span.expand(nameToken.span); 25 | 26 | @override 27 | Object computeValue(Map variables) => variables[name]; 28 | } 29 | -------------------------------------------------------------------------------- /graphql_parser/lib/src/language/ast/variable_definition.dart: -------------------------------------------------------------------------------- 1 | import '../token.dart'; 2 | import 'node.dart'; 3 | import 'default_value.dart'; 4 | import 'package:source_span/source_span.dart'; 5 | import 'type.dart'; 6 | import 'variable.dart'; 7 | 8 | /// A single variable definition. 9 | class VariableDefinitionContext extends Node { 10 | /// The source token. 11 | final Token colonToken; 12 | 13 | /// The declared variable. 14 | final VariableContext variable; 15 | 16 | /// The type of the variable. 17 | final TypeContext type; 18 | 19 | /// The default value of the variable. 20 | final DefaultValueContext defaultValue; 21 | 22 | VariableDefinitionContext(this.variable, this.colonToken, this.type, 23 | [this.defaultValue]); 24 | 25 | /// Use [colonToken] instead. 26 | @deprecated 27 | Token get COLON => colonToken; 28 | 29 | @override 30 | FileSpan get span => variable.span.expand(defaultValue?.span ?? type.span); 31 | } 32 | -------------------------------------------------------------------------------- /graphql_parser/lib/src/language/ast/variable_definitions.dart: -------------------------------------------------------------------------------- 1 | import '../token.dart'; 2 | import 'node.dart'; 3 | import 'package:source_span/source_span.dart'; 4 | import 'variable_definition.dart'; 5 | 6 | /// A set of variable definitions in a GraphQL operation. 7 | class VariableDefinitionsContext extends Node { 8 | /// The source tokens. 9 | final Token lParenToken, rParenToken; 10 | 11 | /// The variables defined in this node. 12 | final List variableDefinitions = []; 13 | 14 | VariableDefinitionsContext(this.lParenToken, this.rParenToken); 15 | 16 | /// Use [lParenToken] instead. 17 | @deprecated 18 | Token get LPAREN => lParenToken; 19 | 20 | /// Use [rParenToken] instead. 21 | @deprecated 22 | Token get RPAREN => rParenToken; 23 | 24 | @override 25 | FileSpan get span { 26 | var out = variableDefinitions.fold( 27 | lParenToken.span, (o, v) => o.expand(v.span)); 28 | return out.expand(rParenToken.span); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /graphql_parser/lib/src/language/language.dart: -------------------------------------------------------------------------------- 1 | library graphql_parser.language; 2 | 3 | export 'lexer.dart'; 4 | export 'parser.dart'; 5 | export 'syntax_error.dart'; 6 | export 'token.dart'; 7 | export 'token_type.dart'; 8 | -------------------------------------------------------------------------------- /graphql_parser/lib/src/language/lexer.dart: -------------------------------------------------------------------------------- 1 | import 'package:string_scanner/string_scanner.dart'; 2 | 3 | import 'syntax_error.dart'; 4 | import 'token.dart'; 5 | import 'token_type.dart'; 6 | 7 | final RegExp _comment = RegExp(r'#[^\n]*'); 8 | final RegExp _whitespace = RegExp('[ \t\n\r]+'); 9 | // final RegExp _boolean = RegExp(r'true|false'); 10 | final RegExp _number = RegExp(r'-?[0-9]+(\.[0-9]+)?(E|e(\+|-)?[0-9]+)?'); 11 | final RegExp _string = RegExp( 12 | r'"((\\(["\\/bfnrt]|(u[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])))|([^"\\]))*"'); 13 | final RegExp _blockString = RegExp(r'"""(([^"])|(\\"""))*"""'); 14 | final RegExp _name = RegExp(r'[_A-Za-z][_0-9A-Za-z]*'); 15 | 16 | final Map _patterns = { 17 | '@': TokenType.ARROBA, 18 | ':': TokenType.COLON, 19 | ',': TokenType.COMMA, 20 | r'$': TokenType.DOLLAR, 21 | '...': TokenType.ELLIPSIS, 22 | '=': TokenType.EQUALS, 23 | '!': TokenType.EXCLAMATION, 24 | '{': TokenType.LBRACE, 25 | '}': TokenType.RBRACE, 26 | '[': TokenType.LBRACKET, 27 | ']': TokenType.RBRACKET, 28 | '(': TokenType.LPAREN, 29 | ')': TokenType.RPAREN, 30 | // 'fragment': TokenType.FRAGMENT, 31 | // 'mutation': TokenType.MUTATION, 32 | // 'subscription': TokenType.SUBSCRIPTION, 33 | // 'on': TokenType.ON, 34 | // 'query': TokenType.QUERY, 35 | // 'null': TokenType.NULL, 36 | // _boolean: TokenType.BOOLEAN, 37 | _number: TokenType.NUMBER, 38 | _string: TokenType.STRING, 39 | _blockString: TokenType.BLOCK_STRING, 40 | _name: TokenType.NAME 41 | }; 42 | 43 | List scan(String text, {sourceUrl}) { 44 | List out = []; 45 | var scanner = SpanScanner(text, sourceUrl: sourceUrl); 46 | 47 | while (!scanner.isDone) { 48 | List potential = []; 49 | 50 | if (scanner.scan(_comment) || 51 | scanner.scan(_whitespace) || 52 | scanner.scan(',') || 53 | scanner.scan('\ufeff')) continue; 54 | 55 | for (var pattern in _patterns.keys) { 56 | if (scanner.matches(pattern)) { 57 | potential.add( 58 | Token(_patterns[pattern], scanner.lastMatch[0], scanner.lastSpan)); 59 | } 60 | } 61 | 62 | if (potential.isEmpty) { 63 | var ch = String.fromCharCode(scanner.readChar()); 64 | throw SyntaxError("Unexpected token '$ch'.", scanner.emptySpan); 65 | } else { 66 | // Choose longest token 67 | potential.sort((a, b) => b.text.length.compareTo(a.text.length)); 68 | var chosen = potential.first; 69 | out.add(chosen); 70 | scanner.scan(chosen.text); 71 | } 72 | } 73 | 74 | return out; 75 | } 76 | -------------------------------------------------------------------------------- /graphql_parser/lib/src/language/syntax_error.dart: -------------------------------------------------------------------------------- 1 | import 'package:source_span/source_span.dart'; 2 | 3 | class SyntaxError implements Exception { 4 | final String message; 5 | final FileSpan span; 6 | 7 | SyntaxError(this.message, this.span); 8 | 9 | @override 10 | String toString() => 11 | 'Syntax error at ${span.start.toolString}: $message\n${span.highlight()}'; 12 | } 13 | -------------------------------------------------------------------------------- /graphql_parser/lib/src/language/token.dart: -------------------------------------------------------------------------------- 1 | import 'package:source_span/source_span.dart'; 2 | import 'token_type.dart'; 3 | 4 | class Token { 5 | final TokenType type; 6 | final String text; 7 | FileSpan span; 8 | 9 | Token(this.type, this.text, [this.span]); 10 | 11 | @override 12 | String toString() { 13 | if (span == null) { 14 | return "'$text' -> $type"; 15 | } else { 16 | return "(${span.start.line}:${span.start.column}) '$text' -> $type"; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /graphql_parser/lib/src/language/token_type.dart: -------------------------------------------------------------------------------- 1 | enum TokenType { 2 | ARROBA, 3 | COLON, 4 | COMMA, 5 | DOLLAR, 6 | ELLIPSIS, 7 | EQUALS, 8 | EXCLAMATION, 9 | LBRACE, 10 | RBRACE, 11 | LBRACKET, 12 | RBRACKET, 13 | LPAREN, 14 | RPAREN, 15 | 16 | // Note: these are *not* reserved names. 17 | // FRAGMENT, 18 | // MUTATION, 19 | // SUBSCRIPTION, 20 | // ON, 21 | // QUERY, 22 | // NULL 23 | // BOOLEAN, 24 | 25 | NUMBER, 26 | STRING, 27 | BLOCK_STRING, 28 | 29 | NAME, 30 | } 31 | -------------------------------------------------------------------------------- /graphql_parser/mono_pkg.yaml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angel-dart-archive/graphql/33e2f86ba73d559197b6270df036256104726aca/graphql_parser/mono_pkg.yaml -------------------------------------------------------------------------------- /graphql_parser/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: graphql_parser 2 | version: 1.1.4 3 | description: Parses GraphQL queries and schemas. Also includes classes for the GraphQL AST. 4 | author: Tobe O 5 | homepage: https://github.com/angel-dart/graphql 6 | environment: 7 | sdk: ">=1.8.0 <3.0.0" 8 | dependencies: 9 | charcode: ^1.0.0 10 | source_span: ^1.0.0 11 | string_scanner: ^1.0.0 12 | dev_dependencies: 13 | matcher: any 14 | pedantic: ^1.0.0 15 | test: ">=0.12.0 <2.0.0" 16 | -------------------------------------------------------------------------------- /graphql_parser/test/argument_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:graphql_parser/graphql_parser.dart'; 2 | import 'package:matcher/matcher.dart'; 3 | import 'package:test/test.dart'; 4 | 5 | import 'common.dart'; 6 | 7 | main() { 8 | test('argument', () { 9 | expect('foo: 2', isArgument('foo', 2)); 10 | expect(r'foo: $bar', isArgument('foo', 'bar')); 11 | }); 12 | 13 | test('exception', () { 14 | var isSyntaxError = predicate((x) { 15 | var parser = parse(x.toString())..parseArgument(); 16 | return parser.errors.isNotEmpty; 17 | }, 'fails to parse argument'); 18 | 19 | var isSyntaxErrorOnArguments = predicate((x) { 20 | var parser = parse(x.toString())..parseArguments(); 21 | return parser.errors.isNotEmpty; 22 | }, 'fails to parse arguments'); 23 | 24 | expect('foo', isSyntaxError); 25 | expect('foo:', isSyntaxError); 26 | expect(r'(foo: $bar', isSyntaxErrorOnArguments); 27 | }); 28 | } 29 | 30 | ArgumentContext parseArgument(String text) => parse(text).parseArgument(); 31 | 32 | List parseArgumentList(String text) => 33 | parse(text).parseArguments(); 34 | 35 | Matcher isArgument(String name, value) => _IsArgument(name, value); 36 | 37 | Matcher isArgumentList(List arguments) => _IsArgumentList(arguments); 38 | 39 | class _IsArgument extends Matcher { 40 | final String name; 41 | final value; 42 | 43 | _IsArgument(this.name, this.value); 44 | 45 | @override 46 | Description describe(Description description) { 47 | return description.add('is an argument named "$name" with value $value'); 48 | } 49 | 50 | @override 51 | bool matches(item, Map matchState) { 52 | var arg = item is ArgumentContext ? item : parseArgument(item.toString()); 53 | if (arg == null) return false; 54 | print(arg.span.highlight()); 55 | 56 | var v = arg.value; 57 | return equals(name).matches(arg.name, matchState) && 58 | ((v is VariableContext && equals(value).matches(v.name, matchState)) || 59 | equals(value).matches(arg.value.computeValue({}), matchState)); 60 | } 61 | } 62 | 63 | class _IsArgumentList extends Matcher { 64 | final List arguments; 65 | 66 | _IsArgumentList(this.arguments); 67 | 68 | @override 69 | Description describe(Description description) { 70 | return description.add('is list of ${arguments.length} argument(s)'); 71 | } 72 | 73 | @override 74 | bool matches(item, Map matchState) { 75 | var args = item is List 76 | ? item 77 | : parse(item.toString()).parseArguments(); 78 | 79 | if (args.length != arguments.length) return false; 80 | 81 | for (int i = 0; i < args.length; i++) { 82 | if (!arguments[i].matches(args[i], matchState)) return false; 83 | } 84 | 85 | return true; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /graphql_parser/test/comment_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:graphql_parser/graphql_parser.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | void main() { 5 | test('heeds comments', () { 6 | var tokens = scan(''' 7 | # Hello 8 | { 9 | # Goodbye 10 | } 11 | # Bonjour 12 | '''); 13 | 14 | expect(tokens, hasLength(2)); 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /graphql_parser/test/common.dart: -------------------------------------------------------------------------------- 1 | import 'package:graphql_parser/graphql_parser.dart'; 2 | 3 | Parser parse(String text) => Parser(scan(text)); 4 | -------------------------------------------------------------------------------- /graphql_parser/test/directive_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:graphql_parser/graphql_parser.dart'; 2 | import 'package:matcher/matcher.dart'; 3 | import 'package:test/test.dart'; 4 | import 'argument_test.dart'; 5 | import 'common.dart'; 6 | 7 | main() { 8 | test('name only', () { 9 | expect('@foo', isDirective('foo')); 10 | }); 11 | 12 | test('with value or variable', () { 13 | expect('@foo: 2', isDirective('foo', valueOrVariable: equals(2))); 14 | expect(r'@foo: $bar', isDirective('foo', valueOrVariable: equals('bar'))); 15 | }); 16 | 17 | test('with argument', () { 18 | expect('@foo (bar: 2)', isDirective('foo', argument: isArgument('bar', 2))); 19 | expect(r'@foo (bar: $baz)', 20 | isDirective('foo', argument: isArgument('bar', r'baz'))); 21 | }); 22 | 23 | test('exceptions', () { 24 | var isSyntaxError = predicate((x) { 25 | var parser = parse(x.toString())..parseDirective(); 26 | return parser.errors.isNotEmpty; 27 | }, 'fails to parse directive'); 28 | 29 | expect('@', isSyntaxError); 30 | expect('@foo:', isSyntaxError); 31 | expect('@foo (', isSyntaxError); 32 | expect('@foo (bar: 2', isSyntaxError); 33 | expect('@foo (', isSyntaxError); 34 | }); 35 | } 36 | 37 | DirectiveContext parseDirective(String text) => parse(text).parseDirective(); 38 | 39 | Matcher isDirective(String name, {Matcher valueOrVariable, Matcher argument}) => 40 | _IsDirective(name, valueOrVariable: valueOrVariable, argument: argument); 41 | 42 | Matcher isDirectiveList(List directives) => 43 | _IsDirectiveList(directives); 44 | 45 | class _IsDirective extends Matcher { 46 | final String name; 47 | final Matcher valueOrVariable, argument; 48 | 49 | _IsDirective(this.name, {this.valueOrVariable, this.argument}); 50 | 51 | @override 52 | Description describe(Description description) { 53 | var desc = description.add('is a directive with name "$name"'); 54 | 55 | if (valueOrVariable != null) { 56 | return valueOrVariable.describe(desc.add(' and ')); 57 | } else if (argument != null) { 58 | return argument.describe(desc.add(' and ')); 59 | } else { 60 | return desc; 61 | } 62 | } 63 | 64 | @override 65 | bool matches(item, Map matchState) { 66 | var directive = 67 | item is DirectiveContext ? item : parseDirective(item.toString()); 68 | if (directive == null) return false; 69 | if (valueOrVariable != null) { 70 | if (directive.value == null) { 71 | return false; 72 | } else { 73 | var v = directive.value; 74 | if (v is VariableContext) { 75 | return valueOrVariable.matches(v.name, matchState); 76 | } else { 77 | return valueOrVariable.matches( 78 | directive.value.computeValue({}), matchState); 79 | } 80 | } 81 | } else if (argument != null) { 82 | if (directive.argument == null) { 83 | return false; 84 | } else { 85 | return argument.matches(directive.argument, matchState); 86 | } 87 | } else { 88 | return true; 89 | } 90 | } 91 | } 92 | 93 | class _IsDirectiveList extends Matcher { 94 | final List directives; 95 | 96 | _IsDirectiveList(this.directives); 97 | 98 | @override 99 | Description describe(Description description) { 100 | return description.add('is list of ${directives.length} directive(s)'); 101 | } 102 | 103 | @override 104 | bool matches(item, Map matchState) { 105 | var args = item is List 106 | ? item 107 | : parse(item.toString()).parseDirectives(); 108 | 109 | if (args.length != directives.length) return false; 110 | 111 | for (int i = 0; i < args.length; i++) { 112 | if (!directives[i].matches(args[i], matchState)) return false; 113 | } 114 | 115 | return true; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /graphql_parser/test/document_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:graphql_parser/graphql_parser.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | import 'common.dart'; 5 | import 'directive_test.dart'; 6 | import 'field_test.dart'; 7 | import 'selection_set_test.dart'; 8 | import 'type_test.dart'; 9 | import 'value_test.dart'; 10 | import 'variable_definition_test.dart'; 11 | 12 | main() { 13 | test('fragment', () { 14 | var fragment = parse(''' 15 | fragment PostInfo on Post { 16 | description 17 | comments { 18 | id 19 | } 20 | } 21 | ''').parseFragmentDefinition(); 22 | 23 | expect(fragment, isNotNull); 24 | expect(fragment.name, 'PostInfo'); 25 | expect(fragment.typeCondition.typeName.name, 'Post'); 26 | expect( 27 | fragment.selectionSet, 28 | isSelectionSet([ 29 | isField(fieldName: isFieldName('description')), 30 | isField( 31 | fieldName: isFieldName('comments'), 32 | selectionSet: 33 | isSelectionSet([isField(fieldName: isFieldName('id'))])), 34 | ])); 35 | }); 36 | 37 | test('fragment exceptions', () { 38 | var throwsSyntaxError = predicate((x) { 39 | var parser = parse(x.toString())..parseFragmentDefinition(); 40 | return parser.errors.isNotEmpty; 41 | }, 'fails to parse fragment definition'); 42 | 43 | expect('fragment', throwsSyntaxError); 44 | expect('fragment foo', throwsSyntaxError); 45 | expect('fragment foo on', throwsSyntaxError); 46 | expect('fragment foo on bar', throwsSyntaxError); 47 | }); 48 | 49 | group('operation', () { 50 | test('with selection set', () { 51 | var op = parse('{foo, bar: baz}').parseOperationDefinition(); 52 | expect(op.variableDefinitions, isNull); 53 | expect(op.isQuery, isTrue); 54 | expect(op.isMutation, isFalse); 55 | expect(op.name, isNull); 56 | expect( 57 | op.selectionSet, 58 | isSelectionSet([ 59 | isField(fieldName: isFieldName('foo')), 60 | isField(fieldName: isFieldName('bar', alias: 'baz')) 61 | ])); 62 | }); 63 | 64 | test('mutation', () { 65 | var op = parse('mutation {foo, bar: baz}').parseOperationDefinition(); 66 | expect(op.variableDefinitions, isNull); 67 | expect(op.isQuery, isFalse); 68 | expect(op.isMutation, isTrue); 69 | expect(op.name, isNull); 70 | expect( 71 | op.selectionSet, 72 | isSelectionSet([ 73 | isField(fieldName: isFieldName('foo')), 74 | isField(fieldName: isFieldName('bar', alias: 'baz')) 75 | ])); 76 | }); 77 | 78 | test('with operation type', () { 79 | var doc = 80 | parse(r'query foo ($one: [int] = 2) @foo @bar: 2 {foo, bar: baz}') 81 | .parseDocument(); 82 | print(doc.span.highlight()); 83 | expect(doc.definitions, hasLength(1)); 84 | expect(doc.definitions.first is OperationDefinitionContext, isTrue); 85 | var op = doc.definitions.first as OperationDefinitionContext; 86 | expect(op.isMutation, isFalse); 87 | expect(op.isQuery, isTrue); 88 | 89 | expect(op.variableDefinitions.variableDefinitions, hasLength(1)); 90 | expect( 91 | op.variableDefinitions.variableDefinitions.first, 92 | isVariableDefinition('one', 93 | type: isListType(isType('int'), isNullable: true), 94 | defaultValue: isValue(2))); 95 | 96 | expect(op.directives, hasLength(2)); 97 | expect(op.directives[0], isDirective('foo')); 98 | expect(op.directives[1], isDirective('bar', valueOrVariable: equals(2))); 99 | 100 | expect(op.selectionSet, isNotNull); 101 | expect( 102 | op.selectionSet, 103 | isSelectionSet([ 104 | isField(fieldName: isFieldName('foo')), 105 | isField(fieldName: isFieldName('bar', alias: 'baz')) 106 | ])); 107 | }); 108 | 109 | test('exceptions', () { 110 | var throwsSyntaxError = predicate((x) { 111 | var parser = parse(x.toString())..parseOperationDefinition(); 112 | return parser.errors.isNotEmpty; 113 | }, 'fails to parse operation definition'); 114 | 115 | expect('query', throwsSyntaxError); 116 | expect('query foo()', throwsSyntaxError); 117 | }); 118 | }); 119 | } 120 | -------------------------------------------------------------------------------- /graphql_parser/test/field_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:graphql_parser/graphql_parser.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | import 'argument_test.dart'; 5 | import 'common.dart'; 6 | import 'directive_test.dart'; 7 | import 'fragment_spread_test.dart'; 8 | import 'selection_set_test.dart'; 9 | import 'value_test.dart'; 10 | 11 | main() { 12 | group('field name', () { 13 | test('plain field name', () { 14 | expect('foo', isFieldName('foo')); 15 | }); 16 | test('alias', () { 17 | expect('foo: bar', isFieldName('foo', alias: 'bar')); 18 | }); 19 | test('exceptions', () { 20 | var throwsSyntaxError = predicate((x) { 21 | var parser = parse(x.toString())..parseFieldName(); 22 | return parser.errors.isNotEmpty; 23 | }, 'fails to parse field name'); 24 | 25 | expect('foo:', throwsSyntaxError); 26 | }); 27 | }); 28 | 29 | test('arguments', () { 30 | expect('()', isArgumentList([])); 31 | expect(r'(a: 2)', isArgumentList([isArgument('a', 2)])); 32 | expect(r'(a: 2, b: $c)', 33 | isArgumentList([isArgument('a', 2), isArgument('b', 'c')])); 34 | }); 35 | 36 | group('field tests', () { 37 | test('plain field name', () { 38 | expect('foo', isField(fieldName: isFieldName('foo'))); 39 | }); 40 | 41 | test('aliased field name', () { 42 | expect('foo: bar', isField(fieldName: isFieldName('foo', alias: 'bar'))); 43 | }); 44 | 45 | test('with arguments', () { 46 | expect( 47 | r'foo (a: 2, b: $c)', 48 | isField( 49 | fieldName: isFieldName('foo'), 50 | arguments: 51 | isArgumentList([isArgument('a', 2), isArgument('b', 'c')]))); 52 | }); 53 | 54 | test('with directives', () { 55 | expect( 56 | 'foo: bar (a: 2) @bar @baz: 2 @quux (one: 1)', 57 | isField( 58 | fieldName: isFieldName('foo', alias: 'bar'), 59 | arguments: isArgumentList([isArgument('a', 2)]), 60 | directives: isDirectiveList([ 61 | isDirective('bar'), 62 | isDirective('baz', valueOrVariable: isValue(2)), 63 | isDirective('quux', argument: isArgument('one', 1)) 64 | ]))); 65 | }); 66 | 67 | test('with selection set', () { 68 | expect( 69 | 'foo: bar {baz, ...quux}', 70 | isField( 71 | fieldName: isFieldName('foo', alias: 'bar'), 72 | selectionSet: isSelectionSet([ 73 | isField(fieldName: isFieldName('baz')), 74 | isFragmentSpread('quux') 75 | ]))); 76 | }); 77 | }); 78 | } 79 | 80 | FieldContext parseField(String text) => parse(text).parseField(); 81 | 82 | FieldNameContext parseFieldName(String text) => parse(text).parseFieldName(); 83 | 84 | Matcher isField( 85 | {Matcher fieldName, 86 | Matcher arguments, 87 | Matcher directives, 88 | Matcher selectionSet}) => 89 | _IsField(fieldName, arguments, directives, selectionSet); 90 | 91 | Matcher isFieldName(String name, {String alias}) => _IsFieldName(name, alias); 92 | 93 | class _IsField extends Matcher { 94 | final Matcher fieldName, arguments, directives, selectionSet; 95 | 96 | _IsField(this.fieldName, this.arguments, this.directives, this.selectionSet); 97 | 98 | @override 99 | Description describe(Description description) { 100 | // Too lazy to make a real description... 101 | return description.add('is field'); 102 | } 103 | 104 | @override 105 | bool matches(item, Map matchState) { 106 | var field = item is FieldContext ? item : parseField(item.toString()); 107 | if (field == null) return false; 108 | if (fieldName != null && !fieldName.matches(field.fieldName, matchState)) { 109 | return false; 110 | } 111 | if (arguments != null && !arguments.matches(field.arguments, matchState)) { 112 | return false; 113 | } 114 | return true; 115 | } 116 | } 117 | 118 | class _IsFieldName extends Matcher { 119 | final String name, realName; 120 | 121 | _IsFieldName(this.name, this.realName); 122 | 123 | @override 124 | Description describe(Description description) { 125 | if (realName != null) { 126 | return description 127 | .add('is field with name "$name" and alias "$realName"'); 128 | } 129 | return description.add('is field with name "$name"'); 130 | } 131 | 132 | @override 133 | bool matches(item, Map matchState) { 134 | var fieldName = 135 | item is FieldNameContext ? item : parseFieldName(item.toString()); 136 | if (realName != null) { 137 | return fieldName.alias?.alias == name && 138 | fieldName.alias?.name == realName; 139 | } else { 140 | return fieldName.name == name; 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /graphql_parser/test/fragment_spread_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:graphql_parser/graphql_parser.dart'; 2 | import 'package:test/test.dart'; 3 | import 'common.dart'; 4 | import 'argument_test.dart'; 5 | import 'directive_test.dart'; 6 | 7 | main() { 8 | test('name only', () { 9 | expect(['...foo', '... foo'], everyElement(isFragmentSpread('foo'))); 10 | }); 11 | 12 | test('with directives', () { 13 | expect( 14 | '... foo @bar @baz: 2 @quux(one: 1)', 15 | isFragmentSpread('foo', 16 | directives: isDirectiveList([ 17 | isDirective('bar'), 18 | isDirective('baz', valueOrVariable: equals(2)), 19 | isDirective('quux', argument: isArgument('one', 1)) 20 | ]))); 21 | }); 22 | } 23 | 24 | FragmentSpreadContext parseFragmentSpread(String text) => 25 | parse(text).parseFragmentSpread(); 26 | 27 | Matcher isFragmentSpread(String name, {Matcher directives}) => 28 | _IsFragmentSpread(name, directives); 29 | 30 | class _IsFragmentSpread extends Matcher { 31 | final String name; 32 | final Matcher directives; 33 | 34 | _IsFragmentSpread(this.name, this.directives); 35 | 36 | @override 37 | Description describe(Description description) { 38 | if (directives != null) { 39 | return directives.describe( 40 | description.add('is a fragment spread named "$name" that also ')); 41 | } 42 | return description.add('is a fragment spread named "$name"'); 43 | } 44 | 45 | @override 46 | bool matches(item, Map matchState) { 47 | var spread = item is FragmentSpreadContext 48 | ? item 49 | : parseFragmentSpread(item.toString()); 50 | if (spread == null) return false; 51 | if (spread.name != name) return false; 52 | if (directives != null) { 53 | return directives.matches(spread.directives, matchState); 54 | } else { 55 | return true; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /graphql_parser/test/inline_fragment_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:graphql_parser/graphql_parser.dart'; 2 | import 'package:test/test.dart'; 3 | import 'common.dart'; 4 | import 'argument_test.dart'; 5 | import 'directive_test.dart'; 6 | import 'field_test.dart'; 7 | import 'fragment_spread_test.dart'; 8 | import 'selection_set_test.dart'; 9 | 10 | main() { 11 | test('no directives', () { 12 | expect( 13 | '... on foo {bar, baz: quux}', 14 | isInlineFragment('foo', 15 | selectionSet: isSelectionSet([ 16 | isField(fieldName: isFieldName('bar')), 17 | isField(fieldName: isFieldName('baz', alias: 'quux')) 18 | ]))); 19 | }); 20 | 21 | test('with directives', () { 22 | expect( 23 | '... on foo @bar @baz: 2 @quux(one: 1) {... bar}', 24 | isInlineFragment('foo', 25 | directives: isDirectiveList([ 26 | isDirective('bar'), 27 | isDirective('baz', valueOrVariable: equals(2)), 28 | isDirective('quux', argument: isArgument('one', 1)) 29 | ]), 30 | selectionSet: isSelectionSet([isFragmentSpread('bar')]))); 31 | }); 32 | 33 | test('exceptions', () { 34 | var throwsSyntaxError = predicate((x) { 35 | var parser = parse(x.toString())..parseInlineFragment(); 36 | return parser.errors.isNotEmpty; 37 | }, 'fails to parse inline fragment'); 38 | expect('... on foo', throwsSyntaxError); 39 | expect('... on foo @bar', throwsSyntaxError); 40 | expect('... on', throwsSyntaxError); 41 | expect('...', throwsSyntaxError); 42 | }); 43 | } 44 | 45 | InlineFragmentContext parseInlineFragment(String text) => 46 | parse(text).parseInlineFragment(); 47 | 48 | Matcher isInlineFragment(String name, 49 | {Matcher directives, Matcher selectionSet}) => 50 | _IsInlineFragment(name, directives, selectionSet); 51 | 52 | class _IsInlineFragment extends Matcher { 53 | final String name; 54 | final Matcher directives, selectionSet; 55 | 56 | _IsInlineFragment(this.name, this.directives, this.selectionSet); 57 | 58 | @override 59 | Description describe(Description description) { 60 | return description.add('is an inline fragment named "$name"'); 61 | } 62 | 63 | @override 64 | bool matches(item, Map matchState) { 65 | var fragment = item is InlineFragmentContext 66 | ? item 67 | : parseInlineFragment(item.toString()); 68 | if (fragment == null) return false; 69 | if (fragment.typeCondition.typeName.name != name) return false; 70 | if (directives != null && 71 | !directives.matches(fragment.directives, matchState)) return false; 72 | if (selectionSet != null && 73 | !selectionSet.matches(fragment.selectionSet, matchState)) return false; 74 | return true; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /graphql_parser/test/issue23_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:graphql_parser/graphql_parser.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | /// This is an *extremely* verbose test, but basically it 5 | /// parses both documents, and makes sure that $memberId has 6 | /// a valid value. 7 | /// 8 | /// Resolves https://github.com/angel-dart/graphql/issues/23. 9 | void main() { 10 | void testStr(String name, String text) { 11 | test('name', () { 12 | final List tokens = scan(text); 13 | final Parser parser = Parser(tokens); 14 | 15 | if (parser.errors.isNotEmpty) { 16 | print(parser.errors.toString()); 17 | } 18 | expect(parser.errors, isEmpty); 19 | 20 | // Parse the GraphQL document using recursive descent 21 | final DocumentContext doc = parser.parseDocument(); 22 | 23 | expect(doc.definitions, isNotNull); 24 | expect(doc.definitions, isNotEmpty); 25 | 26 | // Sanity check 27 | var queryDef = doc.definitions[0] as OperationDefinitionContext; 28 | expect(queryDef.isQuery, true); 29 | expect(queryDef.name, 'customerMemberAttributes'); 30 | expect(queryDef.variableDefinitions.variableDefinitions, hasLength(1)); 31 | var memberIdDef = queryDef.variableDefinitions.variableDefinitions[0]; 32 | expect(memberIdDef.variable.name, 'memberId'); 33 | 34 | // Find $memberId 35 | var customerByCustomerId = queryDef.selectionSet.selections[0]; 36 | var customerMemberAttributesByCustomerId = 37 | customerByCustomerId.field.selectionSet.selections[0]; 38 | var nodes0 = 39 | customerMemberAttributesByCustomerId.field.selectionSet.selections[0]; 40 | var customerMemberAttributeId = nodes0.field.selectionSet.selections[0]; 41 | expect(customerMemberAttributeId.field.fieldName.name, 42 | 'customerMemberAttributeId'); 43 | var memberAttr = nodes0.field.selectionSet.selections[1]; 44 | expect(memberAttr.field.fieldName.name, 45 | 'memberAttributesByCustomerMemberAttributeId'); 46 | expect(memberAttr.field.arguments, hasLength(1)); 47 | var condition = memberAttr.field.arguments[0]; 48 | expect(condition.name, 'condition'); 49 | expect(condition.value, TypeMatcher()); 50 | var conditionValue = condition.value as ObjectValueContext; 51 | var memberId = conditionValue.fields 52 | .singleWhere((f) => f.nameToken.text == 'memberId'); 53 | expect(memberId.value, TypeMatcher()); 54 | print('Found \$memberId: Instance of $T'); 55 | }); 56 | } 57 | 58 | testStr('member id as var', memberIdAsVar); 59 | testStr('member id as constant', memberIdAsConstant); 60 | } 61 | 62 | final String memberIdAsVar = r''' 63 | query customerMemberAttributes($memberId: Int!){ 64 | customerByCustomerId(customerId: 7) { 65 | customerMemberAttributesByCustomerId { 66 | nodes { 67 | customerMemberAttributeId 68 | memberAttributesByCustomerMemberAttributeId(condition: {memberId: $memberId}) { 69 | nodes { 70 | memberAttributeId 71 | } 72 | } 73 | } 74 | } 75 | } 76 | } 77 | '''; 78 | 79 | final String memberIdAsConstant = r''' 80 | query customerMemberAttributes($memberId: Int!){ 81 | customerByCustomerId(customerId: 7) { 82 | customerMemberAttributesByCustomerId { 83 | nodes { 84 | customerMemberAttributeId 85 | memberAttributesByCustomerMemberAttributeId(condition: {memberId: 7}) { 86 | nodes { 87 | memberAttributeId 88 | } 89 | } 90 | } 91 | } 92 | } 93 | } 94 | '''; 95 | -------------------------------------------------------------------------------- /graphql_parser/test/next_name_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:test/test.dart'; 2 | import 'common.dart'; 3 | 4 | var githubSrc = r''' 5 | query searchRepos($queryString: String!, $repositoryOrder: RepositoryOrder, $first: Int!) { 6 | search(type: REPOSITORY, query: $queryString, first: $first) { 7 | ...SearchResultItemConnection 8 | } 9 | } 10 | '''; 11 | 12 | void main() { 13 | test('can parse formerly-reserved words', () { 14 | var def = parse(githubSrc).parseOperationDefinition(); 15 | expect(def.isQuery, isTrue); 16 | expect(def.variableDefinitions.variableDefinitions, hasLength(3)); 17 | 18 | var searchField = def.selectionSet.selections[0].field; 19 | expect(searchField.fieldName.name, 'search'); 20 | 21 | var argNames = searchField.arguments.map((a) => a.name).toList(); 22 | expect(argNames, ['type', 'query', 'first']); 23 | }); 24 | } 25 | -------------------------------------------------------------------------------- /graphql_parser/test/selection_set_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:graphql_parser/graphql_parser.dart'; 2 | import 'package:test/test.dart'; 3 | import 'common.dart'; 4 | import 'field_test.dart'; 5 | import 'fragment_spread_test.dart'; 6 | import 'inline_fragment_test.dart'; 7 | 8 | main() { 9 | test('empty', () { 10 | expect('{}', isSelectionSet([])); 11 | }); 12 | 13 | test('with commas', () { 14 | expect( 15 | '{foo, bar: baz}', 16 | isSelectionSet([ 17 | isField(fieldName: isFieldName('foo')), 18 | isField(fieldName: isFieldName('bar', alias: 'baz')) 19 | ])); 20 | }); 21 | 22 | test('no commas', () { 23 | expect( 24 | ''' 25 | { 26 | foo 27 | bar: baz ...quux 28 | ... on foo {bar, baz} 29 | }''' 30 | .split('\n') 31 | .map((s) => s.trim()) 32 | .join(' '), 33 | isSelectionSet([ 34 | isField(fieldName: isFieldName('foo')), 35 | isField(fieldName: isFieldName('bar', alias: 'baz')), 36 | isFragmentSpread('quux'), 37 | isInlineFragment('foo', 38 | selectionSet: isSelectionSet([ 39 | isField(fieldName: isFieldName('bar')), 40 | isField(fieldName: isFieldName('baz')), 41 | ])) 42 | ])); 43 | }); 44 | 45 | test('exceptions', () { 46 | var throwsSyntaxError = predicate((x) { 47 | var parser = parse(x.toString())..parseSelectionSet(); 48 | return parser.errors.isNotEmpty; 49 | }, 'fails to parse selection set'); 50 | 51 | expect('{foo,bar,baz', throwsSyntaxError); 52 | }); 53 | } 54 | 55 | SelectionSetContext parseSelectionSet(String text) => 56 | parse(text).parseSelectionSet(); 57 | 58 | Matcher isSelectionSet(List selections) => _IsSelectionSet(selections); 59 | 60 | class _IsSelectionSet extends Matcher { 61 | final List selections; 62 | 63 | _IsSelectionSet(this.selections); 64 | 65 | @override 66 | Description describe(Description description) { 67 | return description 68 | .add('is selection set with ${selections.length} selection(s)'); 69 | } 70 | 71 | @override 72 | bool matches(item, Map matchState) { 73 | var set = 74 | item is SelectionSetContext ? item : parseSelectionSet(item.toString()); 75 | 76 | // if (set != null) { 77 | // print('Item: $set has ${set.selections.length} selection(s):'); 78 | // for (var s in set.selections) { 79 | // print(' * $s (${s.span.text})'); 80 | // } 81 | // } 82 | 83 | if (set == null) return false; 84 | if (set.selections.length != selections.length) return false; 85 | 86 | for (int i = 0; i < set.selections.length; i++) { 87 | var sel = set.selections[i]; 88 | if (!selections[i].matches( 89 | sel.field ?? sel.fragmentSpread ?? sel.inlineFragment, matchState)) { 90 | return false; 91 | } 92 | } 93 | 94 | return true; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /graphql_parser/test/type_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:graphql_parser/graphql_parser.dart'; 2 | import 'package:test/test.dart'; 3 | import 'common.dart'; 4 | 5 | main() { 6 | test('nullable', () { 7 | expect('foo', isType('foo', isNullable: true)); 8 | }); 9 | 10 | test('non-nullable', () { 11 | expect('foo!', isType('foo', isNullable: false)); 12 | }); 13 | 14 | group('list type', () { 15 | group('nullable list type', () { 16 | test('with nullable', () { 17 | expect('[foo]', isListType(isType('foo', isNullable: true))); 18 | }); 19 | 20 | test('with non-nullable', () { 21 | expect('[foo!]', isListType(isType('foo', isNullable: false))); 22 | }); 23 | }); 24 | 25 | group('non-nullable list type', () { 26 | test('with nullable', () { 27 | expect('[foo]!', 28 | isListType(isType('foo', isNullable: true), isNullable: false)); 29 | }); 30 | 31 | test('with non-nullable', () { 32 | expect('[foo!]!', 33 | isListType(isType('foo', isNullable: false), isNullable: false)); 34 | }); 35 | }); 36 | 37 | test('exceptions', () { 38 | var throwsSyntaxError = predicate((x) { 39 | var parser = parse(x.toString())..parseType(); 40 | return parser.errors.isNotEmpty; 41 | }, 'fails to parse type'); 42 | 43 | expect('[foo', throwsSyntaxError); 44 | expect('[', throwsSyntaxError); 45 | }); 46 | }); 47 | } 48 | 49 | TypeContext parseType(String text) => parse(text).parseType(); 50 | 51 | Matcher isListType(Matcher innerType, {bool isNullable}) => 52 | _IsListType(innerType, isNullable: isNullable != false); 53 | 54 | Matcher isType(String name, {bool isNullable}) => 55 | _IsType(name, nonNull: isNullable != true); 56 | 57 | class _IsListType extends Matcher { 58 | final Matcher innerType; 59 | final bool isNullable; 60 | 61 | _IsListType(this.innerType, {this.isNullable}); 62 | 63 | @override 64 | Description describe(Description description) { 65 | var tok = isNullable != false ? 'nullable' : 'non-nullable'; 66 | var desc = description.add('is $tok list type with an inner type that '); 67 | return innerType.describe(desc); 68 | } 69 | 70 | @override 71 | bool matches(item, Map matchState) { 72 | var type = item is TypeContext ? item : parseType(item.toString()); 73 | if (type.listType == null) return false; 74 | if (type.isNullable != (isNullable != false)) return false; 75 | return innerType.matches(type.listType.innerType, matchState); 76 | } 77 | } 78 | 79 | class _IsType extends Matcher { 80 | final String name; 81 | final bool nonNull; 82 | 83 | _IsType(this.name, {this.nonNull}); 84 | 85 | @override 86 | Description describe(Description description) { 87 | if (nonNull == true) { 88 | return description.add('is non-null type named "$name"'); 89 | } else { 90 | return description.add('is nullable type named "$name"'); 91 | } 92 | } 93 | 94 | @override 95 | bool matches(item, Map matchState) { 96 | var type = item is TypeContext ? item : parseType(item.toString()); 97 | if (type.typeName == null) return false; 98 | var result = type.typeName.name == name; 99 | return result && type.isNullable == !(nonNull == true); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /graphql_parser/test/value_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math' as math; 2 | 3 | import 'package:graphql_parser/graphql_parser.dart'; 4 | import 'package:test/test.dart'; 5 | 6 | import 'common.dart'; 7 | 8 | main() { 9 | test('boolean', () { 10 | expect('true', isValue(true)); 11 | expect('false', isValue(false)); 12 | }); 13 | 14 | test('number', () { 15 | expect('1', isValue(1)); 16 | expect('1.0', isValue(1.0)); 17 | expect('-1', isValue(-1)); 18 | expect('-1.0', isValue(-1.0)); 19 | expect('6.26e-34', isValue(6.26 * math.pow(10, -34))); 20 | expect('-6.26e-34', isValue(-6.26 * math.pow(10, -34))); 21 | expect('-6.26e34', isValue(-6.26 * math.pow(10, 34))); 22 | }); 23 | 24 | test('array', () { 25 | expect('[]', isValue([])); 26 | expect('[1,2]', isValue([1, 2])); 27 | expect('[1,2, 3]', isValue([1, 2, 3])); 28 | expect('["a"]', isValue(['a'])); 29 | }); 30 | 31 | test('string', () { 32 | expect('""', isValue('')); 33 | expect('"a"', isValue('a')); 34 | expect('"abc"', isValue('abc')); 35 | expect('"\\""', isValue('"')); 36 | expect('"\\b"', isValue('\b')); 37 | expect('"\\f"', isValue('\f')); 38 | expect('"\\n"', isValue('\n')); 39 | expect('"\\r"', isValue('\r')); 40 | expect('"\\t"', isValue('\t')); 41 | expect('"\\u0123"', isValue('\u0123')); 42 | expect('"\\u0123\\u4567"', isValue('\u0123\u4567')); 43 | }); 44 | 45 | test('block string', () { 46 | expect('""""""', isValue('')); 47 | expect('"""abc"""', isValue('abc')); 48 | expect('"""\n\n\nabc\n\n\n"""', isValue('abc')); 49 | }); 50 | 51 | test('object', () { 52 | expect('{}', isValue({})); 53 | expect('{a: 2}', isValue({'a': 2})); 54 | expect('{a: 2, b: "c"}', isValue({'a': 2, 'b': 'c'})); 55 | }); 56 | 57 | test('null', () { 58 | expect('null', isValue(null)); 59 | }); 60 | 61 | test('enum', () { 62 | expect('FOO_BAR', isValue('FOO_BAR')); 63 | }); 64 | 65 | test('exceptions', () { 66 | var throwsSyntaxError = predicate((x) { 67 | var parser = parse(x.toString())..parseInputValue(); 68 | return parser.errors.isNotEmpty; 69 | }, 'fails to parse value'); 70 | 71 | expect('[1', throwsSyntaxError); 72 | }); 73 | } 74 | 75 | InputValueContext parseValue(String text) => parse(text).parseInputValue(); 76 | 77 | Matcher isValue(value) => _IsValue(value); 78 | 79 | class _IsValue extends Matcher { 80 | final value; 81 | 82 | _IsValue(this.value); 83 | 84 | @override 85 | Description describe(Description description) => 86 | description.add('equals $value when parsed as a GraphQL value'); 87 | 88 | @override 89 | bool matches(item, Map matchState) { 90 | var v = item is InputValueContext ? item : parseValue(item.toString()); 91 | return equals(value).matches(v.computeValue({}), matchState); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /graphql_parser/test/variable_definition_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:graphql_parser/graphql_parser.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | import 'common.dart'; 5 | import 'type_test.dart'; 6 | import 'value_test.dart'; 7 | 8 | main() { 9 | test('no default value', () { 10 | expect(r'$foo: bar', 11 | isVariableDefinition('foo', type: isType('bar', isNullable: true))); 12 | }); 13 | 14 | test('default value', () { 15 | expect( 16 | r'$foo: int! = 2', 17 | isVariableDefinition('foo', 18 | type: isType('int', isNullable: false), defaultValue: isValue(2))); 19 | }); 20 | 21 | test('exceptions', () { 22 | var throwsSyntaxError = predicate((x) { 23 | var parser = parse(x.toString())..parseVariableDefinition(); 24 | return parser.errors.isNotEmpty; 25 | }, 'fails to parse variable definition'); 26 | 27 | var throwsSyntaxErrorOnDefinitions = predicate((x) { 28 | var parser = parse(x.toString())..parseVariableDefinitions(); 29 | return parser.errors.isNotEmpty; 30 | }, 'fails to parse variable definitions'); 31 | 32 | expect(r'$foo', throwsSyntaxError); 33 | expect(r'$foo:', throwsSyntaxError); 34 | expect(r'$foo: int =', throwsSyntaxError); 35 | 36 | expect(r'($foo: int = 2', throwsSyntaxErrorOnDefinitions); 37 | }); 38 | } 39 | 40 | VariableDefinitionContext parseVariableDefinition(String text) => 41 | parse(text).parseVariableDefinition(); 42 | 43 | Matcher isVariableDefinition(String name, 44 | {Matcher type, Matcher defaultValue}) => 45 | _IsVariableDefinition(name, type, defaultValue); 46 | 47 | class _IsVariableDefinition extends Matcher { 48 | final String name; 49 | final Matcher type, defaultValue; 50 | 51 | _IsVariableDefinition(this.name, this.type, this.defaultValue); 52 | 53 | @override 54 | Description describe(Description description) { 55 | var desc = description.add('is variable definition with name "$name"'); 56 | 57 | if (type != null) { 58 | desc = type.describe(desc.add(' with type that ')); 59 | } 60 | 61 | if (defaultValue != null) { 62 | desc = type.describe(desc.add(' with default value that ')); 63 | } 64 | 65 | return desc; 66 | } 67 | 68 | @override 69 | bool matches(item, Map matchState) { 70 | var def = item is VariableDefinitionContext 71 | ? item 72 | : parseVariableDefinition(item.toString()); 73 | if (def == null) return false; 74 | if (def.variable.name != name) return false; 75 | bool result = true; 76 | 77 | if (type != null) { 78 | result == result && type.matches(def.type, matchState); 79 | } 80 | 81 | if (defaultValue != null) { 82 | result = 83 | result && defaultValue.matches(def.defaultValue.value, matchState); 84 | } 85 | 86 | return result; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /graphql_parser/test/variable_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:test/test.dart'; 2 | 3 | import 'common.dart'; 4 | 5 | main() { 6 | test('variables', () { 7 | expect(r'$a', isVariable('a')); 8 | expect(r'$abc', isVariable('abc')); 9 | expect(r'$abc123', isVariable('abc123')); 10 | expect(r'$_', isVariable('_')); 11 | expect(r'$___', isVariable('___')); 12 | expect(r'$_123', isVariable('_123')); 13 | }); 14 | 15 | test('exceptions', () { 16 | var throwsSyntaxError = predicate((x) { 17 | var parser = parse(x.toString())..parseVariable(); 18 | return parser.errors.isNotEmpty; 19 | }, 'fails to parse variable'); 20 | 21 | expect(r'$', throwsSyntaxError); 22 | }); 23 | } 24 | 25 | Matcher isVariable(String name) => _IsVariable(name); 26 | 27 | class _IsVariable extends Matcher { 28 | final String name; 29 | 30 | _IsVariable(this.name); 31 | 32 | @override 33 | Description describe(Description description) { 34 | return description.add('parses as a variable named "$name"'); 35 | } 36 | 37 | @override 38 | bool matches(item, Map matchState) { 39 | var p = parse(item.toString()); 40 | var v = p.parseVariable(); 41 | return equals(name).matches(v?.name, matchState); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /graphql_schema/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://www.dartlang.org/tools/private-files.html 2 | 3 | # Files and directories created by pub 4 | .buildlog 5 | .packages 6 | .project 7 | .pub/ 8 | .scripts-bin/ 9 | build/ 10 | **/packages/ 11 | 12 | # Files created by dart2js 13 | # (Most Dart developers will use pub build to compile Dart, use/modify these 14 | # rules if you intend to use dart2js directly 15 | # Convention is to use extension '.dart.js' for Dart compiled to Javascript to 16 | # differentiate from explicit Javascript files) 17 | *.dart.js 18 | *.part.js 19 | *.js.deps 20 | *.js.map 21 | *.info.json 22 | 23 | # Directory created by dartdoc 24 | doc/api/ 25 | 26 | # Don't commit pubspec lock file 27 | # (Library packages only! Remove pattern if developing an application package) 28 | pubspec.lock 29 | ### Dart template 30 | # See https://www.dartlang.org/tools/private-files.html 31 | 32 | # Files and directories created by pub 33 | 34 | # SDK 1.20 and later (no longer creates packages directories) 35 | 36 | # Older SDK versions 37 | # (Include if the minimum SDK version specified in pubsepc.yaml is earlier than 1.20) 38 | 39 | 40 | # Files created by dart2js 41 | # (Most Dart developers will use pub build to compile Dart, use/modify these 42 | # rules if you intend to use dart2js directly 43 | # Convention is to use extension '.dart.js' for Dart compiled to Javascript to 44 | # differentiate from explicit Javascript files) 45 | 46 | # Directory created by dartdoc 47 | 48 | # Don't commit pubspec lock file 49 | # (Library packages only! Remove pattern if developing an application package) 50 | ### JetBrains template 51 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 52 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 53 | 54 | # User-specific stuff: 55 | ../.idea/workspace.xml 56 | .idea/**/tasks.xml 57 | .idea/dictionaries 58 | 59 | # Sensitive or high-churn files: 60 | .idea/**/dataSources/ 61 | .idea/**/dataSources.ids 62 | .idea/**/dataSources.xml 63 | .idea/**/dataSources.local.xml 64 | .idea/**/sqlDataSources.xml 65 | .idea/**/dynamic.xml 66 | .idea/**/uiDesigner.xml 67 | 68 | # Gradle: 69 | .idea/**/gradle.xml 70 | .idea/**/libraries 71 | 72 | # Mongo Explorer plugin: 73 | .idea/**/mongoSettings.xml 74 | 75 | ## File-based project format: 76 | *.iws 77 | 78 | ## Plugin-specific files: 79 | 80 | # IntelliJ 81 | /out/ 82 | 83 | # mpeltonen/sbt-idea plugin 84 | .idea_modules/ 85 | 86 | # JIRA plugin 87 | atlassian-ide-plugin.xml 88 | 89 | # Crashlytics plugin (for Android Studio and IntelliJ) 90 | com_crashlytics_export_strings.xml 91 | crashlytics.properties 92 | crashlytics-build.properties 93 | fabric.properties 94 | -------------------------------------------------------------------------------- /graphql_schema/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 1.0.4 2 | * Add `convert` method to types, to avoid some annoying generics bugs. 3 | 4 | # 1.0.3 5 | * `enumTypeFromStrings` now returns `GraphQLEnumType`. 6 | 7 | # 1.0.2 8 | * Added `GraphQLClass()`. 9 | * Added `typeName`. 10 | 11 | # 1.0.1 12 | * Dart 2 updates. -------------------------------------------------------------------------------- /graphql_schema/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 The Angel Framework 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | 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 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /graphql_schema/README.md: -------------------------------------------------------------------------------- 1 | # graphql_schema 2 | [![Pub](https://img.shields.io/pub/v/graphql_schema.svg)](https://pub.dartlang.org/packages/graphql_schema) 3 | [![build status](https://travis-ci.org/angel-dart/graphql.svg)](https://travis-ci.org/angel-dart/graphql) 4 | 5 | An implementation of GraphQL's type system in Dart. Supports any platform where Dart runs. 6 | The decisions made in the design of this library were done to make the experience 7 | as similar to the JavaScript reference implementation as possible, and to also 8 | correctly implement the official specification. 9 | 10 | Contains functionality to build *all* GraphQL types: 11 | * `String` 12 | * `Int` 13 | * `Float` 14 | * `Boolean` 15 | * `GraphQLObjectType` 16 | * `GraphQLUnionType` 17 | * `GraphQLEnumType` 18 | * `GraphQLInputObjectType` 19 | * `Date` - ISO-8601 Date string, deserializes to a Dart `DateTime` object 20 | 21 | Of course, for a full description of GraphQL's type system, see the official 22 | specification: 23 | http://facebook.github.io/graphql/October2016/ 24 | 25 | Mostly analogous to `graphql-js`; many names are verbatim: 26 | https://graphql.org/graphql-js/type/ 27 | 28 | # Usage 29 | It's easy to define a schema with the 30 | [helper functions](#helpers): 31 | 32 | ```dart 33 | final GraphQLSchema todoSchema = new GraphQLSchema( 34 | query: objectType('Todo', [ 35 | field('text', graphQLString.nonNullable()), 36 | field('created_at', graphQLDate) 37 | ])); 38 | ``` 39 | 40 | All GraphQL types are generic, in order to leverage Dart's strong typing support. 41 | 42 | # Serialization 43 | GraphQL types can `serialize` and `deserialize` input data. 44 | The exact implementation of this depends on the type. 45 | 46 | ```dart 47 | var iso8601String = graphQLDate.serialize(new DateTime.now()); 48 | var date = graphQLDate.deserialize(iso8601String); 49 | print(date.millisecondsSinceEpoch); 50 | ``` 51 | 52 | # Validation 53 | GraphQL types can `validate` input data. 54 | 55 | ```dart 56 | var validation = myType.validate('@root', {...}); 57 | 58 | if (validation.successful) { 59 | doSomething(validation.value); 60 | } else { 61 | print(validation.errors); 62 | } 63 | ``` 64 | 65 | # Helpers 66 | * `graphQLSchema` - Create a `GraphQLSchema` 67 | * `objectType` - Create a `GraphQLObjectType` with fields 68 | * `field` - Create a `GraphQLField` with a type/argument/resolver 69 | * `listOf` - Create a `GraphQLListType` with the provided `innerType` 70 | * `inputObjectType` - Creates a `GraphQLInputObjectType` 71 | * `inputField` - Creates a field for a `GraphQLInputObjectType` 72 | 73 | # Types 74 | All of the GraphQL scalar types are built in, as well as a `Date` type: 75 | * `graphQLString` 76 | * `graphQLId` 77 | * `graphQLBoolean` 78 | * `graphQLInt` 79 | * `graphQLFloat` 80 | * `graphQLDate` 81 | 82 | ## Non-Nullable Types 83 | You can easily make a type non-nullable by calling its `nonNullable` method. 84 | 85 | ## List Types 86 | Support for list types is also included. Use the `listType` helper for convenience. 87 | 88 | ```dart 89 | /// A non-nullable list of non-nullable integers 90 | listOf(graphQLInt.nonNullable()).nonNullable(); 91 | ``` 92 | 93 | ### Input values and parameters 94 | Take the following GraphQL query: 95 | 96 | ```graphql 97 | { 98 | anime { 99 | characters(title: "Hunter x Hunter") { 100 | name 101 | age 102 | } 103 | } 104 | } 105 | ``` 106 | 107 | And subsequently, its schema: 108 | 109 | ```graphql 110 | type AnimeQuery { 111 | characters($title: String!): [Character!] 112 | } 113 | 114 | type Character { 115 | name: String 116 | age: Int 117 | } 118 | ``` 119 | 120 | The field `characters` accepts a parameter, `title`. To reproduce this in 121 | `package:graphql_schema`, use `GraphQLFieldInput`: 122 | 123 | ```dart 124 | final GraphQLObjectType queryType = objectType('AnimeQuery', fields: [ 125 | field('characters', 126 | listOf(characterType.nonNullable()), 127 | inputs: [ 128 | new GraphQLFieldInput('title', graphQLString.nonNullable()) 129 | ] 130 | ), 131 | ]); 132 | 133 | final GraphQLObjectType characterType = objectType('Character', fields: [ 134 | field('name', graphQLString), 135 | field('age', graphQLInt), 136 | ]); 137 | ``` 138 | 139 | In the majority of cases where you use GraphQL, you will be delegate the 140 | actual fetching of data to a database object, or some asynchronous resolver 141 | function. 142 | 143 | `package:graphql_schema` includes this functionality in the `resolve` property, 144 | which is passed a context object and a `Map` of arguments. 145 | 146 | A hypothetical example of the above might be: 147 | 148 | ```dart 149 | var field = field( 150 | 'characters', 151 | graphQLString, 152 | resolve: (_, args) async { 153 | return await myDatabase.findCharacters(args['title']); 154 | }, 155 | ); 156 | ``` -------------------------------------------------------------------------------- /graphql_schema/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | analyzer: 2 | strong-mode: 3 | implicit-casts: false -------------------------------------------------------------------------------- /graphql_schema/example/example.dart: -------------------------------------------------------------------------------- 1 | import 'package:graphql_schema/graphql_schema.dart'; 2 | 3 | final GraphQLSchema todoSchema = new GraphQLSchema( 4 | queryType: objectType('Todo', fields: [ 5 | field( 6 | 'text', 7 | graphQLString.nonNullable(), 8 | resolve: resolveToNull, 9 | ), 10 | field( 11 | 'created_at', 12 | graphQLDate, 13 | resolve: resolveToNull, 14 | ), 15 | ]), 16 | ); 17 | 18 | main() { 19 | // Validation 20 | var validation = todoSchema.queryType.validate( 21 | '@root', 22 | { 23 | 'foo': 'bar', 24 | 'text': null, 25 | 'created_at': 24, 26 | }, 27 | ); 28 | 29 | if (validation.successful) { 30 | print('This is valid data!!!'); 31 | } else { 32 | print('Invalid data.'); 33 | validation.errors.forEach((s) => print(' * $s')); 34 | } 35 | 36 | // Serialization 37 | print(todoSchema.queryType.serialize({ 38 | 'text': 'Clean your room!', 39 | 'created_at': new DateTime.now().subtract(new Duration(days: 10)) 40 | })); 41 | } 42 | -------------------------------------------------------------------------------- /graphql_schema/graphql_schema.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /graphql_schema/lib/graphql_schema.dart: -------------------------------------------------------------------------------- 1 | export 'src/schema.dart'; 2 | -------------------------------------------------------------------------------- /graphql_schema/lib/src/argument.dart: -------------------------------------------------------------------------------- 1 | part of graphql_schema.src.schema; 2 | 3 | /// An input to a GraphQL field. This is analogous to a function parameter in Dart. 4 | class GraphQLFieldInput { 5 | /// The name of this field. 6 | final String name; 7 | 8 | /// The type that input values must conform to. 9 | final GraphQLType type; 10 | 11 | /// An optional default value for this field. 12 | final Value defaultValue; 13 | 14 | /// An optional description for this field. 15 | /// 16 | /// This is useful when documenting your API for consumers like GraphiQL. 17 | final String description; 18 | 19 | /// If [defaultValue] is `null`, and `null` is a valid value for this parameter, set this to `true`. 20 | final bool defaultsToNull; 21 | 22 | static bool _isInputTypeOrScalar(GraphQLType type) { 23 | if (type is GraphQLInputObjectType) { 24 | return true; 25 | } else if (type is GraphQLUnionType) { 26 | return type.possibleTypes.every(_isInputTypeOrScalar); 27 | } else if (type is GraphQLObjectType) { 28 | return false; 29 | } else if (type is GraphQLNonNullableType) { 30 | return _isInputTypeOrScalar(type.ofType); 31 | } else if (type is GraphQLListType) { 32 | return _isInputTypeOrScalar(type.ofType); 33 | } else { 34 | return true; 35 | } 36 | } 37 | 38 | GraphQLFieldInput(this.name, this.type, 39 | {this.defaultValue, this.defaultsToNull: false, this.description}) { 40 | assert(_isInputTypeOrScalar(type), 41 | 'All inputs to a GraphQL field must either be scalar types, or explicitly marked as INPUT_OBJECT. Call `GraphQLObjectType.asInputObject()` on any object types you are passing as inputs to a field.'); 42 | } 43 | 44 | @override 45 | bool operator ==(other) => 46 | other is GraphQLFieldInput && 47 | other.name == name && 48 | other.type == type && 49 | other.defaultValue == other.defaultValue && 50 | other.defaultsToNull == defaultsToNull && 51 | other.description == description; 52 | } 53 | -------------------------------------------------------------------------------- /graphql_schema/lib/src/enum.dart: -------------------------------------------------------------------------------- 1 | part of graphql_schema.src.schema; 2 | 3 | /// Shorthand for building a [GraphQLEnumType]. 4 | GraphQLEnumType enumType(String name, Map values, 5 | {String description}) { 6 | return new GraphQLEnumType( 7 | name, values.keys.map((k) => new GraphQLEnumValue(k, values[k])).toList(), 8 | description: description); 9 | } 10 | 11 | /// Shorthand for building a [GraphQLEnumType] where all the possible values 12 | /// are mapped to Dart strings. 13 | GraphQLEnumType enumTypeFromStrings(String name, List values, 14 | {String description}) { 15 | return new GraphQLEnumType( 16 | name, values.map((s) => new GraphQLEnumValue(s, s)).toList(), 17 | description: description); 18 | } 19 | 20 | /// A [GraphQLType] with only a predetermined number of possible values. 21 | /// 22 | /// Though these are serialized as strings, they carry special meaning with a type system. 23 | class GraphQLEnumType extends GraphQLScalarType 24 | with _NonNullableMixin { 25 | /// The name of this enum type. 26 | final String name; 27 | 28 | /// The defined set of possible values for this type. 29 | /// 30 | /// No other values will be accepted than the ones you define. 31 | final List> values; 32 | 33 | /// A description of this enum type, for tools like GraphiQL. 34 | final String description; 35 | 36 | GraphQLEnumType(this.name, this.values, {this.description}); 37 | 38 | @override 39 | String serialize(Value value) { 40 | if (value == null) return null; 41 | return values.firstWhere((v) => v.value == value).name; 42 | } 43 | 44 | @override 45 | Value deserialize(String serialized) { 46 | return values.firstWhere((v) => v.name == serialized).value; 47 | } 48 | 49 | @override 50 | ValidationResult validate(String key, String input) { 51 | if (!values.any((v) => v.name == input)) { 52 | if (input == null) { 53 | return new ValidationResult._failure( 54 | ['The enum "$name" does not accept null values.']); 55 | } 56 | 57 | return new ValidationResult._failure( 58 | ['"$input" is not a valid value for the enum "$name".']); 59 | } 60 | 61 | return new ValidationResult._ok(input); 62 | } 63 | 64 | @override 65 | bool operator ==(other) => 66 | other is GraphQLEnumType && 67 | other.name == name && 68 | other.description == description && 69 | const ListEquality().equals(other.values, values); 70 | 71 | @override 72 | GraphQLType coerceToInputObject() => this; 73 | } 74 | 75 | /// A known value of a [GraphQLEnumType]. 76 | /// 77 | /// In practice, you might not directly call this constructor very often. 78 | class GraphQLEnumValue { 79 | /// The name of this value. 80 | final String name; 81 | 82 | /// The Dart value associated with enum values bearing the given [name]. 83 | final Value value; 84 | 85 | /// An optional description of this value; useful for tools like GraphiQL. 86 | final String description; 87 | 88 | /// The reason, if any, that this value was deprecated, if it indeed is deprecated. 89 | final String deprecationReason; 90 | 91 | GraphQLEnumValue(this.name, this.value, 92 | {this.description, this.deprecationReason}); 93 | 94 | /// Returns `true` if this value has a [deprecationReason]. 95 | bool get isDeprecated => deprecationReason != null; 96 | 97 | @override 98 | bool operator ==(other) => 99 | other is GraphQLEnumValue && 100 | other.name == name && 101 | other.value == value && 102 | other.description == description && 103 | other.deprecationReason == deprecationReason; 104 | } 105 | -------------------------------------------------------------------------------- /graphql_schema/lib/src/field.dart: -------------------------------------------------------------------------------- 1 | part of graphql_schema.src.schema; 2 | 3 | /// Typedef for a function that resolves the value of a [GraphQLObjectField], whether asynchronously or not. 4 | typedef FutureOr GraphQLFieldResolver( 5 | Serialized serialized, Map argumentValues); 6 | 7 | /// A field on a [GraphQLObjectType]. 8 | /// 9 | /// It can have input values and additional documentation, and explicitly declares it shape 10 | /// within the schema. 11 | class GraphQLObjectField { 12 | /// The list of input values this field accepts, if any. 13 | final List inputs = []; 14 | 15 | /// The name of this field in serialized input. 16 | final String name; 17 | 18 | /// A function used to evaluate the value of this field, with respect to an arbitrary Dart value. 19 | final GraphQLFieldResolver resolve; 20 | 21 | /// The [GraphQLType] associated with values that this field's [resolve] callback returns. 22 | final GraphQLType type; 23 | 24 | /// An optional description of this field; useful for tools like GraphiQL. 25 | final String description; 26 | 27 | /// The reason that this field, if it is deprecated, was deprecated. 28 | final String deprecationReason; 29 | 30 | GraphQLObjectField(this.name, this.type, 31 | {Iterable arguments: const [], 32 | @required this.resolve, 33 | this.deprecationReason, 34 | this.description}) { 35 | assert(type != null, 'GraphQL fields must specify a `type`.'); 36 | // assert( 37 | // resolve != null, 'GraphQL fields must specify a `resolve` callback.'); 38 | this.inputs.addAll(arguments ?? []); 39 | } 40 | 41 | /// Returns `true` if this field has a [deprecationReason]. 42 | bool get isDeprecated => deprecationReason?.isNotEmpty == true; 43 | 44 | FutureOr serialize(Value value) { 45 | return type.serialize(value); 46 | } 47 | 48 | FutureOr deserialize(Serialized serialized, 49 | [Map argumentValues = const {}]) { 50 | if (resolve != null) return resolve(serialized, argumentValues); 51 | return type.deserialize(serialized); 52 | } 53 | 54 | @override 55 | bool operator ==(other) => 56 | other is GraphQLObjectField && 57 | other.name == name && 58 | other.deprecationReason == deprecationReason && 59 | other.type == type && 60 | other.resolve == resolve && 61 | const ListEquality().equals(other.inputs, inputs); 62 | } 63 | -------------------------------------------------------------------------------- /graphql_schema/lib/src/gen.dart: -------------------------------------------------------------------------------- 1 | part of graphql_schema.src.schema; 2 | 3 | /// Shorthand for generating a [GraphQLObjectType]. 4 | GraphQLObjectType objectType(String name, 5 | {String description, 6 | bool isInterface: false, 7 | Iterable fields = const [], 8 | Iterable interfaces = const []}) { 9 | var obj = new GraphQLObjectType(name, description, isInterface: isInterface) 10 | ..fields.addAll(fields ?? []); 11 | 12 | if (interfaces?.isNotEmpty == true) { 13 | for (var i in interfaces) { 14 | obj.inheritFrom(i); 15 | } 16 | } 17 | 18 | return obj; 19 | } 20 | 21 | /// Shorthand for generating a [GraphQLObjectField]. 22 | GraphQLObjectField field( 23 | String name, GraphQLType type, 24 | {Iterable> inputs: const [], 25 | GraphQLFieldResolver resolve, 26 | String deprecationReason, 27 | String description}) { 28 | return new GraphQLObjectField(name, type, 29 | arguments: inputs, 30 | resolve: resolve, 31 | description: description, 32 | deprecationReason: deprecationReason); 33 | } 34 | 35 | /// Shorthand for generating a [GraphQLInputObjectType]. 36 | GraphQLInputObjectType inputObjectType(String name, 37 | {String description, 38 | Iterable inputFields: const []}) { 39 | return new GraphQLInputObjectType(name, 40 | description: description, inputFields: inputFields); 41 | } 42 | 43 | /// Shorthand for generating a [GraphQLInputObjectField]. 44 | GraphQLInputObjectField inputField( 45 | String name, GraphQLType type, 46 | {String description, T defaultValue}) { 47 | return new GraphQLInputObjectField(name, type, 48 | description: description, defaultValue: defaultValue); 49 | } 50 | -------------------------------------------------------------------------------- /graphql_schema/lib/src/union.dart: -------------------------------------------------------------------------------- 1 | part of graphql_schema.src.schema; 2 | 3 | /// A special [GraphQLType] that indicates that an input value may be valid against one or more [possibleTypes]. 4 | /// 5 | /// All provided types must be [GraphQLObjectType]s. 6 | class GraphQLUnionType 7 | extends GraphQLType, Map> 8 | with _NonNullableMixin, Map> { 9 | /// The name of this type. 10 | final String name; 11 | 12 | /// A list of all types that conform to this union. 13 | final List possibleTypes = []; 14 | 15 | GraphQLUnionType( 16 | this.name, 17 | Iterable, Map>> 18 | possibleTypes) { 19 | assert(possibleTypes.every((t) => t is GraphQLObjectType), 20 | 'The member types of a Union type must all be Object base types; Scalar, Interface and Union types must not be member types of a Union. Similarly, wrapping types must not be member types of a Union.'); 21 | assert(possibleTypes.isNotEmpty, 22 | 'A Union type must define one or more member types.'); 23 | 24 | for (var t in possibleTypes.toSet()) { 25 | this.possibleTypes.add(t as GraphQLObjectType); 26 | } 27 | } 28 | 29 | @override 30 | String get description => possibleTypes.map((t) => t.name).join(' | '); 31 | 32 | @override 33 | GraphQLType, Map> 34 | coerceToInputObject() { 35 | return new GraphQLUnionType( 36 | '${name}Input', possibleTypes.map((t) => t.coerceToInputObject())); 37 | } 38 | 39 | @override 40 | Map serialize(Map value) { 41 | for (var type in possibleTypes) { 42 | try { 43 | if (type.validate('@root', value).successful) { 44 | return type.serialize(value); 45 | } 46 | } catch (_) {} 47 | } 48 | 49 | throw new ArgumentError(); 50 | } 51 | 52 | @override 53 | Map deserialize(Map serialized) { 54 | for (var type in possibleTypes) { 55 | try { 56 | return type.deserialize(serialized); 57 | } catch (_) {} 58 | } 59 | 60 | throw new ArgumentError(); 61 | } 62 | 63 | @override 64 | ValidationResult> validate( 65 | String key, Map input) { 66 | List errors = []; 67 | 68 | for (var type in possibleTypes) { 69 | var result = type.validate(key, input); 70 | 71 | if (result.successful) { 72 | return result; 73 | } else { 74 | errors.addAll(result.errors); 75 | } 76 | } 77 | 78 | return new ValidationResult>._failure(errors); 79 | } 80 | 81 | @override 82 | bool operator ==(other) => 83 | other is GraphQLUnionType && 84 | other.name == name && 85 | other.description == description && 86 | const ListEquality() 87 | .equals(other.possibleTypes, possibleTypes); 88 | } 89 | -------------------------------------------------------------------------------- /graphql_schema/lib/src/validation_result.dart: -------------------------------------------------------------------------------- 1 | part of graphql_schema.src.schema; 2 | 3 | /// Represents the result of asserting an input [value] against a [GraphQLType]. 4 | 5 | class ValidationResult { 6 | /// `true` if there were no errors during validation. 7 | final bool successful; 8 | 9 | /// The input value passed to whatever caller invoked validation. 10 | final Value value; 11 | 12 | /// A list of errors that caused validation to fail. 13 | final List errors; 14 | 15 | ValidationResult._(this.successful, this.value, this.errors); 16 | 17 | ValidationResult._ok(this.value) 18 | : errors = [], 19 | successful = true; 20 | 21 | ValidationResult._failure(this.errors) 22 | : value = null, 23 | successful = false; 24 | 25 | // ValidationResult _asFailure() { 26 | // return new ValidationResult._(false, value, errors); 27 | // } 28 | } 29 | -------------------------------------------------------------------------------- /graphql_schema/mono_pkg.yaml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angel-dart-archive/graphql/33e2f86ba73d559197b6270df036256104726aca/graphql_schema/mono_pkg.yaml -------------------------------------------------------------------------------- /graphql_schema/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: graphql_schema 2 | version: 1.0.4 3 | description: An implementation of GraphQL's type system in Dart. Basis of graphql_server. 4 | author: Tobe O 5 | homepage: https://github.com/angel-dart/graphql 6 | environment: 7 | sdk: ">=1.8.0 <3.0.0" 8 | dependencies: 9 | collection: ^1.0.0 10 | meta: ^1.0.0 11 | source_span: ^1.0.0 12 | dev_dependencies: 13 | test: ">=0.12.0 <2.0.0" -------------------------------------------------------------------------------- /graphql_schema/test/common.dart: -------------------------------------------------------------------------------- 1 | import 'package:graphql_schema/graphql_schema.dart'; 2 | 3 | final GraphQLObjectType pokemonType = objectType('Pokemon', fields: [ 4 | field('species', graphQLString), 5 | field('catch_date', graphQLDate) 6 | ]); 7 | 8 | final GraphQLObjectType trainerType = 9 | objectType('Trainer', fields: [field('name', graphQLString)]); 10 | 11 | final GraphQLObjectType pokemonRegionType = objectType('PokemonRegion', 12 | fields: [ 13 | field('trainer', trainerType), 14 | field('pokemon_species', listOf(pokemonType)) 15 | ]); 16 | -------------------------------------------------------------------------------- /graphql_schema/test/equality_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:graphql_schema/graphql_schema.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | /// Note: this doesn't test for scalar types, which are final, and therefore use built-in equality. 5 | void main() { 6 | group('equality', () { 7 | test('enums', () { 8 | expect(enumTypeFromStrings('A', ['B', 'C']), 9 | enumTypeFromStrings('A', ['B', 'C'])); 10 | expect(enumTypeFromStrings('A', ['B', 'C']), 11 | isNot(enumTypeFromStrings('B', ['B', 'C']))); 12 | }); 13 | 14 | test('objects', () { 15 | expect( 16 | objectType('B', fields: [ 17 | field('b', graphQLString.nonNullable()), 18 | ]), 19 | objectType('B', fields: [ 20 | field('b', graphQLString.nonNullable()), 21 | ]), 22 | ); 23 | 24 | expect( 25 | objectType('B', fields: [ 26 | field('b', graphQLString.nonNullable()), 27 | ]), 28 | isNot(objectType('BD', fields: [ 29 | field('b', graphQLString.nonNullable()), 30 | ])), 31 | ); 32 | 33 | expect( 34 | objectType('B', fields: [ 35 | field('b', graphQLString.nonNullable()), 36 | ]), 37 | isNot(objectType('B', fields: [ 38 | field('ba', graphQLString.nonNullable()), 39 | ])), 40 | ); 41 | 42 | expect( 43 | objectType('B', fields: [ 44 | field('b', graphQLString.nonNullable()), 45 | ]), 46 | isNot(objectType('B', fields: [ 47 | field('a', graphQLFloat.nonNullable()), 48 | ])), 49 | ); 50 | }); 51 | 52 | test('input type', () {}); 53 | 54 | test('union type', () { 55 | expect( 56 | new GraphQLUnionType('A', [ 57 | objectType('B', fields: [ 58 | field('b', graphQLString.nonNullable()), 59 | ]), 60 | objectType('C', fields: [ 61 | field('c', graphQLString.nonNullable()), 62 | ]), 63 | ]), 64 | new GraphQLUnionType('A', [ 65 | objectType('B', fields: [ 66 | field('b', graphQLString.nonNullable()), 67 | ]), 68 | objectType('C', fields: [ 69 | field('c', graphQLString.nonNullable()), 70 | ]), 71 | ]), 72 | ); 73 | 74 | expect( 75 | new GraphQLUnionType('A', [ 76 | objectType('B', fields: [ 77 | field('b', graphQLString.nonNullable()), 78 | ]), 79 | objectType('C', fields: [ 80 | field('c', graphQLString.nonNullable()), 81 | ]), 82 | ]), 83 | isNot(new GraphQLUnionType('AA', [ 84 | objectType('B', fields: [ 85 | field('b', graphQLString.nonNullable()), 86 | ]), 87 | objectType('C', fields: [ 88 | field('c', graphQLString.nonNullable()), 89 | ]), 90 | ])), 91 | ); 92 | 93 | expect( 94 | new GraphQLUnionType('A', [ 95 | objectType('BB', fields: [ 96 | field('b', graphQLString.nonNullable()), 97 | ]), 98 | objectType('C', fields: [ 99 | field('c', graphQLString.nonNullable()), 100 | ]), 101 | ]), 102 | isNot(new GraphQLUnionType('AA', [ 103 | objectType('BDD', fields: [ 104 | field('b', graphQLString.nonNullable()), 105 | ]), 106 | objectType('C', fields: [ 107 | field('c', graphQLString.nonNullable()), 108 | ]), 109 | ])), 110 | ); 111 | }); 112 | }); 113 | } 114 | -------------------------------------------------------------------------------- /graphql_schema/test/inheritance_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:graphql_schema/graphql_schema.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | void main() { 5 | group('interface', () { 6 | var a = objectType( 7 | 'A', 8 | isInterface: true, 9 | fields: [ 10 | field('text', graphQLString.nonNullable()), 11 | ], 12 | ); 13 | 14 | var b = objectType( 15 | 'B', 16 | isInterface: true, 17 | interfaces: [a], 18 | fields: [ 19 | field('text', graphQLString.nonNullable()), 20 | ], 21 | ); 22 | 23 | var c = objectType( 24 | 'C', 25 | isInterface: true, 26 | interfaces: [b], 27 | fields: [ 28 | field('text', graphQLString.nonNullable()), 29 | ], 30 | ); 31 | 32 | test('child implements parent', () { 33 | expect(b.isImplementationOf(a), true); 34 | expect(c.isImplementationOf(b), true); 35 | }); 36 | 37 | test('parent does not implement child', () { 38 | expect(a.isImplementationOf(b), false); 39 | }); 40 | 41 | test('child interfaces contains parent', () { 42 | expect(b.interfaces, contains(a)); 43 | expect(c.interfaces, contains(b)); 44 | }); 45 | 46 | test('parent possibleTypes contains child', () { 47 | expect(a.possibleTypes, contains(b)); 48 | expect(b.possibleTypes, contains(c)); 49 | }); 50 | 51 | test('grandchild implements grandparent', () { 52 | expect(c.isImplementationOf(a), true); 53 | }); 54 | 55 | test('grandparent does not implement grandchild', () { 56 | expect(a.isImplementationOf(c), false); 57 | }); 58 | 59 | test('grandchild interfaces contains grandparent', () { 60 | expect(c.interfaces, contains(a)); 61 | }); 62 | 63 | test('grandparent possibleTypes contains grandchild', () { 64 | expect(a.possibleTypes, contains(c)); 65 | }); 66 | }); 67 | } 68 | -------------------------------------------------------------------------------- /graphql_schema/test/serialize_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:graphql_schema/graphql_schema.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | import 'common.dart'; 5 | 6 | main() { 7 | test('int', () { 8 | expect(graphQLInt.serialize(23), 23); 9 | }); 10 | 11 | test('float', () { 12 | expect(graphQLFloat.serialize(23.0), 23.0); 13 | }); 14 | 15 | test('bool', () { 16 | expect(graphQLBoolean.serialize(true), true); 17 | }); 18 | 19 | test('string', () { 20 | expect(graphQLString.serialize('a'), 'a'); 21 | }); 22 | 23 | test('enum', () { 24 | var response = enumTypeFromStrings('Response', ['YES', 'NO']); 25 | expect(response.serialize('YES'), 'YES'); 26 | }); 27 | 28 | test('enum only serializes correct values', () { 29 | var response = enumTypeFromStrings('Response', ['YES', 'NO']); 30 | expect(() => response.serialize('MAYBE'), throwsStateError); 31 | }); 32 | 33 | test('date', () { 34 | var now = new DateTime.now(); 35 | expect(graphQLDate.serialize(now), now.toIso8601String()); 36 | }); 37 | 38 | test('list', () { 39 | expect(listOf(graphQLString).serialize(['foo', 'bar']), ['foo', 'bar']); 40 | 41 | var today = new DateTime.now(); 42 | var tomorrow = today.add(new Duration(days: 1)); 43 | expect(listOf(graphQLDate).serialize([today, tomorrow]), 44 | [today.toIso8601String(), tomorrow.toIso8601String()]); 45 | }); 46 | 47 | group('input object', () { 48 | var type = inputObjectType( 49 | 'Foo', 50 | inputFields: [ 51 | inputField('bar', graphQLString.nonNullable()), 52 | inputField('baz', graphQLFloat.nonNullable()), 53 | ], 54 | ); 55 | 56 | test('serializes valid input', () { 57 | expect( 58 | type.serialize({'bar': 'a', 'baz': 2.0}), {'bar': 'a', 'baz': 2.0}); 59 | }); 60 | }); 61 | 62 | test('object', () { 63 | var catchDate = new DateTime.now(); 64 | 65 | var pikachu = {'species': 'Pikachu', 'catch_date': catchDate}; 66 | 67 | expect(pokemonType.serialize(pikachu), 68 | {'species': 'Pikachu', 'catch_date': catchDate.toIso8601String()}); 69 | }); 70 | 71 | test('union type lets any of its types serialize', () { 72 | var typeType = enumTypeFromStrings('Type', [ 73 | 'FIRE', 74 | 'WATER', 75 | 'GRASS', 76 | ]); 77 | 78 | var pokemonType = objectType('Pokémon', fields: [ 79 | field( 80 | 'name', 81 | graphQLString.nonNullable(), 82 | ), 83 | field( 84 | 'type', 85 | typeType, 86 | ), 87 | ]); 88 | 89 | var digimonType = objectType( 90 | 'Digimon', 91 | fields: [ 92 | field('size', graphQLFloat.nonNullable()), 93 | ], 94 | ); 95 | 96 | var u = new GraphQLUnionType('Monster', [pokemonType, digimonType]); 97 | 98 | expect(u.serialize({'size': 10.0}), {'size': 10.0}); 99 | expect(u.serialize({'name': 'Charmander', 'type': 'FIRE'}), 100 | {'name': 'Charmander', 'type': 'FIRE'}); 101 | }); 102 | 103 | test('nested object', () { 104 | var pikachuDate = new DateTime.now(), 105 | charizardDate = pikachuDate.subtract(new Duration(days: 10)); 106 | 107 | var pikachu = {'species': 'Pikachu', 'catch_date': pikachuDate}; 108 | var charizard = {'species': 'Charizard', 'catch_date': charizardDate}; 109 | 110 | var trainer = {'name': 'Tobe O'}; 111 | 112 | var region = pokemonRegionType.serialize({ 113 | 'trainer': trainer, 114 | 'pokemon_species': [pikachu, charizard] 115 | }); 116 | print(region); 117 | 118 | expect(region, { 119 | 'trainer': trainer, 120 | 'pokemon_species': [ 121 | {'species': 'Pikachu', 'catch_date': pikachuDate.toIso8601String()}, 122 | {'species': 'Charizard', 'catch_date': charizardDate.toIso8601String()} 123 | ] 124 | }); 125 | 126 | expect( 127 | () => pokemonRegionType.serialize({ 128 | 'trainer': trainer, 129 | 'DIGIMON_species': [pikachu, charizard] 130 | }), 131 | throwsUnsupportedError); 132 | }); 133 | } 134 | -------------------------------------------------------------------------------- /graphql_schema/test/validation_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:graphql_schema/graphql_schema.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | void main() { 5 | var typeType = enumTypeFromStrings('Type', [ 6 | 'FIRE', 7 | 'WATER', 8 | 'GRASS', 9 | ]); 10 | 11 | var pokemonType = objectType('Pokémon', fields: [ 12 | field( 13 | 'name', 14 | graphQLString.nonNullable(), 15 | ), 16 | field( 17 | 'type', 18 | typeType, 19 | ), 20 | ]); 21 | 22 | var isValidPokemon = predicate( 23 | (x) => 24 | pokemonType.validate('@root', x as Map).successful, 25 | 'is a valid Pokémon'); 26 | 27 | var throwsATypeError = throwsA(predicate( 28 | (x) => x is TypeError || x is CastError, 'is a type or cast error')); 29 | 30 | test('object accepts valid input', () { 31 | expect({'name': 'Charmander', 'type': 'FIRE'}, isValidPokemon); 32 | }); 33 | 34 | test('mismatched scalar type', () { 35 | expect(() => pokemonType.validate('@root', {'name': 24}), throwsATypeError); 36 | }); 37 | 38 | test('empty passed for non-nullable', () { 39 | expect({}, isNot(isValidPokemon)); 40 | }); 41 | 42 | test('null passed for non-nullable', () { 43 | expect({'name': null}, isNot(isValidPokemon)); 44 | }); 45 | 46 | test('rejects extraneous fields', () { 47 | expect({'name': 'Vulpix', 'foo': 'bar'}, isNot(isValidPokemon)); 48 | }); 49 | 50 | test('enum accepts valid value', () { 51 | expect(typeType.validate('@root', 'FIRE').successful, true); 52 | }); 53 | 54 | test('enum rejects invalid value', () { 55 | expect(typeType.validate('@root', 'POISON').successful, false); 56 | }); 57 | 58 | group('union type', () { 59 | var digimonType = objectType( 60 | 'Digimon', 61 | fields: [ 62 | field('size', graphQLFloat.nonNullable()), 63 | ], 64 | ); 65 | 66 | var u = new GraphQLUnionType('Monster', [pokemonType, digimonType]); 67 | 68 | test('any of its types returns valid', () { 69 | expect(u.validate('@root', {'size': 32.0}).successful, true); 70 | expect( 71 | u.validate( 72 | '@root', {'name': 'Charmander', 'type': 'FIRE'}).successful, 73 | true); 74 | }); 75 | }); 76 | 77 | group('input object', () { 78 | var type = inputObjectType( 79 | 'Foo', 80 | inputFields: [ 81 | inputField('bar', graphQLString.nonNullable()), 82 | inputField('baz', graphQLFloat.nonNullable()), 83 | ], 84 | ); 85 | 86 | test('accept valid input', () { 87 | expect(type.validate('@root', {'bar': 'a', 'baz': 2.0}).value, 88 | {'bar': 'a', 'baz': 2.0}); 89 | }); 90 | 91 | test('error on missing non-null fields', () { 92 | expect(type.validate('@root', {'bar': 'a'}).successful, false); 93 | }); 94 | 95 | test('error on unrecognized fields', () { 96 | expect( 97 | type.validate( 98 | '@root', {'bar': 'a', 'baz': 2.0, 'franken': 'stein'}).successful, 99 | false); 100 | }); 101 | }); 102 | } 103 | -------------------------------------------------------------------------------- /graphql_server/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://www.dartlang.org/tools/private-files.html 2 | 3 | # Files and directories created by pub 4 | .buildlog 5 | .packages 6 | .project 7 | .pub/ 8 | .scripts-bin/ 9 | build/ 10 | **/packages/ 11 | 12 | # Files created by dart2js 13 | # (Most Dart developers will use pub build to compile Dart, use/modify these 14 | # rules if you intend to use dart2js directly 15 | # Convention is to use extension '.dart.js' for Dart compiled to Javascript to 16 | # differentiate from explicit Javascript files) 17 | *.dart.js 18 | *.part.js 19 | *.js.deps 20 | *.js.map 21 | *.info.json 22 | 23 | # Directory created by dartdoc 24 | doc/api/ 25 | 26 | # Don't commit pubspec lock file 27 | # (Library packages only! Remove pattern if developing an application package) 28 | pubspec.lock 29 | ### Dart template 30 | # See https://www.dartlang.org/tools/private-files.html 31 | 32 | # Files and directories created by pub 33 | 34 | # SDK 1.20 and later (no longer creates packages directories) 35 | 36 | # Older SDK versions 37 | # (Include if the minimum SDK version specified in pubsepc.yaml is earlier than 1.20) 38 | 39 | 40 | # Files created by dart2js 41 | # (Most Dart developers will use pub build to compile Dart, use/modify these 42 | # rules if you intend to use dart2js directly 43 | # Convention is to use extension '.dart.js' for Dart compiled to Javascript to 44 | # differentiate from explicit Javascript files) 45 | 46 | # Directory created by dartdoc 47 | 48 | # Don't commit pubspec lock file 49 | # (Library packages only! Remove pattern if developing an application package) 50 | ### JetBrains template 51 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 52 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 53 | 54 | # User-specific stuff: 55 | ../.idea/workspace.xml 56 | .idea/**/tasks.xml 57 | .idea/dictionaries 58 | 59 | # Sensitive or high-churn files: 60 | .idea/**/dataSources/ 61 | .idea/**/dataSources.ids 62 | .idea/**/dataSources.xml 63 | .idea/**/dataSources.local.xml 64 | .idea/**/sqlDataSources.xml 65 | .idea/**/dynamic.xml 66 | .idea/**/uiDesigner.xml 67 | 68 | # Gradle: 69 | .idea/**/gradle.xml 70 | .idea/**/libraries 71 | 72 | # Mongo Explorer plugin: 73 | .idea/**/mongoSettings.xml 74 | 75 | ## File-based project format: 76 | *.iws 77 | 78 | ## Plugin-specific files: 79 | 80 | # IntelliJ 81 | /out/ 82 | 83 | # mpeltonen/sbt-idea plugin 84 | .idea_modules/ 85 | 86 | # JIRA plugin 87 | atlassian-ide-plugin.xml 88 | 89 | # Crashlytics plugin (for Android Studio and IntelliJ) 90 | com_crashlytics_export_strings.xml 91 | crashlytics.properties 92 | crashlytics-build.properties 93 | fabric.properties 94 | -------------------------------------------------------------------------------- /graphql_server/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 1.1.0 2 | * Updates for `package:graphql_parser@1.2.0`. 3 | * Now that variables are `InputValueContext` descendants, handle them the 4 | same way as other values in `coerceArgumentValues`. TLDR - Removed 5 | now-obsolete, variable-specific logic in `coerceArgumentValues`. 6 | * Pass `argumentName`, not `fieldName`, to type validations. 7 | 8 | # 1.0.3 9 | * Make field resolution asynchronous. 10 | * Make introspection cycle-safe. 11 | * Thanks @deep-guarav and @micimize! 12 | 13 | # 1.0.2 14 | * https://github.com/angel-dart/graphql/pull/32 15 | 16 | # 1.0.1 17 | * Fix a bug where `globalVariables` were not being properly passed 18 | to field resolvers. 19 | 20 | # 1.0.0 21 | * Finish testing. 22 | * Add `package:pedantic` fixes. 23 | 24 | # 1.0.0-rc.0 25 | * Get the Apollo support working with the latest version of `subscriptions-transport-ws`. 26 | 27 | # 1.0.0-beta.4 28 | For some reason, Pub was not including `subscriptions_transport_ws.dart`. 29 | 30 | # 1.0.0-beta.3 31 | * Introspection on subscription types (if any). 32 | 33 | # 1.0.0-beta.2 34 | * Fix bug where field aliases would not be resolved. 35 | 36 | # 1.0.0-beta.1 37 | * Add (currently untested) subscription support. 38 | 39 | # 1.0.0-beta 40 | * First release. 41 | -------------------------------------------------------------------------------- /graphql_server/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 The Angel Framework 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | 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 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /graphql_server/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:pedantic/analysis_options.yaml 2 | analyzer: 3 | strong-mode: 4 | implicit-casts: false -------------------------------------------------------------------------------- /graphql_server/example/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:graphql_schema/graphql_schema.dart'; 2 | import 'package:graphql_server/graphql_server.dart'; 3 | import 'package:test/test.dart'; 4 | 5 | void main() { 6 | test('single element', () async { 7 | var todoType = objectType('todo', fields: [ 8 | field( 9 | 'text', 10 | graphQLString, 11 | resolve: (obj, args) => obj.text, 12 | ), 13 | field( 14 | 'completed', 15 | graphQLBoolean, 16 | resolve: (obj, args) => obj.completed, 17 | ), 18 | ]); 19 | 20 | var schema = graphQLSchema( 21 | queryType: objectType('api', fields: [ 22 | field( 23 | 'todos', 24 | listOf(todoType), 25 | resolve: (_, __) => [ 26 | Todo( 27 | text: 'Clean your room!', 28 | completed: false, 29 | ) 30 | ], 31 | ), 32 | ]), 33 | ); 34 | 35 | var graphql = GraphQL(schema); 36 | var result = await graphql.parseAndExecute('{ todos { text } }'); 37 | 38 | print(result); 39 | expect(result, { 40 | 'todos': [ 41 | {'text': 'Clean your room!'} 42 | ] 43 | }); 44 | }); 45 | } 46 | 47 | class Todo { 48 | final String text; 49 | final bool completed; 50 | 51 | Todo({this.text, this.completed}); 52 | } 53 | -------------------------------------------------------------------------------- /graphql_server/graphql_server.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /graphql_server/lib/src/apollo/remote_client.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:stream_channel/stream_channel.dart'; 3 | import 'transport.dart'; 4 | 5 | class RemoteClient extends StreamChannelMixin { 6 | final StreamChannel channel; 7 | final StreamChannelController _ctrl = 8 | StreamChannelController(); 9 | 10 | RemoteClient.withoutJson(this.channel) { 11 | _ctrl.local.stream 12 | .map((m) => m.toJson()) 13 | .cast() 14 | .forEach(channel.sink.add); 15 | channel.stream.listen((m) { 16 | _ctrl.local.sink.add(OperationMessage.fromJson(m)); 17 | }); 18 | } 19 | 20 | RemoteClient(StreamChannel channel) 21 | : this.withoutJson(jsonDocument.bind(channel).cast()); 22 | @override 23 | StreamSink get sink => _ctrl.foreign.sink; 24 | 25 | @override 26 | Stream get stream => _ctrl.foreign.stream; 27 | 28 | void close() { 29 | channel.sink.close(); 30 | _ctrl.local.sink.close(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /graphql_server/lib/src/apollo/transport.dart: -------------------------------------------------------------------------------- 1 | import 'package:graphql_schema/graphql_schema.dart'; 2 | 3 | /// A basic message in the Apollo WebSocket protocol. 4 | class OperationMessage { 5 | static const String gqlConnectionInit = 'connection_init', 6 | gqlConnectionAck = 'connection_ack', 7 | gqlConnectionKeepAlive = 'ka', 8 | gqlConnectionError = 'connection_error', 9 | gqlStart = 'start', 10 | gqlStop = 'stop', 11 | gqlConnectionTerminate = 'connection_terminate', 12 | gqlData = 'data', 13 | gqlError = 'error', 14 | gqlComplete = 'complete'; 15 | static const String legacyGqlConnectionInit = 'connection_init', 16 | legacyGqlConnectionAck = 'connection_ack', 17 | legacyGqlConnectionKeepAlive = 'ka', 18 | legacyGqlConnectionError = 'connection_error', 19 | legacyGqlStart = 'start', 20 | legacyGqlStop = 'stop', 21 | legacyGqlConnectionTerminate = 'connection_terminate', 22 | legacyGqlData = 'data', 23 | legacyGqlError = 'error', 24 | legacyGqlComplete = 'complete'; 25 | 26 | // static const String gqlConnectionInit = 'GQL_CONNECTION_INIT', 27 | // gqlConnectionAck = 'GQL_CONNECTION_ACK', 28 | // gqlConnectionKeepAlive = 'GQL_CONNECTION_KEEP_ALIVE', 29 | // gqlConnectionError = 'GQL_CONNECTION_ERROR', 30 | // gqlStart = 'GQL_START', 31 | // gqlStop = 'GQL_STOP', 32 | // gqlConnectionTerminate = 'GQL_CONNECTION_TERMINATE', 33 | // gqlData = 'GQL_DATA', 34 | // gqlError = 'GQL_ERROR', 35 | // gqlComplete = 'GQL_COMPLETE'; 36 | final dynamic payload; 37 | final String id; 38 | final String type; 39 | 40 | OperationMessage(this.type, {this.payload, this.id}); 41 | 42 | factory OperationMessage.fromJson(Map map) { 43 | var type = map['type']; 44 | var payload = map['payload']; 45 | var id = map['id']; 46 | 47 | if (type == null) { 48 | throw ArgumentError.notNull('type'); 49 | } else if (type is! String) { 50 | throw ArgumentError.value(type, 'type', 'must be a string'); 51 | } else if (id is num) { 52 | id = id.toString(); 53 | } else if (id != null && id is! String) { 54 | throw ArgumentError.value(id, 'id', 'must be a string or number'); 55 | } 56 | 57 | // TODO: This is technically a violation of the spec. 58 | // https://github.com/apollographql/subscriptions-transport-ws/issues/551 59 | if (map.containsKey('query') || 60 | map.containsKey('operationName') || 61 | map.containsKey('variables')) payload = Map.from(map); 62 | return OperationMessage(type as String, id: id as String, payload: payload); 63 | } 64 | 65 | Map toJson() { 66 | var out = {'type': type}; 67 | if (id != null) out['id'] = id; 68 | if (payload != null) out['payload'] = payload; 69 | return out; 70 | } 71 | } 72 | 73 | class GraphQLResult { 74 | final dynamic data; 75 | final Iterable errors; 76 | 77 | GraphQLResult(this.data, {this.errors = const []}); 78 | } 79 | -------------------------------------------------------------------------------- /graphql_server/lib/subscriptions_transport_ws.dart: -------------------------------------------------------------------------------- 1 | /// An implementation of Apollo's `subscriptions-transport-ws` in Dart. 2 | /// 3 | /// See: 4 | /// https://github.com/apollographql/subscriptions-transport-ws/blob/master/PROTOCOL.md 5 | library graphql_server.subscriptions_transport_ws; 6 | 7 | export 'src/apollo/remote_client.dart'; 8 | export 'src/apollo/server.dart'; 9 | export 'src/apollo/transport.dart'; 10 | -------------------------------------------------------------------------------- /graphql_server/mono_pkg.yaml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angel-dart-archive/graphql/33e2f86ba73d559197b6270df036256104726aca/graphql_server/mono_pkg.yaml -------------------------------------------------------------------------------- /graphql_server/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: graphql_server 2 | version: 1.1.0 3 | author: Tobe O 4 | description: Base package for implementing GraphQL servers. You might prefer `package:angel_graphql`, the fastest way to implement GraphQL backends in Dart. 5 | homepage: https://github.com/angel-dart/graphql 6 | environment: 7 | sdk: ">=2.0.0 <3.0.0" 8 | dependencies: 9 | angel_serialize: ^2.0.0 10 | collection: ^1.0.0 11 | graphql_schema: ^1.0.0 12 | graphql_parser: ^1.0.0 13 | meta: ^1.0.0 14 | recase: ^2.0.0 15 | stream_channel: ^2.0.0 16 | tuple: ^1.0.0 17 | dev_dependencies: 18 | pedantic: ^1.0.0 19 | test: ">=0.12.0 <2.0.0" 20 | dependency_overrides: 21 | graphql_parser: 22 | path: ../graphql_parser 23 | -------------------------------------------------------------------------------- /graphql_server/test/common.dart: -------------------------------------------------------------------------------- 1 | import 'package:graphql_schema/graphql_schema.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | final Matcher throwsAGraphQLException = 5 | throwsA(predicate((x) => x is GraphQLException, 'is a GraphQL exception')); 6 | -------------------------------------------------------------------------------- /graphql_server/test/mirrors_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:graphql_schema/graphql_schema.dart'; 2 | import 'package:graphql_server/mirrors.dart'; 3 | import 'package:test/test.dart'; 4 | 5 | void main() { 6 | group('convertDartType', () { 7 | group('on enum', () { 8 | // ignore: deprecated_member_use_from_same_package 9 | var type = convertDartType(RomanceLanguage); 10 | var asEnumType = type as GraphQLEnumType; 11 | 12 | test('produces enum type', () { 13 | expect(type is GraphQLEnumType, true); 14 | }); 15 | 16 | test('rejects invalid value', () { 17 | expect(asEnumType.validate('@root', 'GERMAN').successful, false); 18 | }); 19 | 20 | test('accepts valid value', () { 21 | expect(asEnumType.validate('@root', 'SPANISH').successful, true); 22 | }); 23 | 24 | test('deserializes to concrete value', () { 25 | expect(asEnumType.deserialize('ITALIAN'), RomanceLanguage.ITALIAN); 26 | }); 27 | 28 | test('serializes to concrete value', () { 29 | expect(asEnumType.serialize(RomanceLanguage.FRANCE), 'FRANCE'); 30 | }); 31 | 32 | test('can serialize null', () { 33 | expect(asEnumType.serialize(null), null); 34 | }); 35 | 36 | test('fails to serialize invalid value', () { 37 | expect(() => asEnumType.serialize(34), throwsStateError); 38 | }); 39 | 40 | test('fails to deserialize invalid value', () { 41 | expect(() => asEnumType.deserialize('JAPANESE'), throwsStateError); 42 | }); 43 | }); 44 | }); 45 | } 46 | 47 | @graphQLClass 48 | enum RomanceLanguage { 49 | SPANISH, 50 | FRANCE, 51 | ITALIAN, 52 | } 53 | -------------------------------------------------------------------------------- /graphql_server/test/query_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:graphql_schema/graphql_schema.dart'; 2 | import 'package:graphql_server/graphql_server.dart'; 3 | import 'package:test/test.dart'; 4 | 5 | void main() { 6 | test('single element', () async { 7 | var todoType = objectType('todo', fields: [ 8 | field( 9 | 'text', 10 | graphQLString, 11 | resolve: (obj, args) => obj.text, 12 | ), 13 | field( 14 | 'completed', 15 | graphQLBoolean, 16 | resolve: (obj, args) => obj.completed, 17 | ), 18 | ]); 19 | 20 | var schema = graphQLSchema( 21 | queryType: objectType('api', fields: [ 22 | field( 23 | 'todos', 24 | listOf(todoType), 25 | resolve: (_, __) => [ 26 | Todo( 27 | text: 'Clean your room!', 28 | completed: false, 29 | ) 30 | ], 31 | ), 32 | ]), 33 | ); 34 | 35 | var graphql = GraphQL(schema); 36 | var result = await graphql.parseAndExecute('{ todos { text } }'); 37 | 38 | print(result); 39 | expect(result, { 40 | 'todos': [ 41 | {'text': 'Clean your room!'} 42 | ] 43 | }); 44 | }); 45 | } 46 | 47 | class Todo { 48 | final String text; 49 | final bool completed; 50 | 51 | Todo({this.text, this.completed}); 52 | } 53 | -------------------------------------------------------------------------------- /graphql_server/test/subscription_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:graphql_schema/graphql_schema.dart'; 3 | import 'package:graphql_server/graphql_server.dart'; 4 | import 'package:test/test.dart'; 5 | 6 | void main() { 7 | var episodes = [ 8 | {'name': 'The Phantom Menace'}, 9 | {'name': 'Attack of the Clones'}, 10 | {'name': 'Attack of the Clones'} 11 | ]; 12 | var episodesAsData = episodes.map((ep) { 13 | return { 14 | 'data': {'prequels': ep} 15 | }; 16 | }); 17 | 18 | Stream> resolveEpisodes(_, __) => 19 | Stream.fromIterable(episodes) 20 | .map((ep) => {'prequels': ep, 'not_selected': 1337}); 21 | 22 | var episodeType = objectType('Episode', fields: [ 23 | field('name', graphQLString.nonNullable()), 24 | field('not_selected', graphQLInt), 25 | ]); 26 | 27 | var schema = graphQLSchema( 28 | queryType: objectType('TestQuery', fields: [ 29 | field('episodes', graphQLInt, resolve: (_, __) => episodes), 30 | ]), 31 | subscriptionType: objectType('TestSubscription', fields: [ 32 | field('prequels', episodeType, resolve: resolveEpisodes), 33 | ]), 34 | ); 35 | 36 | var graphQL = GraphQL(schema); 37 | 38 | test('subscribe with selection', () async { 39 | var stream = await graphQL.parseAndExecute(''' 40 | subscription { 41 | prequels { 42 | name 43 | } 44 | } 45 | ''') as Stream>; 46 | 47 | var asList = await stream.toList(); 48 | print(asList); 49 | expect(asList, episodesAsData); 50 | }); 51 | } 52 | -------------------------------------------------------------------------------- /img/angel_graphql.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angel-dart-archive/graphql/33e2f86ba73d559197b6270df036256104726aca/img/angel_graphql.png -------------------------------------------------------------------------------- /img/angel_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angel-dart-archive/graphql/33e2f86ba73d559197b6270df036256104726aca/img/angel_logo.png -------------------------------------------------------------------------------- /img/angel_logo.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angel-dart-archive/graphql/33e2f86ba73d559197b6270df036256104726aca/img/angel_logo.xcf -------------------------------------------------------------------------------- /travis.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | PWD=$(pwd) 3 | cd "$PWD/graphql_parser" && pub get && pub run test -j2 && cd .. 4 | cd "$PWD/graphql_schema" && pub get && pub run test -j2 && cd .. 5 | cd "$PWD/graphql_server" && pub get && pub run test -j2 && cd .. 6 | cd "$PWD/data_loader" && pub get && pub run test -j2 && cd .. -------------------------------------------------------------------------------- /video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angel-dart-archive/graphql/33e2f86ba73d559197b6270df036256104726aca/video.png --------------------------------------------------------------------------------