├── .buildscript ├── gradle.properties.gpg └── snapshot.sh ├── .gitignore ├── .gitmodules ├── .java-version ├── .travis.yml ├── LICENSE.txt ├── README.md ├── build.gradle ├── code_of_conduct.md ├── core ├── build.gradle └── src │ ├── main │ └── kotlin │ │ └── org │ │ └── kotlinq │ │ ├── api │ │ ├── Adapter.kt │ │ ├── AdapterService.kt │ │ ├── Adapters.kt │ │ ├── BindableContext.kt │ │ ├── Fragment.kt │ │ ├── FragmentContext.kt │ │ ├── GraphQlInstance.kt │ │ ├── GraphQlInstanceProvider.kt │ │ ├── GraphVisitor.kt │ │ ├── JsonParser.kt │ │ ├── Kind.kt │ │ ├── Kotlinq.kt │ │ ├── Printer.kt │ │ ├── PrintingConfiguration.kt │ │ ├── PropertyInfo.kt │ │ ├── Resolver.kt │ │ ├── ScalarAdapterService.kt │ │ ├── ScalarAdapters.kt │ │ └── services │ │ │ ├── ConfigurableDependency.kt │ │ │ ├── Configuration.kt │ │ │ ├── ServiceContainer.kt │ │ │ ├── Wrapper.kt │ │ │ └── wrappers │ │ │ ├── AdapterWrapper.kt │ │ │ ├── GraphQlInstanceProviderWrapper.kt │ │ │ └── JsonParsingWrapper.kt │ │ ├── common │ │ └── Common.kt │ │ ├── fragments │ │ └── FragmentCollector.kt │ │ ├── models │ │ ├── GraphQlInstanceImpl.kt │ │ └── GraphQlInstanceProviderImpl.kt │ │ ├── printer │ │ └── PluginPrinter.kt │ │ ├── properties │ │ ├── AdapterServiceImpl.kt │ │ ├── BuiltInTypeMapper.kt │ │ ├── DeserializingProperty.kt │ │ ├── FragmentProperty.kt │ │ ├── InstanceProperty.kt │ │ ├── ParsedProperty.kt │ │ ├── ScalarAdapterServiceImpl.kt │ │ └── ScalarAdaptersImpl.kt │ │ └── resolvers │ │ └── JsonParserImpl.kt │ └── test │ └── kotlin │ └── org │ └── kotlinq │ ├── Common.kt │ ├── MockKType.kt │ ├── PrimitiveData.kt │ ├── ScalarPropertyTests.kt │ ├── configurations │ └── ConfigurationTest.kt │ ├── entities │ ├── FragmentEquality.kt │ ├── PropertyInfoTests.kt │ ├── SimpleFragmentEquality.kt │ ├── TestDsl.kt │ ├── hashcodes │ │ └── EntityHashcodeTests.kt │ └── print │ │ └── PrinterConfigurationTests.kt │ └── graph │ └── Traversal.kt ├── docs ├── contributing.md ├── core.md ├── dsl.md ├── index.md └── license.md ├── dsl ├── build.gradle └── src │ ├── main │ └── kotlin │ │ ├── Api.kt │ │ ├── Common.kt │ │ ├── DslExtensionScope.kt │ │ ├── FragmentContextBuilder.kt │ │ ├── FragmentSelection.kt │ │ ├── FreeProperty.kt │ │ ├── GraphBuilder.kt │ │ ├── GraphComponent.kt │ │ ├── GraphQlDslObject.kt │ │ ├── ScalarSymbol.kt │ │ ├── SelectionSet.kt │ │ ├── TypeBuilder.kt │ │ └── org │ │ └── kotlinq │ │ └── dsl │ │ └── extensions │ │ ├── FreePropertyExtensionScope.kt │ │ ├── ListDeclarationExtensionScope.kt │ │ ├── NullabilityOperatorScope.kt │ │ └── StringExtensionScope.kt │ └── test │ └── kotlin │ ├── Exp.kt │ ├── FragmentContains.kt │ └── Scratch.kt ├── gradle-compiler ├── README.md ├── build.gradle └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── prestongarno │ │ │ └── kotlinq │ │ │ ├── core │ │ │ └── org │ │ │ │ └── antlr4 │ │ │ │ └── base │ │ │ │ ├── GraphQLBaseSchema.java │ │ │ │ └── GraphQLBaseSchema.tokens │ │ │ └── org │ │ │ └── antlr4 │ │ │ └── definitions │ │ │ ├── GraphQLSchema.tokens │ │ │ ├── GraphQLSchemaBaseListener.java │ │ │ ├── GraphQLSchemaLexer.java │ │ │ ├── GraphQLSchemaLexer.tokens │ │ │ ├── GraphQLSchemaListener.java │ │ │ └── GraphQLSchemaParser.java │ ├── kotlin │ │ └── com │ │ │ └── prestongarno │ │ │ └── kotlinq │ │ │ └── compiler │ │ │ ├── ArgBuilderDef.kt │ │ │ ├── Attr.kt │ │ │ ├── Builtins.kt │ │ │ ├── CompilerRunner.kt │ │ │ ├── Fields.kt │ │ │ ├── GraphQLCompiler.kt │ │ │ ├── GraphQLsLexer.kt │ │ │ ├── KotlinLangElement.kt │ │ │ ├── Schema.kt │ │ │ └── TypeDefinitions.kt │ └── resources │ │ ├── GraphQLBaseSchema.g4 │ │ ├── GraphQLBaseSchema.tokens │ │ ├── GraphQLSchema.g4 │ │ └── META-INF │ │ └── gradle-plugins │ │ └── com.prestongarno.kotlinq.properties │ └── test │ ├── kotlin │ └── com │ │ └── prestongarno │ │ └── kotlinq │ │ └── compiler │ │ ├── EnumCompileTest.kt │ │ ├── InheritanceTests.kt │ │ ├── InputTypes.kt │ │ ├── InterfaceMultiInheritance.kt │ │ ├── InterfaceToKotlin.kt │ │ ├── JavacTest.kt │ │ ├── JvmCompile.kt │ │ ├── KeywordsAsSymbols.kt │ │ ├── PrimitiveFields.kt │ │ ├── Reflect.kt │ │ ├── Test.kt │ │ ├── TypeAliasImports.kt │ │ ├── UnionToKotlin.kt │ │ └── YelpPublicAPICompile.kt │ └── resources │ ├── graphql.schema.graphqls │ ├── sample.schema.graphqls │ └── yelp.graphqls ├── gradle ├── dependencies.gradle ├── deploy.gradle ├── projectsnapshot.gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── jvm ├── build.gradle └── src │ ├── main │ └── kotlin │ │ ├── Api.kt │ │ ├── ClassFragment.kt │ │ ├── ClassFragmentResolver.kt │ │ ├── Common.kt │ │ ├── Data.kt │ │ ├── Delegates.kt │ │ ├── FragmentField.kt │ │ ├── FragmentSpread.kt │ │ ├── GraphQlDsl.kt │ │ ├── GraphQlResult.kt │ │ ├── InterfaceFragmentSpreadScope.kt │ │ ├── Reflekt.kt │ │ ├── TypedFragmentScope.kt │ │ └── annotations │ │ └── Ignore.kt │ └── test │ └── kotlin │ ├── ClassFragmentDsl.kt │ ├── Common.kt │ ├── EqualityTest.kt │ ├── JsonArrayResolve.kt │ ├── JsonDsl.kt │ ├── ResolverTest.kt │ ├── Tests.kt │ └── Types.kt ├── ktlint.xml ├── mkdocs.yml ├── schema ├── build.gradle └── src │ └── main │ └── kotlin │ └── org │ └── kotlinq │ ├── Model.kt │ ├── delegates │ ├── CollectionStubs.kt │ ├── Context.kt │ ├── GraphQlProperty.kt │ └── PredicateStub.kt │ ├── dsl │ ├── ArgBuilder.kt │ └── DslBuilder.kt │ ├── providers │ ├── DeserializingProvider.kt │ ├── GraphQlPropertyProvider.kt │ ├── Initialized.kt │ ├── ParsingProvider.kt │ └── Providers.kt │ └── static │ ├── ContextBuilder.kt │ └── Static.kt └── settings.gradle /.buildscript/gradle.properties.gpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prestongarno/kotlinq/a9e8dab66e2704aab1434f76cff7129d84494796/.buildscript/gradle.properties.gpg -------------------------------------------------------------------------------- /.buildscript/snapshot.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if [ "$BRANCH" == "master" ]; then 3 | echo "deploying snapshot build to oss.jfrog.org..." 4 | /bin/bash ./gradlew artifactoryPublish 5 | fi 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .iml 3 | *.iws 4 | *.ipr 5 | lib/ 6 | lib/* 7 | out/ 8 | out/* 9 | .idea 10 | .xml 11 | .gradle/ 12 | build/ 13 | compiler/build 14 | compiler/.gradle 15 | compiler/*.iml 16 | compiler/*.xml 17 | compiler/*.iws 18 | .DS_Store 19 | *.scratch 20 | site 21 | site/ 22 | site/* 23 | *.log 24 | .log 25 | kotlinq-test-api/src/main/kotlin 26 | libs 27 | **/libs 28 | kotlinq-core/libs 29 | 30 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "kotlinq-core/src/test/resources/test-server"] 2 | path = kotlinq-core/src/test/resources/test-server 3 | url = https://github.com/prestongarno/kotlinq-test-harness 4 | -------------------------------------------------------------------------------- /.java-version: -------------------------------------------------------------------------------- 1 | 1.8.0.144 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | 3 | jdk: 4 | - oraclejdk8 5 | 6 | script: 7 | - ./gradlew test build 8 | 9 | branches: 10 | except: 11 | - gh-pages 12 | 13 | sudo: false 14 | 15 | before_cache: 16 | - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock 17 | - rm -fr $HOME/.gradle/caches/*/plugin-resolution/ 18 | 19 | cache: 20 | directories: 21 | - $HOME/.gradle/caches/ 22 | - $HOME/.gradle/wrapper/ 23 | - $HOME/.m2 24 | 25 | 26 | notifications: 27 | - email: false 28 | 29 | env: 30 | global: 31 | secure: Cw3t4c8ffNhPoFIWxwu1+tLPsOapYFHjGhF8tTyeU6GlbhA2DyjlmL6sEzJqEQFiS3Hrn6aADl1/ohfRms8wgaArYmfeq0tojjpZml4WbLA7mndB+W62lgIqZgKhjlc0++oBOzmUB8j/NWWPvml2EAMzvwsOkcV2fXGKfGXTx1VsF5WtoTDTLGWYjg45UPB9qLlmwPCQFOQzz1CVlY3+JNIJmcb2mOu384cDFyvWel6XKUG2JaMGzUXvMx+sdo+tOPOpPC2IleAANt8asv/dxyATWXpbzR6TtML4BwEqE1ckr7GabMKVwzpgXcVBXhTmmewzmDMBRHTgKB/ywqXMsJOWoRpYQ3mEaAisQlapEARPli8Ub8u1ogqmszdIh2KBtbhEj7SJRiqwu4ZlmQJwhdT7k4MKgU5OkODkXJGkkIBiOZ2VZsQP/q18Ho2LmBY95ou/Mm0I6JdVCfL0X3Ut1rh4E/6DnXJcBhDdbE2GYbAxalO+HVl8E1eCNtL4E2snHM1RH66GWxg5/Pbu43clumclT4wNyq7FtGe4GQ8x9OD+zX6yAK30CZsD6ailmh2L/75BmoGTkBic2CspVAVjqduxhMaw5+KeYroShviP1mFXewFOkesiEnnKR9giZbH65FgvLKm1O6nt1eqYfAlMAOatQZk7+HkleoMu2NzydaM= 32 | 33 | before_install: 34 | - export BRANCH=$(if [ "$TRAVIS_PULL_REQUEST" == "false" ]; then echo $TRAVIS_BRANCH; else echo $TRAVIS_PULL_REQUEST_BRANCH; fi) 35 | - echo "TRAVIS_BRANCH=$TRAVIS_BRANCH, PR=$PR, BRANCH=$BRANCH" 36 | - echo $GMAIL_GPG | gpg --decrypt --passphrase-fd 0 .buildscript/gradle.properties.gpg >> ~/.gradle/gradle.properties 37 | #- ./kotlinq-core/src/test/resources/test-server/install.sh 38 | 39 | after_success: 40 | - BRANCH=$BRANCH ./.buildscript/snapshot.sh 41 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Preston Garno 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ***a Kotlin GraphQL client for composable, reusable GraphQL queries*** 2 | ----------------------------- 3 | 4 | [![Download](https://api.bintray.com/packages/prestongarno/kotlinq/kotlinq-core/images/download.svg)](https://bintray.com/com/prestongarno/kotlinq/kotlinq-core/_latestVersion) 5 | [![Build Status](https://travis-ci.org/prestongarno/kotlinq.svg?branch=master)](https://travis-ci.org/prestongarno/kotlinq) 6 | 7 | 8 | ## Query DSL (new with version 0.4.0) 9 | 10 | Version 0.4.0 supports [**ad-hoc, type-hinted**](https://github.com/prestongarno/kotlinq/blob/query-dsl/query-dsl/src/main/kotlin/org/kotlinq/dsl/extensions/FreePropertyExtensionScope.kt) but natively expressed queries and mutations! 11 | 12 | 13 | A powerful feature is the ability to **compose** and **reuse** graphql queries easily. 14 | 15 | 16 | 17 | ## GraphQL Star Wars example 18 | 19 | Define a "Human" type fragment: 20 | 21 | ``` 22 | fun humanDef() = fragment("Human") { 23 | "name"(string) 24 | "nicknames" listOf !string 25 | } 26 | ``` 27 | 28 | Define a "Droid" type fragment: 29 | 30 | ``` 31 | fun droidDef() = fragment("Droid") { 32 | "modelNumber"(string) 33 | "owner" on humanDef() 34 | } 35 | ``` 36 | 37 | Now, define a query for Star Wars characters: 38 | 39 | ``` 40 | fun charactersQuery(fragments: List) = query { 41 | "characters"("first" to 10)..listOf { 42 | on..fragments 43 | } 44 | } 45 | ``` 46 | 47 | Query characters from Star Wars that are humans: 48 | 49 | ``` 50 | val query = charactersQuery(listOf(humanDef()) 51 | println(query.toGraphQl(pretty = true)) 52 | ``` 53 | 54 | prints: 55 | 56 | ``` 57 | { 58 | characters(first: 100) { 59 | __typename 60 | ... on Human { 61 | name 62 | nicknames 63 | } 64 | } 65 | } 66 | ``` 67 | 68 | But if you want to query both humans and droids, to do so it's as simple as: 69 | 70 | ``` 71 | val query = charactersQuery(listOf(humanDef(), droidDef())) 72 | println(query.toGraphQl(pretty = true)) 73 | ``` 74 | 75 | 76 | which results in: 77 | 78 | ``` 79 | { 80 | characters(first: 100) { 81 | __typename 82 | ... on Human { 83 | name 84 | nicknames 85 | } 86 | ... on Droid { 87 | modelNumber 88 | maker { 89 | name 90 | nicknames 91 | } 92 | } 93 | } 94 | } 95 | ``` 96 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Preston Garno 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | buildscript { 19 | 20 | apply from: rootProject.file("gradle/dependencies.gradle") 21 | 22 | ext.sourceCompatibility = 1.8 23 | ext.projectDescription = "a kotlin client for graphql" 24 | ext.gitBaseUrl = GIT_BASE_URL + '/kotlinq' 25 | ext.projLicenseUrl = "$gitBaseUrl/LICENSE.txt" 26 | 27 | repositories { 28 | jcenter() 29 | mavenCentral() 30 | } 31 | 32 | dependencies { 33 | classpath dep.kotlinGradlePlugin 34 | classpath dep.bintrayGradlePlugin 35 | classpath dep.jfrogLatestRelease 36 | } 37 | } 38 | 39 | 40 | subprojects { 41 | 42 | group = 'com.prestongarno.kotlinq' 43 | version = rootProject.findProperty("release") == "true" ? "0.4.2" : "0.4.2-SNAPSHOT" 44 | 45 | apply plugin: 'java' 46 | apply plugin: 'idea' 47 | apply plugin: 'kotlin' 48 | 49 | 50 | repositories { 51 | jcenter() 52 | } 53 | 54 | dependencies { 55 | compile dep.kotlinStdLib 56 | testCompile dep.kotlinTest 57 | testCompile dep.junit 58 | testCompile dep.truth 59 | } 60 | 61 | compileKotlin { 62 | kotlinOptions.jvmTarget = "1.8" 63 | } 64 | compileTestKotlin { 65 | kotlinOptions.jvmTarget = "1.8" 66 | } 67 | 68 | sourceSets { 69 | main { 70 | java { 71 | srcDirs = ['src/main/kotlin', 72 | 'src/main/java'] 73 | } 74 | resources { 75 | srcDirs = ['src/main/resources'] 76 | } 77 | } 78 | test { 79 | java { 80 | srcDirs = ['src/test/kotlin', 81 | 'src/main/java'] 82 | } 83 | resources { 84 | srcDirs = ['src/test/resources'] 85 | } 86 | } 87 | } 88 | 89 | jar { 90 | from(compileJava.outputs) 91 | from(sourceSets.main.resources) 92 | } 93 | 94 | task jarDoc(type: Jar) { 95 | from(javadoc.destinationDir) 96 | classifier 'javadoc' 97 | } 98 | 99 | task jarSource(type: Jar) { 100 | from(sourceSets.main.allSource) 101 | from(sourceSets.test.allSource) 102 | classifier 'sources' 103 | } 104 | 105 | artifacts { 106 | archives jar, jarSource, jarDoc 107 | } 108 | 109 | kotlin { 110 | experimental { 111 | coroutines "enable" 112 | } 113 | } 114 | 115 | idea.module.downloadJavadoc = true 116 | 117 | apply from: rootProject.file("gradle/deploy.gradle") 118 | 119 | } 120 | 121 | // snapshot full project jar 122 | apply from: rootProject.file("gradle/projectsnapshot.gradle") 123 | -------------------------------------------------------------------------------- /code_of_conduct.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at prestongarno@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /core/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | apply plugin: 'java' 3 | 4 | repositories { 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | compile dep.kotlinReflect 10 | compile dep.klaxon 11 | compile dep.kodein 12 | compile dep.kotlinxCoroutines 13 | } 14 | 15 | -------------------------------------------------------------------------------- /core/src/main/kotlin/org/kotlinq/api/Adapter.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.api 2 | 3 | 4 | /** 5 | * Root interface for all delegated properties. 6 | * Contains reflective type information about the GraphQL field 7 | * that this graphQlInstance property represents and arguments for the query 8 | * 9 | * @author preston 10 | */ 11 | interface Adapter { 12 | 13 | /** Resulting return type of this property */ 14 | val propertyInfo: PropertyInfo 15 | 16 | /** Returns the result of the query, or null if unresolved */ 17 | fun getValue(): Any? 18 | 19 | /** 20 | * Visitor pattern for setting values of the query on response 21 | * @param resolver the algorithm for resolving the native object graph from a GraphQL query 22 | */ 23 | fun accept(resolver: GraphVisitor) 24 | 25 | 26 | /** @return true if this property is resolved */ 27 | fun isResolved(): Boolean 28 | 29 | 30 | companion object { 31 | 32 | fun adapterHashcode(adapter: Adapter): Int = 33 | adapter.propertyInfo.hashCode() 34 | 35 | fun adapterEquals(thisAdapter: Adapter, other: Adapter?): Boolean = 36 | other is Adapter && other.propertyInfo == thisAdapter.propertyInfo 37 | } 38 | } 39 | 40 | -------------------------------------------------------------------------------- /core/src/main/kotlin/org/kotlinq/api/AdapterService.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.api 2 | 3 | 4 | /** 5 | * Provides adapters (entities) for properties with different return types (use cases) 6 | * 7 | * @author prestongarno 8 | */ 9 | interface AdapterService { 10 | 11 | val scalarAdapters: ScalarAdapterService 12 | 13 | fun deserializer(info: PropertyInfo, init: (java.io.InputStream) -> Any?): Adapter 14 | 15 | fun parser(info: PropertyInfo, init: (String) -> Any?): Adapter 16 | 17 | fun instanceProperty(info: PropertyInfo, fragment: Fragment): Adapter 18 | 19 | fun fragmentProperty(info: PropertyInfo, fragments: Set): Adapter 20 | 21 | } 22 | 23 | 24 | -------------------------------------------------------------------------------- /core/src/main/kotlin/org/kotlinq/api/Adapters.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.api 2 | 3 | 4 | /** Adapter for a property which returns a nested [Fragment] graphQlInstance */ 5 | interface InstanceAdapter : Adapter, ReifiedFragmentContext { 6 | 7 | /** 8 | * Instead of implementing [Fragment], contains a fragment so that 9 | * equals and hashcode contracts can easily optimize printing/resolving GraphQL request 10 | */ 11 | override val fragment: Fragment 12 | 13 | /** 14 | * Sets the value of this property. 15 | * Initializes a new context and calls [Resolver.resolve] on the new context 16 | */ 17 | fun setValue(result: Map, resolver: Resolver = Resolver): Boolean 18 | 19 | override fun getValue(): Fragment? 20 | 21 | override fun accept(resolver: GraphVisitor) { 22 | if (resolver.enterField(this) && resolver.notifyEnter(fragment, inline = false)) { 23 | resolver.visitContext(fragment) 24 | resolver.notifyExit(fragment) 25 | } 26 | } 27 | } 28 | 29 | interface FragmentAdapter : FragmentContext { 30 | fun setValue(typeName: String, values: Map, resolver: Resolver = Resolver): Boolean 31 | override fun accept(resolver: GraphVisitor) { 32 | resolver.visitFragmentContext(this) 33 | } 34 | } 35 | 36 | /** 37 | * Adapter for a custom-deserialized property. 38 | * Should usually be for Custom GraphQL Scalars 39 | */ 40 | interface DeserializingAdapter : Adapter { 41 | fun setValue(value: java.io.InputStream?): Boolean 42 | val initializer: (java.io.InputStream) -> Any? 43 | override fun accept(resolver: GraphVisitor) { 44 | resolver.enterField(this) 45 | } 46 | } 47 | 48 | /** 49 | * Adapter for a custom-deserialized property, but from a UTF-8 String for convenience. 50 | * Should usually be for Custom GraphQL Scalars, but is also the base interface for all primitive types 51 | */ 52 | interface ParsingAdapter : Adapter { 53 | fun setValue(value: String?): Boolean 54 | val initializer: (String) -> Any? 55 | override fun accept(resolver: GraphVisitor) { 56 | resolver.enterField(this) 57 | } 58 | } 59 | 60 | -------------------------------------------------------------------------------- /core/src/main/kotlin/org/kotlinq/api/BindableContext.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.api 2 | 3 | 4 | /** 5 | * Usually this would be done with a lambda, 6 | * but it's an important concept so an interface is defined 7 | */ 8 | interface BindableContext { 9 | 10 | fun register(adapter: Adapter): BindableContext 11 | 12 | fun build(typeName: String): Fragment 13 | 14 | 15 | companion object { 16 | 17 | internal 18 | fun newBindableContext( 19 | call: (Adapter) -> Unit, 20 | onBuild: (typeName: String) -> Fragment 21 | ): BindableContext = BindableContextImpl(call, onBuild) 22 | 23 | private 24 | class BindableContextImpl( 25 | private val call: (Adapter) -> Unit, 26 | private val onBuild: (typeName: String) -> Fragment 27 | ) : BindableContext { 28 | 29 | override fun build(typeName: String) = onBuild(typeName) 30 | 31 | override fun register(adapter: Adapter) = apply { call(adapter) } 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/org/kotlinq/api/Fragment.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.api 2 | 3 | 4 | interface Fragment { 5 | 6 | val typeName: String 7 | 8 | val graphQlInstance: GraphQlInstance 9 | 10 | fun toGraphQl(pretty: Boolean = false, idAndTypeName: Boolean = true): String = 11 | (if (pretty) PrintingConfiguration.PRETTY else PrintingConfiguration.DEFAULT).let { 12 | if (idAndTypeName) it else it.toBuilder().metaStrategy(Printer.MetaStrategy.NONE).build() 13 | }.let(::toGraphQl) 14 | 15 | fun toGraphQl(configuration: PrintingConfiguration): String = 16 | toGraphQl(Printer.fromConfiguration(configuration)) 17 | 18 | fun toGraphQl(printer: Printer): String = printer.printFragmentToString(this) 19 | 20 | fun traverse(visitor: GraphVisitor) { 21 | if (visitor.notifyEnter(this, inline = false)) { 22 | visitor.visitContext(this) 23 | visitor.notifyExit(this) 24 | } 25 | } 26 | 27 | operator fun contains(other: Fragment): Boolean { 28 | var result = false 29 | 30 | GraphVisitor.builder() 31 | .onNotifyEnter { 32 | other != it || let { 33 | result = true 34 | false // stop visiting 35 | } 36 | }.build() 37 | .let(::traverse) 38 | 39 | return result 40 | } 41 | 42 | 43 | companion object { 44 | 45 | fun createFragment(typeName: String, graphQlInstance: GraphQlInstance) = 46 | FragmentImpl(typeName, graphQlInstance) 47 | } 48 | } 49 | 50 | /** 51 | * Represents a GraphQL Fragment definition 52 | */ 53 | data class FragmentImpl( 54 | override val typeName: String, 55 | override val graphQlInstance: GraphQlInstance) : Fragment 56 | -------------------------------------------------------------------------------- /core/src/main/kotlin/org/kotlinq/api/FragmentContext.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.api 2 | 3 | 4 | interface FragmentContext : Adapter { 5 | val fragments: Map 6 | } 7 | 8 | /** 9 | * Need to separate [FragmentContext] from [ReifiedFragmentContext] because: 10 | * 11 | * ``` 12 | * queryPerson(id: String) { 13 | * node { 14 | * name, 15 | * DOB, 16 | * friends ... etc. 17 | * } 18 | * } 19 | * ``` 20 | * 21 | * is not the same as: 22 | * 23 | * ``` 24 | * query(id: String) { 25 | * ... on Person { 26 | * name, 27 | * DOB, 28 | * friends ... etc. 29 | * } 30 | * } 31 | * ``` 32 | */ 33 | interface ReifiedFragmentContext : Adapter { 34 | val fragment: Fragment 35 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/org/kotlinq/api/GraphQlInstance.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.api 2 | 3 | 4 | /** 5 | * @author prestongarno 6 | */ 7 | interface GraphQlInstance { 8 | 9 | val properties: Map 10 | 11 | fun isResolved(): Boolean 12 | 13 | fun accept(visitor: GraphVisitor) = 14 | properties.forEach { it.value.accept(visitor) } 15 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/org/kotlinq/api/GraphQlInstanceProvider.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.api 2 | 3 | import org.kotlinq.api.services.Configuration 4 | 5 | 6 | /** 7 | * Factory which creates new [GraphQlInstance] 8 | */ 9 | interface GraphQlInstanceProvider { 10 | 11 | fun newContextBuilder(): BindableContext 12 | 13 | companion object : GraphQlInstanceProvider by Configuration.instance() 14 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/org/kotlinq/api/GraphVisitor.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.api 2 | 3 | 4 | interface GraphVisitor { 5 | 6 | /** 7 | * Acts as a predicate for entering a fragment, 8 | * allowing short-circuit traversal (Hierarchical visitor pattern) 9 | */ 10 | fun notifyEnter(fragment: Fragment, inline: Boolean = false): Boolean = true 11 | 12 | fun visitContext(fragment: Fragment) { 13 | fragment.graphQlInstance.accept(this) 14 | notifyExit(fragment) 15 | } 16 | 17 | fun notifyExit(fragment: Fragment) = Unit 18 | 19 | // this is annoying, must be a way to get rid of this 20 | fun visitFragmentContext(context: FragmentContext) { 21 | context.fragments.forEach { _, fragment -> 22 | if (notifyEnter(fragment, inline = true)) { 23 | visitContext(fragment) 24 | notifyExit(fragment) 25 | } 26 | } 27 | } 28 | 29 | fun enterField(adapter: Adapter): Boolean 30 | 31 | companion object { 32 | 33 | fun builder() = Builder() 34 | } 35 | 36 | class Builder internal constructor() { 37 | 38 | private var onNotifyEnter: (Fragment) -> Boolean = { true } 39 | private var onVisitContext: (Fragment) -> Unit = { } 40 | private var onVisitField: (Adapter) -> Unit = { } 41 | private var onExitContext: (Fragment) -> Unit = { } 42 | 43 | /** 44 | * Predicate for visiting a fragment. 45 | * If the predicate evaluates to false, the fragment will not be called with [onVisitContext] 46 | * 47 | * 48 | * 49 | * TODO change signature to: 50 | * 51 | * onNotifyEnter(function: Adapter.(Fragment) -> Boolean) 52 | * 53 | * This makes it easy to know which property is a selection set 54 | */ 55 | fun onNotifyEnter(function: (Fragment) -> Boolean) = 56 | apply { onNotifyEnter = function } 57 | 58 | fun onVisitContext(function: (Fragment) -> Unit) = 59 | apply { onVisitContext = function } 60 | 61 | fun onVisitField(function: (Adapter) -> Unit) = 62 | apply { onVisitField = function } 63 | 64 | fun onExitContext(function: (Fragment) -> Unit) = 65 | apply { onExitContext = function } 66 | 67 | fun build(): GraphVisitor = DelegatingVisitor(this) 68 | 69 | 70 | private 71 | class DelegatingVisitor(builder: Builder) : GraphVisitor { 72 | 73 | private val onNotify = builder.onNotifyEnter 74 | private val onContext = builder.onVisitContext 75 | private val onField = builder.onVisitField 76 | private val onExit = builder.onExitContext 77 | 78 | override fun visitContext(fragment: Fragment) { 79 | if (onNotify(fragment)) { 80 | onContext(fragment) 81 | super.visitContext(fragment) 82 | } 83 | } 84 | 85 | override fun enterField(adapter: Adapter): Boolean { 86 | onField(adapter) 87 | return true 88 | } 89 | 90 | override fun notifyExit(fragment: Fragment) { onExit(fragment) } 91 | } 92 | } 93 | } 94 | 95 | 96 | -------------------------------------------------------------------------------- /core/src/main/kotlin/org/kotlinq/api/JsonParser.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.api 2 | 3 | import org.kotlinq.api.services.Configuration 4 | 5 | 6 | /** 7 | * Provides low-level parsing functionality for resolving queries 8 | * 9 | * @author prestongarno 10 | */ 11 | interface JsonParser { 12 | 13 | fun parseToObject(string: String, rootObjectName: String = "data"): Map 14 | 15 | fun parseToArray(string: String): Iterable 16 | 17 | companion object : JsonParser by Configuration.instance() 18 | } 19 | -------------------------------------------------------------------------------- /core/src/main/kotlin/org/kotlinq/api/Kotlinq.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.api 2 | 3 | import org.kotlinq.api.services.Configuration 4 | 5 | 6 | /** 7 | * God-object exposed to consumers and specifically kotlinq-schema 8 | * for creating kotlin platform delegate properties 9 | */ 10 | interface Kotlinq { 11 | 12 | val adapterService: AdapterService 13 | 14 | fun newContextBuilder(): BindableContext 15 | 16 | 17 | 18 | companion object : Kotlinq { 19 | 20 | override 21 | val adapterService: AdapterService 22 | get() = Configuration.instance() 23 | 24 | override fun newContextBuilder() = 25 | GraphQlInstanceProvider.newContextBuilder() 26 | 27 | } 28 | } 29 | 30 | -------------------------------------------------------------------------------- /core/src/main/kotlin/org/kotlinq/api/PrintingConfiguration.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.api 2 | 3 | 4 | /** 5 | * Added to support different requirements by endpoints 6 | * 7 | * Options supported by both: 8 | * * Quotation character 9 | * * Escape quotation marks 10 | * Pretty Print options: 11 | * * Indent 12 | */ 13 | class PrintingConfiguration private constructor(builder: Builder) { 14 | 15 | val pretty: Boolean = builder.pretty 16 | val indent: String = builder.indent 17 | val quotationCharacter = builder.quotationCharacter 18 | val argumentSeparator = builder.argumentSeparator 19 | val commaSeparator = builder.commaSeparator 20 | val spreadOnFragmentOperator = builder.spreadOnFragmentOperator 21 | val lcurlyChar = builder.lcurlyChar 22 | val rcurlyChar = builder.rcurlyChar 23 | val metaStrategy = builder.metaStrategy 24 | 25 | fun toBuilder() = Builder() 26 | .pretty(pretty) 27 | .indent(indent) 28 | .quotationCharacter(quotationCharacter) 29 | .argumentSeparator(argumentSeparator) 30 | .commaSeparator(commaSeparator) 31 | .spreadOnFragmentOperator(spreadOnFragmentOperator) 32 | .lcurlyChar(lcurlyChar) 33 | .rcurlyChar(rcurlyChar) 34 | .metaStrategy(metaStrategy) 35 | 36 | /** 37 | * Builder for a [PrintingConfiguration]. 38 | */ 39 | class Builder internal constructor() { 40 | 41 | 42 | // TODO dedup 43 | internal var metaStrategy: Printer.MetaStrategy = Printer.MetaStrategy.STANDARD 44 | internal var pretty: Boolean = false 45 | internal var indent: String = " " 46 | internal var quotationCharacter: String = "\"" 47 | internal var argumentSeparator: String = ": " 48 | internal var spreadOnFragmentOperator: String = "... on " 49 | internal var commaSeparator: String = "," 50 | internal var lcurlyChar: Char = '{' 51 | internal var rcurlyChar: Char = '}' 52 | 53 | 54 | fun metaStrategy(it: Printer.MetaStrategy) = apply { metaStrategy = it } 55 | fun quotationCharacter(it: String) = apply { quotationCharacter = it } 56 | fun pretty(it: Boolean = false) = apply { pretty = it } 57 | fun argumentSeparator(it: String) = apply { argumentSeparator = it } 58 | fun spreadOnFragmentOperator(it: String) = apply { spreadOnFragmentOperator = it } 59 | fun commaSeparator(it: String) = apply { commaSeparator = it } 60 | fun indent(it: String) = apply { indent = it } 61 | fun lcurlyChar(it: Char) = apply { lcurlyChar = it } 62 | fun rcurlyChar(it: Char) = apply { rcurlyChar = it } 63 | 64 | 65 | fun build() = PrintingConfiguration(this) 66 | 67 | } 68 | 69 | companion object { 70 | 71 | 72 | fun builder() = PrintingConfiguration.Builder() 73 | 74 | /** 75 | * Double quotations (escaped), includes __typename and ID on all objects 76 | */ 77 | val DEFAULT: PrintingConfiguration = builder() 78 | .pretty(false) 79 | .commaSeparator(",") 80 | .spreadOnFragmentOperator("...on ") 81 | .build() 82 | 83 | val PRETTY = builder() 84 | .pretty(true) 85 | .commaSeparator("") 86 | .spreadOnFragmentOperator("... on ") 87 | .build() 88 | } 89 | } 90 | 91 | -------------------------------------------------------------------------------- /core/src/main/kotlin/org/kotlinq/api/PropertyInfo.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.api 2 | 3 | data 4 | class PropertyInfo( 5 | val graphQlName: String, 6 | val kind: Kind, 7 | val arguments: Map = emptyMap()) { 9 | 10 | val graphQlType: String 11 | get() = kind.classifier 12 | 13 | val isNullable: Boolean get() = 14 | kind is Kind.NullableKind 15 | 16 | 17 | 18 | class Builder internal constructor(private var name: String) { 19 | private var arguments: Map = emptyMap() 20 | private var kind: Kind = Kind.Scalar._String 21 | 22 | fun named(it: String) = apply { this.name = it } 23 | fun arguments(it: Map) = apply { arguments = it } 24 | fun typeKind(it: Kind) = apply { kind = it } 25 | 26 | fun build() = PropertyInfo(name, kind, arguments) 27 | } 28 | 29 | 30 | companion object { 31 | 32 | fun propertyNamed(it: String) = Builder(it) 33 | } 34 | } 35 | 36 | -------------------------------------------------------------------------------- /core/src/main/kotlin/org/kotlinq/api/Resolver.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.api 2 | 3 | import org.kotlinq.api.services.Configuration 4 | 5 | 6 | /** 7 | * Encapsulation of visitor-pattern algorithm for resolving GraphQL queries 8 | * 9 | * Currently no limit on nesting depth or size of response 10 | */ 11 | interface Resolver : GraphVisitor { 12 | 13 | fun resolve(value: Map, target: Fragment): Boolean 14 | 15 | /** 16 | * Equivalent of: 17 | * 18 | * ``` 19 | * val parser: JsonParser = ... 20 | * val map = parser.parseToObject("...") 21 | * require(resolver.resolve(map, fragment)) 22 | * ``` 23 | */ 24 | fun resolve( 25 | value: String, 26 | target: Fragment, 27 | parser: JsonParser = JsonParser.Companion): Boolean = resolve(parser.parseToObject(value), target) 28 | 29 | companion object : Resolver by Configuration.instance() 30 | } 31 | -------------------------------------------------------------------------------- /core/src/main/kotlin/org/kotlinq/api/ScalarAdapterService.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.api 2 | 3 | 4 | /** 5 | * Factory methods for primitive platform delegate delegates. 6 | * 7 | * Added to prevent boxing every value on property access 8 | */ 9 | interface ScalarAdapterService { 10 | 11 | val mappers: TypeMappers 12 | 13 | /** 14 | * TODO eventually add specific types for primitives 15 | */ 16 | fun newAdapter(info: PropertyInfo): ParsingAdapter 17 | 18 | interface TypeMappers { 19 | val booleanMapper: (String) -> Boolean 20 | val floatMapper: (String) -> Float 21 | val intMapper: (String) -> Int 22 | val stringMapper: (String) -> String 23 | } 24 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/org/kotlinq/api/ScalarAdapters.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.api 2 | 3 | 4 | interface IntAdapter : ParsingAdapter { 5 | override fun getValue(): Int 6 | } 7 | 8 | interface FloatAdapter : ParsingAdapter { 9 | override fun getValue(): Float 10 | } 11 | 12 | interface StringAdapter : ParsingAdapter { 13 | override fun getValue(): String 14 | } 15 | 16 | interface BooleanAdapter : ParsingAdapter { 17 | override fun getValue(): Boolean 18 | } 19 | 20 | -------------------------------------------------------------------------------- /core/src/main/kotlin/org/kotlinq/api/services/ConfigurableDependency.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.api.services 2 | 3 | /** 4 | * Internal interface for re-configuring services (these dependencies are stateless, 5 | * so synchronizing on [ConfigurableDependency.use] will be totally fine) 6 | */ 7 | internal interface ConfigurableDependency { 8 | 9 | fun instance(): T 10 | 11 | fun use(instance: T) 12 | 13 | fun useDefault() 14 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/org/kotlinq/api/services/Configuration.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.api.services 2 | 3 | import com.github.salomonbrys.kodein.Kodein 4 | import com.github.salomonbrys.kodein.instance 5 | import org.kotlinq.api.AdapterService 6 | import org.kotlinq.api.GraphQlInstanceProvider 7 | import org.kotlinq.api.JsonParser 8 | import org.kotlinq.api.Resolver 9 | 10 | 11 | /** 12 | * Simple module/dependency container. 13 | * 14 | * Modules are: 15 | * * [AdapterService]: Creates nodes/edges 16 | * * [GraphQlInstanceProvider]: Creates instances (i.e. the idea of mapping GraphQl selection sets -> pojos) 17 | * * [JsonParser]: Lower-level, used by next one. TODO should probably isolate this 18 | * * [Resolver]: Resolves the graph with the result 19 | * 20 | * @author prestongarno 21 | */ 22 | internal 23 | interface Configuration { 24 | 25 | fun configure(configuration: Builder.() -> Unit) 26 | 27 | 28 | companion object : Configuration { 29 | 30 | private val container: Kodein = ServiceContainer.kodein 31 | 32 | inline fun instance() = container.instance() 33 | 34 | inline fun use(instance: T) = 35 | ServiceContainer.reconfigure(instance) 36 | 37 | override fun configure(configuration: Builder.() -> Unit) = Builder() 38 | .apply(configuration) 39 | .notNullValues 40 | .forEach(ServiceContainer::reconfigure) 41 | 42 | internal 43 | class Builder( 44 | var adapterService: AdapterService? = null, 45 | var resolver: Resolver? = null, 46 | var jsonParser: JsonParser? = null, 47 | var instanceProvider: GraphQlInstanceProvider? = null) { 48 | 49 | internal 50 | val notNullValues: List 51 | get() = 52 | listOfNotNull( 53 | adapterService, 54 | resolver, 55 | jsonParser, 56 | instanceProvider) 57 | } 58 | 59 | } 60 | } 61 | 62 | -------------------------------------------------------------------------------- /core/src/main/kotlin/org/kotlinq/api/services/ServiceContainer.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("MemberVisibilityCanBePrivate") 2 | 3 | package org.kotlinq.api.services 4 | 5 | import com.github.salomonbrys.kodein.Kodein 6 | import com.github.salomonbrys.kodein.bind 7 | import com.github.salomonbrys.kodein.singleton 8 | import org.kotlinq.api.AdapterService 9 | import org.kotlinq.api.GraphQlInstanceProvider 10 | import org.kotlinq.api.JsonParser 11 | import org.kotlinq.api.services.wrappers.AdapterWrapper 12 | import org.kotlinq.api.services.wrappers.GraphQlInstanceProviderWrapper 13 | import org.kotlinq.api.services.wrappers.JsonParsingWrapper 14 | import org.kotlinq.models.GraphQlInstanceProviderImpl 15 | import org.kotlinq.properties.AdapterServiceImpl 16 | import org.kotlinq.resolvers.JsonParserImpl 17 | 18 | 19 | /** 20 | * Wrapper for highest level service dependency configuration 21 | * 22 | * Unfortunately manual delegation is required to re-configure 23 | * dependencies while easily supporting companion object delegate impl., 24 | * so these values are wrapped and can be re-configured within the same VM without re-loading classes. 25 | * 26 | * when companion objects of interfaces can implement configurations indirectly, 27 | * it makes referencing the dependency explicit by domain model name. 28 | * 29 | * Probably overengineered. 30 | */ 31 | internal 32 | object ServiceContainer { 33 | 34 | val kodein = Kodein { 35 | bind() with singleton { adapterService } 36 | bind() with singleton { jsonParser } 37 | bind() with singleton { graphQlInstanceProvider } 38 | } 39 | 40 | fun reconfigure(instance: T) = 41 | getWrapper(instance).forEach { 42 | if (it === instance) it.useDefault() else it.use(instance) 43 | } 44 | 45 | @Suppress("UNCHECKED_CAST") 46 | private fun getWrapper(instance: T): List> { 47 | return wrappers.filter { wrapper -> wrapper.clazz.isInstance(instance) } 48 | .mapNotNull { it as? ConfigurableDependency } 49 | } 50 | 51 | private val adapterService = AdapterWrapper(AdapterServiceImpl()) 52 | private val graphQlInstanceProvider = GraphQlInstanceProviderWrapper(GraphQlInstanceProviderImpl()) 53 | private val jsonParser = JsonParsingWrapper(JsonParserImpl()) 54 | 55 | private val wrappers = listOf( 56 | adapterService, 57 | graphQlInstanceProvider, 58 | jsonParser) 59 | 60 | fun useDefaults() { 61 | wrappers.forEach(Wrapper<*>::useDefault) 62 | } 63 | } 64 | 65 | -------------------------------------------------------------------------------- /core/src/main/kotlin/org/kotlinq/api/services/Wrapper.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.api.services 2 | 3 | import kotlin.reflect.KClass 4 | import kotlin.reflect.full.companionObject 5 | 6 | 7 | internal 8 | abstract class Wrapper(private val default: T, val clazz: KClass) 9 | : ConfigurableDependency { 10 | 11 | @Volatile 12 | private var _instance: T = default 13 | 14 | final 15 | override fun use(instance: T) = synchronized(this) { 16 | this._instance = if (instance !== this 17 | && clazz.companionObject != instance::class) instance else default 18 | } 19 | 20 | final 21 | override fun instance(): T = _instance 22 | 23 | final override fun useDefault() = 24 | use(this.default) 25 | } 26 | 27 | 28 | -------------------------------------------------------------------------------- /core/src/main/kotlin/org/kotlinq/api/services/wrappers/AdapterWrapper.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.api.services.wrappers 2 | 3 | import org.kotlinq.api.AdapterService 4 | import org.kotlinq.api.Fragment 5 | import org.kotlinq.api.PropertyInfo 6 | import org.kotlinq.api.ScalarAdapterService 7 | import org.kotlinq.api.services.Wrapper 8 | import java.io.InputStream 9 | 10 | internal 11 | class AdapterWrapper(instance: AdapterService) 12 | : Wrapper(instance, AdapterService::class), 13 | AdapterService { 14 | 15 | override fun instanceProperty(info: PropertyInfo, fragment: Fragment) = 16 | instance().instanceProperty(info, fragment) 17 | 18 | override fun fragmentProperty(info: PropertyInfo, fragments: Set) = 19 | instance().fragmentProperty(info, fragments) 20 | 21 | override val scalarAdapters: ScalarAdapterService 22 | get() = instance().scalarAdapters 23 | 24 | override fun deserializer(info: PropertyInfo, init: (InputStream) -> Any?) = 25 | instance().deserializer(info, init) 26 | 27 | override fun parser(info: PropertyInfo, init: (String) -> Any?) = 28 | instance().parser(info, init) 29 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/org/kotlinq/api/services/wrappers/GraphQlInstanceProviderWrapper.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.api.services.wrappers 2 | 3 | import org.kotlinq.api.GraphQlInstanceProvider 4 | import org.kotlinq.api.services.Wrapper 5 | 6 | internal class GraphQlInstanceProviderWrapper(default: GraphQlInstanceProvider) 7 | : Wrapper(default, GraphQlInstanceProvider::class), 8 | GraphQlInstanceProvider { 9 | 10 | override fun newContextBuilder() = instance().newContextBuilder() 11 | } 12 | -------------------------------------------------------------------------------- /core/src/main/kotlin/org/kotlinq/api/services/wrappers/JsonParsingWrapper.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.api.services.wrappers 2 | 3 | import org.kotlinq.api.JsonParser 4 | import org.kotlinq.api.services.Wrapper 5 | 6 | internal 7 | class JsonParsingWrapper(default: JsonParser) 8 | : Wrapper(default, JsonParser::class), 9 | JsonParser { 10 | 11 | override fun parseToObject(string: String, rootObjectName: String) = 12 | instance().parseToObject(string, rootObjectName) 13 | 14 | override fun parseToArray(string: String) = 15 | instance().parseToArray(string) 16 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/org/kotlinq/common/Common.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.common 2 | 3 | @Suppress("UNCHECKED_CAST") 4 | private object Block { 5 | private val empty: Any?.() -> Unit = { /* nothing */ } 6 | 7 | fun emptyBlock(): T.() -> Unit = empty 8 | } 9 | 10 | fun empty(): T.() -> Unit = Block.emptyBlock() 11 | 12 | internal 13 | fun Map.stringify(): String = if (entries.isEmpty()) "" else 14 | entries.joinToString(prefix = "(", postfix = ")", separator = ",") { (k, v) -> "$k: ${formatArg(v)}" } 15 | 16 | internal 17 | fun String.bracket(): String = "[$this]" 18 | 19 | internal 20 | fun String.parenthesize(): String = "($this)" 21 | 22 | internal fun String.quote() = """"$this"""" 23 | 24 | internal 25 | fun formatArg(value: Any): String = when (value) { 26 | is Int, is Boolean -> "$value" 27 | is Float -> "${value}f" 28 | is String -> value.quote() 29 | is Enum<*> -> value.name.quote() 30 | is List<*> -> value 31 | .map { formatArg(it ?: "") } 32 | .filter { it.isNotBlank() } 33 | .joinToString(",", "[ ", " ]") 34 | else -> value.toString().quote() 35 | } 36 | 37 | internal 38 | fun T.unit(block: T.() -> Any?) { 39 | block() 40 | } 41 | 42 | internal 43 | fun Any?.ignore() = Unit 44 | 45 | internal 46 | fun MutableList.addFirst(element: E) = 47 | add(0, element).ignore() 48 | 49 | internal 50 | fun MutableList.addLast(element: E) = 51 | add(element).ignore() 52 | 53 | 54 | internal inline 55 | fun Any?.cast(): T? { 56 | return this as? T 57 | } 58 | -------------------------------------------------------------------------------- /core/src/main/kotlin/org/kotlinq/fragments/FragmentCollector.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.fragments 2 | 3 | import org.kotlinq.api.Fragment 4 | import org.kotlinq.api.GraphVisitor 5 | 6 | 7 | fun Fragment.getFragments(uniqueOnly: Boolean = true): Set { 8 | val fragments = mutableSetOf() 9 | 10 | GraphVisitor.builder() 11 | .onNotifyEnter { current -> 12 | 13 | if (uniqueOnly && current in fragments) 14 | false 15 | else let { 16 | fragments += current 17 | true 18 | } 19 | 20 | }.build() 21 | .also(::traverse) 22 | 23 | return fragments 24 | } 25 | -------------------------------------------------------------------------------- /core/src/main/kotlin/org/kotlinq/models/GraphQlInstanceImpl.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.models 2 | 3 | import org.kotlinq.api.Adapter 4 | import org.kotlinq.api.Fragment 5 | import org.kotlinq.api.FragmentContext 6 | import org.kotlinq.api.GraphQlInstance 7 | import org.kotlinq.api.InstanceAdapter 8 | import org.kotlinq.common.cast 9 | 10 | 11 | internal 12 | class GraphQlInstanceImpl( 13 | properties: List) : GraphQlInstance { 14 | 15 | 16 | override val properties = properties 17 | .filterNot(Adapter::isEmptySelectionSet) 18 | .map { it.propertyInfo.graphQlName to it } 19 | .toMap() 20 | 21 | override fun isResolved(): Boolean = 22 | properties.filterNot { it.value.propertyInfo.isNullable } 23 | .count { !it.value.isResolved() } == 0 24 | 25 | override fun equals(other: Any?): Boolean { 26 | 27 | (other as? GraphQlInstance) ?: return false 28 | 29 | if (properties.size != other.properties.size) 30 | return false 31 | 32 | return other.properties.all { (name, adapter) -> 33 | properties[name] == adapter 34 | } 35 | } 36 | 37 | override fun hashCode(): Int = 38 | properties.entries.fold(0) { acc, (name, adapter) -> 39 | acc.times(31) + (name.hashCode() + adapter.hashCode()) 40 | } 41 | 42 | } 43 | 44 | 45 | 46 | 47 | private 48 | fun Adapter.isEmptySelectionSet(): Boolean = 49 | this.cast()?.fragment?.isEmpty() 50 | ?: this.cast()?.fragments?.isEmpty() 51 | ?: false 52 | 53 | private fun Fragment.isEmpty(): Boolean = 54 | graphQlInstance.properties.isEmpty() 55 | 56 | -------------------------------------------------------------------------------- /core/src/main/kotlin/org/kotlinq/models/GraphQlInstanceProviderImpl.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.models 2 | 3 | import org.kotlinq.api.Adapter 4 | import org.kotlinq.api.BindableContext 5 | import org.kotlinq.api.Fragment 6 | import org.kotlinq.api.GraphQlInstanceProvider 7 | import org.kotlinq.common.addLast 8 | 9 | internal 10 | class GraphQlInstanceProviderImpl : GraphQlInstanceProvider { 11 | 12 | override fun newContextBuilder(): BindableContext { 13 | val properties = mutableListOf() 14 | return BindableContext.newBindableContext(properties::addLast) { 15 | Fragment.createFragment(it, GraphQlInstanceImpl(properties)) 16 | } 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/org/kotlinq/printer/PluginPrinter.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.printer 2 | 3 | import org.kotlinq.api.Adapter 4 | import org.kotlinq.api.Fragment 5 | import org.kotlinq.api.FragmentContext 6 | import org.kotlinq.api.GraphVisitor 7 | import org.kotlinq.api.Printer 8 | 9 | internal 10 | fun Printer.visit(fragment: Fragment) = 11 | PluginPrinter(this, fragment).toString() 12 | 13 | 14 | internal 15 | class PluginPrinter(val printer: Printer, val fragment: Fragment) : GraphVisitor { 16 | 17 | val config = printer.configuration 18 | val metaPropertyStrategy = printer.metaStrategy 19 | 20 | val stringer = StringBuilder() 21 | private fun String.emit() = stringer.append(this) 22 | 23 | init { 24 | fragment.traverse(this) 25 | } 26 | 27 | private val string by lazy(stringer::toString) 28 | override fun toString(): String = string 29 | 30 | override fun notifyEnter(fragment: Fragment, inline: Boolean): Boolean { 31 | if (inline) printer.inlineFragmentEval(fragment.typeName).emit() 32 | printer.enterContextEval().emit() 33 | return true // fragment extraction not supported yet 34 | } 35 | 36 | override fun visitContext(fragment: Fragment) { 37 | for ((propertyName, predicate) in metaPropertyStrategy.extraProperties) { 38 | if (predicate(fragment)) printer.fieldNameEval(propertyName).emit() 39 | } 40 | fragment.graphQlInstance.accept(this) 41 | } 42 | 43 | override fun notifyExit(fragment: Fragment) { 44 | printer.exitContextEval().emit() 45 | } 46 | 47 | override fun enterField(adapter: Adapter): Boolean { 48 | printer.fieldNameEval( 49 | adapter.propertyInfo.graphQlName).emit() 50 | 51 | var index = 0 52 | val size = adapter.propertyInfo.arguments.size 53 | 54 | if (size > 0) "(".emit() else return true 55 | 56 | for ((name, argument) in adapter.propertyInfo.arguments) { 57 | printer.argumentNameEval(name).emit() 58 | config.argumentSeparator.emit() 59 | (argument ?: "null").let { 60 | printer.argumentValueEval(it).emit() 61 | } 62 | if (++index < size) config.commaSeparator.emit() 63 | } 64 | 65 | ")".emit() 66 | 67 | return true 68 | } 69 | 70 | override fun visitFragmentContext(context: FragmentContext) { 71 | if (enterField(context)) { 72 | printer.enterContextEval().emit() 73 | super.visitFragmentContext(context) 74 | printer.exitContextEval().emit() 75 | } 76 | } 77 | } 78 | 79 | -------------------------------------------------------------------------------- /core/src/main/kotlin/org/kotlinq/properties/AdapterServiceImpl.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.properties 2 | 3 | import org.kotlinq.api.AdapterService 4 | import org.kotlinq.api.Fragment 5 | import org.kotlinq.api.PropertyInfo 6 | import org.kotlinq.api.ScalarAdapterService 7 | import java.io.InputStream 8 | 9 | 10 | internal 11 | class AdapterServiceImpl( 12 | override val scalarAdapters: ScalarAdapterService = ScalarAdapterServiceImpl() 13 | ) : AdapterService { 14 | 15 | override fun deserializer(info: PropertyInfo, init: (InputStream) -> Any?) = 16 | DeserializingProperty(info, init) 17 | 18 | override fun parser(info: PropertyInfo, init: (String) -> Any?) = 19 | ParsedProperty(info, init) 20 | 21 | override fun instanceProperty(info: PropertyInfo, fragment: Fragment) = 22 | InstanceProperty(info, fragment) 23 | 24 | override fun fragmentProperty(info: PropertyInfo, fragments: Set) = 25 | FragmentProperty(info, fragments) 26 | } 27 | -------------------------------------------------------------------------------- /core/src/main/kotlin/org/kotlinq/properties/BuiltInTypeMapper.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.properties 2 | 3 | import org.kotlinq.api.ScalarAdapterService 4 | 5 | 6 | class BuiltInTypeMapper( 7 | override val booleanMapper: (String) -> Boolean = String::toBoolean, 8 | override val floatMapper: (String) -> Float = { it.toFloatOrNull() ?: 0f }, 9 | override val intMapper: (String) -> Int = { it.toIntOrNull() ?: 0 }, 10 | override val stringMapper: (String) -> String = { it } 11 | ) : ScalarAdapterService.TypeMappers -------------------------------------------------------------------------------- /core/src/main/kotlin/org/kotlinq/properties/DeserializingProperty.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.properties 2 | 3 | import org.kotlinq.api.Adapter 4 | import org.kotlinq.api.DeserializingAdapter 5 | import org.kotlinq.api.PropertyInfo 6 | import java.io.InputStream 7 | 8 | 9 | internal 10 | class DeserializingProperty( 11 | override val propertyInfo: PropertyInfo, 12 | override val initializer: (java.io.InputStream) -> Any? 13 | ) : DeserializingAdapter { 14 | 15 | private var value: Any? = null 16 | 17 | override fun getValue() = value 18 | 19 | override fun isResolved() = value != null || propertyInfo.isNullable 20 | 21 | override fun setValue(value: InputStream?): Boolean { 22 | this.value = value?.let(initializer) 23 | return isResolved() 24 | } 25 | 26 | override fun equals(other: Any?) = 27 | Adapter.adapterEquals(this, other as? Adapter) 28 | 29 | override fun hashCode() = 30 | Adapter.adapterHashcode(this) 31 | 32 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/org/kotlinq/properties/FragmentProperty.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.properties 2 | 3 | import org.kotlinq.api.Adapter 4 | import org.kotlinq.api.Fragment 5 | import org.kotlinq.api.FragmentAdapter 6 | import org.kotlinq.api.PropertyInfo 7 | import org.kotlinq.api.Resolver 8 | 9 | 10 | internal 11 | class FragmentProperty( 12 | override val propertyInfo: PropertyInfo, 13 | fragments: Set 14 | ) : FragmentAdapter { 15 | 16 | override val fragments: Map by lazy { 17 | fragments.map { it.typeName to it }.toMap() 18 | } 19 | 20 | private var value: Fragment? = null 21 | 22 | override fun getValue() = value 23 | 24 | override fun setValue(typeName: String, values: Map, resolver: Resolver): Boolean { 25 | this.value = fragments[typeName]?.apply { resolver.resolve(values, this) } 26 | return isResolved() 27 | } 28 | 29 | override fun isResolved() = 30 | value?.graphQlInstance?.isResolved() == true 31 | || propertyInfo.isNullable 32 | 33 | 34 | override fun equals(other: Any?) = (other as? FragmentAdapter) 35 | ?.let { Adapter.adapterEquals(this, it) } == true 36 | && other.fragments.size == this.fragments.size 37 | && other.fragments.count { fragments[it.key] != it.value } == 0 38 | 39 | override fun hashCode(): Int = 40 | Adapter.adapterHashcode(this) * 31 + fragments.asSequence().fold(0) { acc, curr -> 41 | acc.times(31) + curr.value.graphQlInstance.hashCode() 42 | } 43 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/org/kotlinq/properties/InstanceProperty.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.properties 2 | 3 | import org.kotlinq.api.Adapter 4 | import org.kotlinq.api.Fragment 5 | import org.kotlinq.api.InstanceAdapter 6 | import org.kotlinq.api.PropertyInfo 7 | import org.kotlinq.api.Resolver 8 | 9 | internal 10 | class InstanceProperty( 11 | override val propertyInfo: PropertyInfo, 12 | override val fragment: Fragment 13 | ) : InstanceAdapter { 14 | 15 | override fun isResolved(): Boolean = 16 | fragment.graphQlInstance.isResolved() || propertyInfo.isNullable 17 | 18 | override fun setValue(result: Map, resolver: Resolver): Boolean { 19 | resolver.resolve(result, fragment) 20 | return isResolved() 21 | } 22 | 23 | override fun getValue(): Fragment = fragment 24 | 25 | override fun equals(other: Any?): Boolean = (other as? InstanceAdapter)?.let { 26 | Adapter.adapterEquals(this, it) && it.fragment == fragment 27 | } ?: false 28 | 29 | override fun hashCode() = Adapter.adapterHashcode(this) * 31 + fragment.hashCode() 30 | } 31 | -------------------------------------------------------------------------------- /core/src/main/kotlin/org/kotlinq/properties/ParsedProperty.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.properties 2 | 3 | import org.kotlinq.api.Adapter 4 | import org.kotlinq.api.PropertyInfo 5 | import org.kotlinq.api.ParsingAdapter 6 | 7 | 8 | internal 9 | class ParsedProperty( 10 | override val propertyInfo: PropertyInfo, 11 | override val initializer: (String) -> Any? 12 | ) : ParsingAdapter { 13 | 14 | private var result: Any? = null 15 | 16 | override fun getValue(): Any? = result 17 | 18 | override fun setValue(value: String?): Boolean { 19 | result = this.initializer(value ?: "") 20 | return isResolved() 21 | } 22 | 23 | override fun isResolved() = 24 | result != null || propertyInfo.isNullable 25 | 26 | override fun equals(other: Any?) = 27 | Adapter.adapterEquals(this, other as? Adapter) 28 | 29 | override fun hashCode() = 30 | Adapter.adapterHashcode(this) 31 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/org/kotlinq/properties/ScalarAdapterServiceImpl.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.properties 2 | 3 | import org.kotlinq.api.ParsingAdapter 4 | import org.kotlinq.api.PropertyInfo 5 | import org.kotlinq.api.ScalarAdapterService 6 | import org.kotlinq.api.Kind 7 | 8 | 9 | class ScalarAdapterServiceImpl( 10 | override val mappers: ScalarAdapterService.TypeMappers = BuiltInTypeMapper() 11 | ) : ScalarAdapterService { 12 | 13 | override fun newAdapter(info: PropertyInfo): ParsingAdapter { 14 | require(info.kind.isScalar) { "Type $info is not a scalar type" } 15 | return when (info.kind.rootKind()) { 16 | Kind.Scalar._Int -> IntAdapterImpl(info, mappers.intMapper) 17 | Kind.Scalar._String -> StringAdapterImpl(info, mappers.stringMapper) 18 | Kind.Scalar._Float -> FloatAdapterImpl(info, mappers.floatMapper) 19 | Kind.Scalar._Boolean -> BooleanAdapterImpl(info, mappers.booleanMapper) 20 | else -> { 21 | throw IllegalArgumentException("Illegal info '$info'") 22 | } 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/org/kotlinq/properties/ScalarAdaptersImpl.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.properties 2 | 3 | import org.kotlinq.api.Adapter 4 | import org.kotlinq.api.BooleanAdapter 5 | import org.kotlinq.api.FloatAdapter 6 | import org.kotlinq.api.PropertyInfo 7 | import org.kotlinq.api.IntAdapter 8 | import org.kotlinq.api.ParsingAdapter 9 | import org.kotlinq.api.StringAdapter 10 | 11 | 12 | sealed class PrimitiveAdapter : ParsingAdapter { 13 | 14 | override fun equals(other: Any?) = 15 | Adapter.adapterEquals(this, other as? Adapter) 16 | 17 | override fun hashCode() = 18 | Adapter.adapterHashcode(this) 19 | } 20 | 21 | 22 | class IntAdapterImpl( 23 | override val propertyInfo: PropertyInfo, 24 | override val initializer: (String) -> Int 25 | ) : PrimitiveAdapter(), IntAdapter { 26 | 27 | private var value = 0 28 | 29 | override fun getValue() = value 30 | 31 | override fun setValue(value: String?): Boolean { 32 | this.value = initializer(value ?: "") 33 | return isResolved() 34 | } 35 | 36 | override fun isResolved() = true 37 | } 38 | 39 | class StringAdapterImpl( 40 | override val propertyInfo: PropertyInfo, 41 | override val initializer: (String) -> String 42 | ) : PrimitiveAdapter(), StringAdapter { 43 | 44 | private var value: String? = null 45 | 46 | override fun getValue() = value ?: "" 47 | 48 | override fun setValue(value: String?): Boolean { 49 | this.value = initializer(value ?: "") 50 | return isResolved() 51 | } 52 | 53 | override fun isResolved() = true 54 | } 55 | 56 | class FloatAdapterImpl( 57 | override val propertyInfo: PropertyInfo, 58 | override val initializer: (String) -> Float 59 | ) : PrimitiveAdapter(), FloatAdapter { 60 | 61 | private var value = 0f 62 | 63 | override fun getValue() = value 64 | 65 | override fun setValue(value: String?): Boolean { 66 | this.value = initializer(value ?: "") 67 | return isResolved() 68 | } 69 | 70 | override fun isResolved() = true 71 | } 72 | 73 | class BooleanAdapterImpl( 74 | override val propertyInfo: PropertyInfo, 75 | override val initializer: (String) -> Boolean 76 | ) : PrimitiveAdapter(), BooleanAdapter { 77 | 78 | private var value = false 79 | 80 | override fun getValue() = value 81 | 82 | override fun setValue(value: String?): Boolean { 83 | this.value = initializer(value ?: "") 84 | return isResolved() 85 | } 86 | 87 | override fun isResolved() = true 88 | } 89 | -------------------------------------------------------------------------------- /core/src/main/kotlin/org/kotlinq/resolvers/JsonParserImpl.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.resolvers 2 | 3 | import com.beust.klaxon.Klaxon 4 | import org.kotlinq.api.JsonParser 5 | import java.io.StringReader 6 | 7 | 8 | class JsonParserImpl : JsonParser { 9 | 10 | override fun parseToObject(string: String, rootObjectName: String): Map { 11 | return Klaxon().parseJsonObject(reader = StringReader(string)).obj(rootObjectName)?.map ?: emptyMap() 12 | } 13 | 14 | override fun parseToArray(string: String): Iterable { 15 | return Klaxon().parseArray(string) ?: emptyList() 16 | } 17 | 18 | } -------------------------------------------------------------------------------- /core/src/test/kotlin/org/kotlinq/Common.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq 2 | 3 | import com.google.common.truth.Truth.assertThat 4 | import org.kotlinq.api.PropertyInfo 5 | import org.kotlinq.api.Kind 6 | import kotlin.reflect.full.isSubclassOf 7 | 8 | infix fun Throwable.withMessageContaining(value: String) = 9 | require((message ?: "").contains(value)) { 10 | "Expected <'$this'> to contain <'$value'>" 11 | } 12 | 13 | infix fun Throwable.messageMatchingExactly(value: Regex) = 14 | require((message ?: "").matches(value)) { 15 | errNotMatching(this, value) 16 | } 17 | 18 | infix fun Throwable.messageMatchingExactly(value: String) = 19 | message!! eq value 20 | 21 | infix fun Throwable.withMessageContaining(value: Regex) = 22 | require((message ?: "").contains(value)) { 23 | "Expected <'$this'> to contain <'$value'>" 24 | } 25 | 26 | infix fun Any?.matchesNotNull(expect: Any?) = require(this!! == expect!!) { 27 | errNotMatching(this, expect) 28 | } 29 | 30 | infix fun Any.eq(expect: Any?) = 31 | assertThat(this).isEqualTo(expect) 32 | 33 | infix fun Any.notEq(expect: Any?) = 34 | assertThat(this).isNotEqualTo(expect) 35 | 36 | fun Any?.println() = println(this) 37 | 38 | inline fun assertThrows(noinline block: () -> Unit): T { 39 | try { 40 | block() 41 | } catch (ex: Throwable) { 42 | if (!ex::class.isSubclassOf(T::class)) { 43 | throw java.lang.AssertionError("Expected exception '${T::class.qualifiedName}' " + 44 | "but was '${ex::class.qualifiedName}'", ex) 45 | } else return ex as T 46 | } 47 | throw AssertionError("No exception was thrown (Expected: '${T::class.qualifiedName}'") 48 | } 49 | 50 | private fun errNotMatching(expect: Any?, actual: Any?) = 51 | "Expected <'$expect'> was not equal to <'$actual'>" 52 | 53 | fun info( 54 | graphQlName: String, 55 | kind: Kind = Kind.Scalar._String, 56 | arguments: Map = emptyMap() 57 | ) = PropertyInfo(graphQlName, kind, arguments) 58 | -------------------------------------------------------------------------------- /core/src/test/kotlin/org/kotlinq/MockKType.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq 2 | 3 | import org.junit.Test 4 | import kotlin.reflect.KClass 5 | import kotlin.reflect.KClassifier 6 | import kotlin.reflect.KType 7 | import kotlin.reflect.KTypeProjection 8 | import kotlin.reflect.KVariance 9 | import kotlin.reflect.full.withNullability 10 | 11 | 12 | fun mockType(clazz: KClass<*>, arguments: List> = emptyList(), isMarkedNullable: Boolean = true): KType { 13 | return MockedKTypeImpl( 14 | arguments.map { KTypeProjection(KVariance.INVARIANT, MockedKTypeImpl(emptyList(), it, true)) }, 15 | clazz, 16 | isMarkedNullable) 17 | } 18 | 19 | private 20 | class MockedKTypeImpl( 21 | override val arguments: List, 22 | override val classifier: KClassifier?, 23 | override val isMarkedNullable: Boolean 24 | ) : KType { 25 | 26 | override fun equals(other: Any?): Boolean = 27 | other is KType 28 | && other.classifier == classifier 29 | && other.isMarkedNullable == isMarkedNullable 30 | && other.arguments.containsAll(arguments) 31 | 32 | override fun hashCode(): Int = 33 | (classifier?.hashCode() ?: 0 * 31+ 34 | isMarkedNullable.hashCode() * 31) * 31 + 35 | arguments.fold(0) { acc, curr -> acc * 31 + curr.hashCode() } 36 | 37 | override fun toString(): String { 38 | return classifier.toString().let { if (isMarkedNullable) it else "$it!" } 39 | } 40 | } 41 | 42 | 43 | class MockKTypeTestIntegrityTest { 44 | 45 | val expect: String? = "" 46 | 47 | @Test fun mockStringEqualsBuiltinType() { 48 | 49 | val expectType = this::expect.returnType 50 | 51 | mockType(String::class) eq expectType 52 | mockType(String::class, isMarkedNullable = false).let { type -> 53 | type notEq this::expect.returnType 54 | type eq expectType.withNullability(false) 55 | } 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /core/src/test/kotlin/org/kotlinq/PrimitiveData.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq 2 | 3 | import java.util.* 4 | import java.util.concurrent.ThreadLocalRandom 5 | import java.util.concurrent.ThreadLocalRandom.current 6 | import kotlin.coroutines.experimental.buildSequence 7 | 8 | enum class PrimitiveData(val generator: () -> Any) { 9 | STRING(UUID.randomUUID()::toString), 10 | INT({ randomInt(Int.MIN_VALUE, Int.MAX_VALUE) }), 11 | FLOAT({ ThreadLocalRandom.current().nextDouble().toFloat() }), 12 | BOOLEAN({ (randomInt(0, 2) == 1) }), 13 | ENUM({ randomArgumentType() }), // so meta... 14 | LIST({ 15 | val type = randomArgumentType() 16 | buildSequence { 17 | for (i in 1..randomInt(5, 7)) 18 | yield(type.generator()) 19 | }.toList() 20 | }); 21 | 22 | companion object Generator { 23 | 24 | fun randomArgumentType() = 25 | PrimitiveData.values()[ThreadLocalRandom.current().nextInt(0, PrimitiveData.values().size)] 26 | 27 | fun randomArgument() = STRING.generator().toString() to randomArgumentType().generator() 28 | 29 | fun randomGraphQlArgumentMap(size: Int = randomInt(0, 1000)) = buildSequence { 30 | for (i in 1..size) yield(randomArgument()) 31 | }.toMap() 32 | } 33 | } 34 | 35 | private 36 | fun randomInt(min: Int, max: Int) = current().nextInt(min, max) 37 | -------------------------------------------------------------------------------- /core/src/test/kotlin/org/kotlinq/ScalarPropertyTests.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq 2 | 3 | import org.junit.Test 4 | import org.kotlinq.api.Kotlinq 5 | import org.kotlinq.api.Kind 6 | 7 | 8 | class ScalarPropertyTests { 9 | 10 | @Test fun simpleStringAdapterIOTest() { 11 | val expect = "Hello, World!" 12 | Kotlinq.adapterService.scalarAdapters 13 | .newAdapter(info("any")) 14 | .apply { 15 | setValue(expect) 16 | require(getValue() == expect) 17 | } 18 | } 19 | 20 | 21 | @Test fun simpleIntAdapterIOTest() { 22 | val expect = 1000 23 | Kotlinq.adapterService.scalarAdapters 24 | .newAdapter(info("intProperty", Kind.Scalar._Int)) 25 | .apply { 26 | setValue("$expect") 27 | require(getValue() == expect) 28 | } 29 | } 30 | 31 | @Test fun simpleFloatAdapterIOTest() { 32 | val expect = 1000.1f 33 | Kotlinq.adapterService.scalarAdapters 34 | .newAdapter(info("floatProperty", Kind.Scalar._Float)) 35 | .apply { 36 | setValue("$expect") 37 | require(getValue() == expect) 38 | } 39 | } 40 | 41 | @Test fun simpleBooleanAdapterIOTest() { 42 | val expect = false 43 | Kotlinq.adapterService.scalarAdapters 44 | .newAdapter(info("boolProperty", Kind.Scalar._Boolean)) 45 | .apply { 46 | setValue("$expect") 47 | require(getValue() == expect) 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /core/src/test/kotlin/org/kotlinq/configurations/ConfigurationTest.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.configurations 2 | 3 | import org.junit.Ignore 4 | import org.junit.Test 5 | import org.kotlinq.api.GraphQlInstanceProvider 6 | import org.kotlinq.api.JsonParser 7 | import org.kotlinq.api.Kotlinq 8 | import org.kotlinq.api.Resolver 9 | import org.kotlinq.api.services.Configuration 10 | import org.kotlinq.api.services.ServiceContainer 11 | import org.kotlinq.assertThrows 12 | import org.kotlinq.eq 13 | import org.kotlinq.info 14 | import org.kotlinq.messageMatchingExactly 15 | import org.kotlinq.println 16 | import kotlin.reflect.full.memberProperties 17 | import kotlin.reflect.jvm.isAccessible 18 | 19 | class ConfigurationTest { 20 | 21 | private val parser: JsonParser = object : JsonParser { 22 | 23 | override fun parseToObject(string: String, rootObjectName: String) = 24 | listOf(string.split(":").let { it[0] to it[1] }).toMap() 25 | 26 | override fun parseToArray(string: String) = TODO("not implemented") 27 | 28 | } 29 | 30 | @Ignore("Test individual node behaviour post-refactor") 31 | @Test fun assertDependencyIsInitialized() { 32 | 33 | Configuration.configure { 34 | jsonParser = parser 35 | } 36 | 37 | 38 | val parseToObject = JsonParser.parseToObject("Hello:World") 39 | 40 | parseToObject.iterator().next().let { (name, value) -> 41 | require(name == "Hello" && value == "World") 42 | } 43 | 44 | val context = Kotlinq.newContextBuilder().build("Hello") 45 | 46 | require(Resolver.resolve(parseToObject, context)) 47 | 48 | require(context.graphQlInstance.properties["Hello"]?.getValue() == "World") 49 | 50 | } 51 | 52 | @Ignore("Test individual node behaviour post-refactor") 53 | @Test 54 | fun usingDefaults() { 55 | 56 | ServiceContainer.useDefaults() 57 | 58 | val value = JsonParser.parseToObject(""" 59 | { 60 | "data": { 61 | "Hello": "Universe!" 62 | } 63 | } 64 | """.trimIndent()) 65 | 66 | val context = Kotlinq.newContextBuilder().build("Hello") 67 | 68 | 69 | require(Resolver.resolve(value, context)) 70 | 71 | // This fails because the configuration can't change after JVM startup unfortunately 72 | require(context.graphQlInstance.properties["Hello"]?.getValue() == "Universe!") 73 | } 74 | 75 | @Test fun serviceConfigurationDoesNotTriggerStackOverflowByCircularConfiguration() { 76 | // make sure classes are all initialized 77 | ServiceContainer::class 78 | .memberProperties 79 | .filter { it.isAccessible } 80 | .forEach { it.get(ServiceContainer) } 81 | 82 | Kotlinq.newContextBuilder().build("Hello").graphQlInstance.properties.size eq 0 83 | 84 | Configuration.use(object : GraphQlInstanceProvider { 85 | override fun newContextBuilder()= throw NullPointerException("TEST") 86 | }) 87 | 88 | assertThrows { 89 | Kotlinq.newContextBuilder().println() 90 | } messageMatchingExactly "TEST" 91 | 92 | // Pass self-reference to wrapper class to delegate to self 93 | Configuration.use(GraphQlInstanceProvider.Companion) 94 | 95 | // If didn't prevent the circular reference, this will stackoverflow 96 | Kotlinq.newContextBuilder() 97 | } 98 | 99 | @Test fun resetDependenciesWorksCorrectly() { 100 | ServiceContainer::class 101 | .memberProperties 102 | .filter { it.isAccessible } 103 | .forEach { it.get(ServiceContainer) } 104 | 105 | Configuration.use(object : GraphQlInstanceProvider { 106 | override fun newContextBuilder()= throw NullPointerException("TEST") 107 | }) 108 | 109 | assertThrows { 110 | Kotlinq.newContextBuilder() 111 | } 112 | 113 | ServiceContainer.useDefaults() 114 | 115 | "prop" eq Kotlinq.newContextBuilder() 116 | .register(Kotlinq.adapterService.parser(info("prop"), { it })) 117 | .build("Hello") 118 | .graphQlInstance.properties["prop"] 119 | ?.propertyInfo?.graphQlName 120 | } 121 | } 122 | 123 | -------------------------------------------------------------------------------- /core/src/test/kotlin/org/kotlinq/entities/FragmentEquality.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.entities 2 | 3 | import org.junit.Test 4 | import org.kotlinq.api.Kind 5 | import org.kotlinq.entities.TestFragmentBuilder.Companion.fragment 6 | import org.kotlinq.eq 7 | import org.kotlinq.notEq 8 | 9 | 10 | class FragmentEquality { 11 | 12 | @Test fun `fragments are equal 1`() { 13 | fragment { 14 | scalar("foo") 15 | scalar("bar") 16 | } eq fragment { 17 | scalar("foo") 18 | scalar("bar") 19 | } 20 | } 21 | 22 | 23 | @Test fun `fragments are not equal 1`() { 24 | fragment { 25 | scalar("foo", kind = Kind.float) 26 | } notEq fragment { 27 | scalar("foo", kind = Kind.bool) 28 | } 29 | } 30 | 31 | @Test fun `fragment fragment are not equal`() { 32 | fragment { 33 | "frag1"(Kind.typeNamed("One")) on { 34 | scalar("foo", Kind.integer) 35 | scalar("bar", Kind.bool) 36 | } 37 | } notEq fragment { 38 | "frag1"(Kind.typeNamed("Two")) on { 39 | scalar("foo", Kind.integer) 40 | scalar("bar", Kind.bool) 41 | } 42 | } 43 | } 44 | 45 | @Test fun `fragments are equal 2`() { 46 | 47 | val frag1 = fragment { 48 | "frag1" on { 49 | scalar("hello") 50 | scalar("world", Kind.integer) 51 | scalar("foo") 52 | "frag2" on { 53 | scalar("hello") 54 | scalar("world", Kind.integer) 55 | } 56 | scalar("foo") 57 | } 58 | } 59 | val frag2 = fragment { 60 | "frag1" on { 61 | scalar("hello") 62 | scalar("world", Kind.integer) 63 | scalar("foo") 64 | "frag2" on { 65 | scalar("hello") 66 | scalar("world", Kind.integer) 67 | } 68 | scalar("foo") 69 | } 70 | } 71 | 72 | 73 | frag1.toGraphQl(pretty = true) eq frag2.toGraphQl(pretty = true) 74 | frag1.toGraphQl(pretty = false) eq frag2.toGraphQl(pretty = false) 75 | frag1 eq frag2 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /core/src/test/kotlin/org/kotlinq/entities/PropertyInfoTests.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.entities 2 | 3 | import com.google.common.truth.Truth.assertThat 4 | import org.junit.Test 5 | import org.kotlinq.api.Kind 6 | import org.kotlinq.api.PropertyInfo 7 | 8 | class PropertyInfoTests { 9 | 10 | @Test fun simplePropertyEquals() { 11 | val arguments = mapOf("hello" to "World", "first" to 10) 12 | val info0 = PropertyInfo.propertyNamed("value") 13 | .arguments(arguments) 14 | .typeKind(Kind.string) 15 | .build() 16 | info0.copy() 17 | .let(assertThat(info0)::isEqualTo) 18 | 19 | info0.copy(arguments = arguments.toMutableMap() 20 | .also { it["foo"] = "BAR" } 21 | .toMap()) 22 | .let(assertThat(info0)::isNotEqualTo) 23 | } 24 | 25 | @Test fun complexKindEquals() { 26 | 27 | Kind.typeNamed("Foo") 28 | .asList() 29 | .asList() 30 | .asNullable() 31 | .asList() 32 | .let { it.copy() to it } 33 | .also { (one, two) -> 34 | assertThat(one).isEqualTo(two) 35 | assertThat(one.rootKind()) 36 | .isEqualTo(two.rootKind()) 37 | assertThat(one.toString()) 38 | .isEqualTo(two.toString()) 39 | }.also { (one, two) -> 40 | // change and compare 41 | assertThat(one.asList()) 42 | .isNotEqualTo(one) 43 | assertThat(one.asNullable()) 44 | .isNotEqualTo(one) 45 | assertThat(two.asList()) 46 | .isNotEqualTo(two) 47 | assertThat(two.asNullable()) 48 | .isNotEqualTo(two) 49 | }.also { (one, two) -> 50 | assertThat(one.asList()) 51 | .isEqualTo(two.asList()) 52 | assertThat(one.asNullable()) 53 | .isEqualTo(two.asNullable()) 54 | assertThat(two.rootKind()) 55 | .isEqualTo(one.rootKind()) 56 | }.let { (one, two) -> 57 | PropertyInfo.propertyNamed("foobar").typeKind(one).build() to PropertyInfo.propertyNamed("foobar").typeKind(two).build() 58 | }.also { (prop1, prop2) -> 59 | assertThat(prop1.copy(arguments = mapOf("number" to 1000))) 60 | .isNotEqualTo(prop1) 61 | } 62 | } 63 | } 64 | 65 | -------------------------------------------------------------------------------- /core/src/test/kotlin/org/kotlinq/entities/TestDsl.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.entities 2 | 3 | import org.kotlinq.api.Adapter 4 | import org.kotlinq.api.Fragment 5 | import org.kotlinq.api.Kind 6 | import org.kotlinq.api.Kotlinq 7 | import org.kotlinq.api.PropertyInfo 8 | import org.kotlinq.common.addLast 9 | 10 | typealias TestFragment = TestFragmentBuilder.() -> Unit 11 | 12 | class TestFragmentBuilder private constructor( 13 | private val callback: (Adapter) -> Unit, 14 | block: TestFragmentBuilder.() -> Unit) { 15 | 16 | init { 17 | this.block() 18 | } 19 | 20 | fun Adapter.bind() = callback(this) 21 | 22 | fun scalar(name: String, kind: Kind.Scalar = Kind.string, arguments: Map = emptyMap()): Adapter = 23 | Kotlinq.adapterService.scalarAdapters 24 | .newAdapter(PropertyInfo.propertyNamed(name) 25 | .typeKind(kind) 26 | .arguments(arguments) 27 | .build()).also(callback) 28 | 29 | infix fun String.on(block: TestFragmentBuilder.() -> Unit) = 30 | this(kind = Kind.typeNamed("Fragment0")).on(block) 31 | 32 | operator fun String.invoke(kind: Kind, arguments: Map = emptyMap()) 33 | : Pair>, Kind> = this to arguments to kind 34 | 35 | infix fun Pair>, Kind>.on(block: TestFragmentBuilder.() -> Unit) { 36 | callback(Kotlinq.adapterService.instanceProperty( 37 | PropertyInfo.propertyNamed(this.first.first) 38 | .typeKind(this.second) 39 | .build(), 40 | fragment(second.rootKind().name, block))) 41 | } 42 | 43 | fun bindFragment(propertyName: String, fragment: Fragment) { 44 | this.callback.invoke(Kotlinq.adapterService.instanceProperty(PropertyInfo.propertyNamed(propertyName).build(), fragment)) 45 | } 46 | 47 | companion object { 48 | 49 | fun fragment(type: String = "Fragment0", block: TestFragmentBuilder.() -> Unit): Fragment { 50 | val properties = mutableListOf() 51 | TestFragmentBuilder(properties::addLast, block) 52 | return Kotlinq.newContextBuilder().apply { 53 | properties.forEach { register(it) } 54 | }.build(type) 55 | } 56 | 57 | fun fragment(kind: Kind, block: TestFragmentBuilder.() -> Unit) = 58 | fragment(kind.rootKind().name, block) 59 | } 60 | } 61 | 62 | -------------------------------------------------------------------------------- /core/src/test/kotlin/org/kotlinq/entities/hashcodes/EntityHashcodeTests.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.entities.hashcodes 2 | 3 | import org.junit.Test 4 | import org.kotlinq.PrimitiveData 5 | import org.kotlinq.api.Adapter 6 | import org.kotlinq.api.Kotlinq 7 | import org.kotlinq.api.PropertyInfo 8 | import org.kotlinq.api.Kind 9 | import org.kotlinq.eq 10 | import org.kotlinq.println 11 | import kotlin.coroutines.experimental.buildSequence 12 | 13 | class EntityHashcodeTests { 14 | 15 | @Test fun `fragments with identities matching return same hashcode`() { 16 | 17 | Kind.typeNamed("Hello").println() 18 | 19 | val name = PrimitiveData.STRING.generator().toString() 20 | val arguments = PrimitiveData.randomGraphQlArgumentMap() 21 | val instanceTypeName = PrimitiveData.STRING.generator().toString() 22 | 23 | val generator: () -> Adapter = { 24 | 25 | Kotlinq.adapterService.fragmentProperty( 26 | PropertyInfo 27 | .propertyNamed(name) 28 | .typeKind(Kind.typeNamed(instanceTypeName)) 29 | .arguments(arguments) 30 | .build(), 31 | setOf (Kotlinq.newContextBuilder().build(instanceTypeName))) 32 | 33 | } 34 | 35 | val expected = generator().hashCode() 36 | 37 | buildSequence { 38 | for (i in 1..10) yield(generator()) 39 | }.forEach { 40 | it.hashCode() eq expected 41 | } 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /core/src/test/kotlin/org/kotlinq/entities/print/PrinterConfigurationTests.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.entities.print 2 | 3 | import com.google.common.truth.Truth.assertThat 4 | import org.junit.Test 5 | import org.kotlinq.api.Kind 6 | import org.kotlinq.api.Printer 7 | import org.kotlinq.api.PrintingConfiguration 8 | import org.kotlinq.api.PropertyInfo 9 | import org.kotlinq.entities.TestFragmentBuilder.Companion.fragment 10 | import org.kotlinq.eq 11 | 12 | class PrinterConfigurationTests { 13 | 14 | val query = fragment("Query") { 15 | scalar("hello") 16 | } 17 | 18 | @Test fun foo() { 19 | 20 | query.typeName eq "Query" 21 | query.graphQlInstance.properties["hello"]!!.propertyInfo eq 22 | PropertyInfo.propertyNamed("hello") 23 | .typeKind(Kind.string) 24 | .build() 25 | 26 | assertThat(query.toGraphQl(pretty = false)) 27 | .isEqualTo("{id,__typename,hello}") 28 | } 29 | 30 | @Test fun bar() { 31 | 32 | 33 | Printer.fromConfiguration(PrintingConfiguration.PRETTY) 34 | .toBuilder() 35 | .metaStrategy( 36 | Printer.MetaStrategy.builder() 37 | .include("TODO") 38 | .includeId() 39 | .includeTypename() 40 | .build() 41 | ).build() 42 | .printFragmentToString(query) 43 | 44 | .let { 45 | 46 | val expect = """ 47 | |{ 48 | | TODO 49 | | id 50 | | __typename 51 | | hello 52 | |} 53 | """.trimMargin("|") 54 | 55 | assertThat(it).isEqualTo(expect) 56 | } 57 | 58 | } 59 | 60 | @Test fun baz() { 61 | 62 | val standard = Printer.transformationBuilder() 63 | .build() 64 | 65 | val printer = standard.toBuilder() 66 | .evalFieldName { standard.fieldNameEval(it) + "!!!" } 67 | .build() 68 | 69 | val query = fragment("Query") { 70 | scalar("foo") 71 | scalar("bar") 72 | scalar("baz") 73 | } 74 | 75 | // interrestingg 76 | assertThat(printer.printFragmentToString(query)) 77 | .isEqualTo("{id!!!,__typename!!!,foo!!!,bar!!!,baz!!!}") 78 | } 79 | 80 | @Test fun nestedFragmentTest() { 81 | val query = fragment { 82 | scalar("foo") 83 | scalar("bar") 84 | "baz" on { 85 | scalar("bazbar") 86 | } 87 | } 88 | 89 | val configuration = PrintingConfiguration.builder() 90 | .pretty(true) 91 | .indent("++++") 92 | .lcurlyChar('<') 93 | .rcurlyChar('>') 94 | .metaStrategy(Printer.MetaStrategy.NONE) 95 | .build() 96 | val prettyPrinter = configuration.let(Printer.Companion::fromConfiguration) 97 | 98 | val unPrettyPrinter = configuration.toBuilder() 99 | .pretty(false) 100 | .build() 101 | .let(Printer.Companion::fromConfiguration) 102 | 103 | val expect = """ 104 | < 105 | ++++foo 106 | ++++bar 107 | ++++baz < 108 | ++++++++bazbar 109 | ++++> 110 | > 111 | """.trimIndent() 112 | 113 | assertThat(prettyPrinter.printFragmentToString(query)) 114 | .isEqualTo(expect) 115 | 116 | assertThat(unPrettyPrinter.printFragmentToString(query)) 117 | .isEqualTo(">") 118 | } 119 | 120 | } 121 | 122 | -------------------------------------------------------------------------------- /core/src/test/kotlin/org/kotlinq/graph/Traversal.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.graph 2 | 3 | import com.google.common.truth.Truth.assertThat 4 | import org.junit.Test 5 | import org.kotlinq.api.GraphVisitor 6 | import org.kotlinq.entities.TestFragmentBuilder.Companion.fragment 7 | import org.kotlinq.println 8 | import kotlin.coroutines.experimental.buildSequence 9 | 10 | class Traversal { 11 | 12 | @Test fun `every scalar in simple fragment is touched in traversal`() { 13 | 14 | val levelOneNames = mutableListOf() 15 | .and("one") 16 | .and("two") 17 | .and("three") 18 | .and("four") 19 | .and("five") 20 | 21 | val frag = fragment("Foo") { 22 | for (name in levelOneNames) scalar(name) 23 | } 24 | 25 | assertThat(frag.typeName).isEqualTo("Foo") 26 | 27 | assertThat(frag.graphQlInstance.properties.values.map { 28 | it.propertyInfo.graphQlName 29 | }).containsExactlyElementsIn(levelOneNames) 30 | 31 | GraphVisitor.builder() 32 | .onVisitField { field -> 33 | levelOneNames.removeAll { field.propertyInfo.graphQlName == it } 34 | }.build() 35 | .let(frag::traverse) 36 | 37 | assertThat(levelOneNames).isEmpty() 38 | } 39 | 40 | 41 | @Test fun `every fragment on enter is notified`() { 42 | 43 | val propertyNames = listOf("one", "two", "three") 44 | 45 | val fragments = buildSequence { 46 | for (i in 0..4) { 47 | fragment("Fragment$i") { 48 | propertyNames.forEach { scalar(it) } 49 | }.let { yield(it) } 50 | } 51 | }.withIndex().toList() 52 | 53 | val topLevelFragment = fragment { 54 | fragments.forEach { (i, fragment) -> 55 | bindFragment("fragment$i", fragment) 56 | } 57 | } 58 | 59 | val expectedTotalScalarCount = fragments.size * propertyNames.size + fragments.size 60 | var visitFieldCounter = 0 61 | var notifyFragmentCounter = 0 62 | var visitContextCounter = 0 63 | 64 | GraphVisitor.builder() 65 | .onVisitField { visitFieldCounter++ } 66 | .onVisitContext { visitContextCounter++ } 67 | .onNotifyEnter { notifyFragmentCounter++ >= 0 } 68 | .build() 69 | .let(topLevelFragment::traverse) 70 | 71 | assertThat(visitFieldCounter) 72 | .isEqualTo(expectedTotalScalarCount) 73 | assertThat(visitContextCounter) 74 | .isEqualTo(fragments.size + 1) 75 | assertThat(notifyFragmentCounter) 76 | .isEqualTo(fragments.size + 1) 77 | 78 | for ((_, fragment) in fragments) { 79 | assertThat(fragment in topLevelFragment).isTrue() 80 | } 81 | } 82 | 83 | 84 | } 85 | 86 | fun MutableList.and(element: T) = 87 | apply { add(element) } 88 | -------------------------------------------------------------------------------- /docs/contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Interested in contributing to the Material theme? Want to report a bug? Before 4 | you do, please read the following guidelines. 5 | 6 | ## Submission context 7 | 8 | ### Got a question or problem? 9 | 10 | For quick questions there's no need to open an issue as you can reach us on 11 | [gitter.im][1]. 12 | 13 | [1]: https://gitter.im/prestongarno/kotlinq 14 | 15 | ### Found a bug? 16 | 17 | If you found a bug in the source code, you can help us by submitting an issue 18 | to the [issue tracker][2] in our GitHub repository. Even better, you can submit 19 | a Pull Request with a fix. However, before doing so, please read the 20 | [submission guidelines][3]. 21 | 22 | [2]: https://github.com/prestongarno/kotlinq/issues 23 | [3]: #submission-guidelines 24 | 25 | ### Missing a feature? 26 | 27 | You can request a new feature by submitting an issue to our GitHub Repository. 28 | If you would like to implement a new feature, please submit an issue with a 29 | proposal for your work first, to be sure that it is of use for everyone, as 30 | the Material theme is highly opinionated. Please consider what kind of change 31 | it is: 32 | 33 | * For a **major feature**, first open an issue and outline your proposal so 34 | that it can be discussed. This will also allow us to better coordinate our 35 | efforts, prevent duplication of work, and help you to craft the change so 36 | that it is successfully accepted into the project. 37 | 38 | * **Small features and bugs** can be crafted and directly submitted as a Pull 39 | Request. However, there is no guarantee that your feature will make it into 40 | the master, as it's always a matter of opinion whether if benefits the 41 | overall functionality of the theme. 42 | 43 | ## Submission guidelines 44 | 45 | ### Submitting an issue 46 | 47 | Before you submit an issue, please search the issue tracker, maybe an issue for 48 | your problem already exists and the discussion might inform you of workarounds 49 | readily available. 50 | 51 | We want to fix all the issues as soon as possible, but before fixing a bug we 52 | need to reproduce and confirm it. In order to reproduce bugs we will 53 | systematically ask you to provide a minimal reproduction scenario using the 54 | custom issue template. Please stick to the issue template. 55 | 56 | Unfortunately we are not able to investigate / fix bugs without a minimal 57 | reproduction scenario, so if we don't hear back from you we may close the issue. 58 | 59 | ### Submitting a Pull Request (PR) 60 | 61 | Search GitHub for an open or closed PR that relates to your submission. You 62 | don't want to duplicate effort. If you do not find a related issue or PR, 63 | go ahead. 64 | 65 | 1. **Development**: Fork the project, set up the development environment, 66 | make your changes in a separate git branch and add descriptive messages to 67 | your commits. 68 | 69 | 2. **Build**: Before submitting a pull requests, build the theme. This is a 70 | mandatory requirement for your PR to objectValue accepted, as the theme should at 71 | all times be installable through GitHub. 72 | 73 | 3. **Pull Request**: After building the theme, commit the compiled output, push 74 | your branch to GitHub and send a PR to `mkdocs-material:master`. If we 75 | suggest changes, make the required updates, rebase your branch and push the 76 | changes to your GitHub repository, which will automatically update your PR. 77 | 78 | After your PR is merged, you can safely delete your branch and pull the changes 79 | from the main (upstream) repository. 80 | 81 | -------------------------------------------------------------------------------- /docs/core.md: -------------------------------------------------------------------------------- 1 | # Core (kotlinq-core) 2 | 3 | ## About 4 | 5 | This is the core module which provides a set of classes to compose & extend in functionality (i.e. for creating a DSL). Below is a quick overview so that you can connect the concepts when working with any of the DSL modules. Main concepts: 6 | 7 | 1. **Fragment**: Main interface for composing a GraphQL request 8 | 2. **Printer**: Highly configurable class for controlling the actual GraphQL request format. Don't like curly braces? Want to decorate GraphQL types in your request with custom properties? The Printer provides an easy-to-use interface via Builders for easily and safely doing things like this. 9 | 3. **Graph Visitor**: An interface for traversing a GraphQL request structure 10 | 11 | ## Fragment 12 | 13 | A Fragment, just like in the GraphQL spec, is essentially a 14 | 15 | * Type name 16 | * *Selection Set* 17 | 18 | The *Selection Set* is a declaration specifying fields from that type. They can be either: 19 | 20 | 1. Scalars (i.e. primitives, or your own custom ones) 21 | 2. Other fragments 22 | 23 | A Fragment is a recursively defined data structure. In kotlinq-core, fragments and their properties do not rely on JVM reflection, but rather uses its own, simplified reflection system to enforce type safety. 24 | 25 | ## Printer 26 | 27 | A GraphQL request printer is composed of a 28 | 29 | 1. PrintingConfiguration: Specification for the format of the request. Provided configurations are *pretty print* and *non-pretty* (single line, optimized for space) 30 | 2. MetaStrategy: Specification for intercepting & transforming the printing of the actual structure of a request 31 | 32 | Example usage: 33 | 34 | ``` 35 | val printer = Printer.fromConfiguration(PrintingConfiguration.PRETTY) 36 | .toBuilder() 37 | .metaStrategy(MetaStrategy.builder() 38 | .includeId() 39 | .includeTypename() 40 | .include("TODO") { fragment -> 41 | fragment.typeName == "User" 42 | } 43 | .build()) 44 | .indent("\t") 45 | .build() 46 | ``` 47 | 48 | The above `printer` shows how to format your requests as pretty-printed with tab indents, and also adds a property called "TODO" to a fragment selection set when the fragment is of type `User`. 49 | 50 | Note that fragments themselves can be easily formatted for standard day-to-day usage: 51 | 52 | ``` 53 | val fragment = ... 54 | val stringQuery = fragment.toGraphQl( 55 | pretty = true, 56 | idAndTypeName = true) 57 | ``` 58 | 59 | ## Graph Visitor 60 | 61 | This is an interface for analyzing fragment structure. Internally, the Printer and many other features use this interface under the hood. For example, here is the implementation of the Fragment's overload of the `in` operator (for checking if a fragment is defined within another one, e.g. `if (fragment in otherFragment) doSmth();`): 62 | 63 | 64 | ``` 65 | operator fun Fragment.contains(other: Fragment): Boolean { 66 | var result = false 67 | 68 | GraphVisitor.builder() 69 | .onNotifyEnter { 70 | other != it || let { 71 | result = true 72 | false // stop traversing, found match 73 | } 74 | } 75 | .build() 76 | .let(::traverse) 77 | 78 | return result 79 | } 80 | ``` 81 | -------------------------------------------------------------------------------- /docs/dsl.md: -------------------------------------------------------------------------------- 1 | # DSL Tutorial 2 | 3 | // TODO: Take a look at the tests in the DSL modules for examples of fragments & how to use them until then 4 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Kotlinq : a kotlin library for easy, type-safe GraphQL DSLs 2 | 3 | ## About 4 | 5 | This project is a type-safe Kotlin DSL for GraphQL. 6 | 7 | ## Project goals 8 | 9 | Kotlinq has the goal of making GraphQL easy to integrate in an existing project in a scalable manner 10 | while taking advantage of the both pragmatic and null/type-safe nature of Kotlin. 11 | 12 | ## What is GraphQL? 13 | 14 | GraphQL is a thoroughly defined [specification][2] which defines 15 | its architecture as structured data conforming to a simple, declarative type system. 16 | 17 | GraphQL is a natural way to describe ***data types*** and their ***relationships to other types*** 18 | (also known as a graph, where nodes and edges describe a confined set of data). You can learn more 19 | about it [here][3] 20 | 21 | The GraphQL type system provides null-safety which is quite convenient. 22 | 23 | ## What does the name *kotlinq* even mean? 24 | 25 | \[Kot\]lin \[L\]anguage \[I\]ntegrated \[Q\]ueries 26 | 27 | ## Development & issue tracker 28 | 29 | 30 | ## Getting started 31 | 32 | There are currently 2 DSLs: 33 | 34 | 1. `kotlinq-dsl`: Un-typed DSL for simply fetching data. Does not support converting to JVM types. 35 | 2. `kotlinq-jvm`: Somewhat type-safe DSL, experimental. Supports defining fragments based on kotlin classes, validating a GraphQL response, and also resolving the response to native JVM class instances. It is unfortunately restrictive and requires tight coupling to the library, so use with caution. 36 | 37 | The next section contains a run-down of shared concepts between the DSL modules. 38 | 39 | 40 | [1]: http://graphql.org 41 | [2]: http://facebook.github.io/graphql 42 | [3]: http://graphql.org/learn/ 43 | [4]: http://github.com/prestongarno/kotlinq 44 | [5]: http://github.com/prestongarno/kotlinq/kotlinq-gradle 45 | -------------------------------------------------------------------------------- /docs/license.md: -------------------------------------------------------------------------------- 1 | # License 2 | 3 | **MIT License** 4 | 5 | Copyright © 2016 - 2017 Preston Garno 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to 9 | deal in the Software without restriction, including without limitation the 10 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 11 | sell copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 23 | IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /dsl/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | dependencies { 3 | compile project(':kotlinq-core') 4 | } 5 | -------------------------------------------------------------------------------- /dsl/src/main/kotlin/Api.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.dsl 2 | 3 | import org.kotlinq.api.Fragment 4 | 5 | /** 6 | * Top-level query. 7 | * 8 | * @param name the name of the operation 9 | * @param selectionSet the query selectionSet 10 | * 11 | * @author prestongarno 12 | * @since 0.4.0 13 | */ 14 | fun query(name: String = "Query", selectionSet: SelectionSet): Fragment = 15 | GraphBuilder(selectionSet).build(name) 16 | 17 | 18 | /** 19 | * Creates a propertyNamed type definition 20 | * 21 | * @author prestongarno 22 | * @since 0.4.0 23 | */ 24 | fun fragment(typeName: String, block: SelectionSet) = query(typeName, block) 25 | 26 | -------------------------------------------------------------------------------- /dsl/src/main/kotlin/Common.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.dsl 2 | 3 | internal 4 | fun Any?.ignore() = Unit 5 | -------------------------------------------------------------------------------- /dsl/src/main/kotlin/DslExtensionScope.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.dsl 2 | 3 | import org.kotlinq.dsl.extensions.FreePropertyExtensionScope 4 | import org.kotlinq.dsl.extensions.ListDeclarationExtensionScope 5 | import org.kotlinq.dsl.extensions.NullabilityOperatorScope 6 | import org.kotlinq.dsl.extensions.StringExtensionScope 7 | 8 | @GraphQlDslObject 9 | interface DslExtensionScope : 10 | NullabilityOperatorScope, 11 | FreePropertyExtensionScope, 12 | StringExtensionScope, 13 | ListDeclarationExtensionScope { 14 | 15 | /** 16 | * Mark a scalar type hint as being nullable: 17 | * 18 | * ``` 19 | * fragment("Person") { 20 | * "name"(!string) 21 | * } 22 | * ``` 23 | */ 24 | operator fun ScalarSymbol.not() = this to true 25 | } 26 | 27 | -------------------------------------------------------------------------------- /dsl/src/main/kotlin/FragmentContextBuilder.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("UNUSED_PARAMETER") 2 | 3 | package org.kotlinq.dsl 4 | 5 | import org.kotlinq.api.Fragment 6 | import org.kotlinq.api.Kind 7 | 8 | 9 | @GraphQlDslObject 10 | class FragmentContextBuilder internal constructor( 11 | var fieldTypeName: String = "") { 12 | 13 | 14 | 15 | private var isCollection = false 16 | 17 | private val fragments = mutableSetOf() 18 | 19 | fun on(typeName: String, block: SelectionSet) { 20 | fragments += GraphBuilder(block).build(typeName) 21 | } 22 | 23 | val on = FragmentPrefix() 24 | 25 | 26 | internal 27 | fun flagField(isCollection: Boolean = true) { 28 | this.isCollection = isCollection 29 | } 30 | 31 | private 32 | fun toFragmentInfo(): FragmentInfo = FragmentInfo( 33 | fieldTypeName, 34 | isCollection, 35 | fragments.toSet()) 36 | 37 | /** 38 | * This exists because of syntax limitations 39 | */ 40 | internal class FragmentInfo( 41 | typeName: String, 42 | isCollection: Boolean, 43 | val fragments: Set) { 44 | 45 | val typeKind = Kind.typeNamed(typeName).let { 46 | if (isCollection) it.asList() else it 47 | } 48 | } 49 | 50 | inner class FragmentPrefix internal constructor(){ 51 | operator fun rangeTo(fragment: Fragment) { 52 | this@FragmentContextBuilder.fragments += fragment 53 | } 54 | operator fun rangeTo(fragments: Iterable) { 55 | fragments.forEach { 56 | this@FragmentContextBuilder.fragments += it 57 | } 58 | } 59 | operator fun rangeTo(fragments: Array) = 60 | rangeTo(fragments.toList()) 61 | } 62 | 63 | 64 | companion object { 65 | 66 | 67 | internal fun fromBlock(block: FragmentSelection) = 68 | FragmentContextBuilder().apply(block) 69 | .let { if (it.fragments.isEmpty()) null else it.toFragmentInfo() } 70 | } 71 | } -------------------------------------------------------------------------------- /dsl/src/main/kotlin/FragmentSelection.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.dsl 2 | 3 | typealias FragmentSelection = FragmentContextBuilder.() -> Unit -------------------------------------------------------------------------------- /dsl/src/main/kotlin/FreeProperty.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.dsl.fields 2 | 3 | import org.kotlinq.api.Kind 4 | import org.kotlinq.dsl.Leaf 5 | import org.kotlinq.dsl.Node 6 | import org.kotlinq.dsl.ScalarSymbol 7 | 8 | 9 | data class FreeProperty internal constructor( 10 | val name: String, 11 | val arguments: Map = emptyMap(), 12 | private var isNullable: Boolean = false) { 13 | 14 | internal fun nullability(isNullable: Boolean = false) = 15 | apply { this.isNullable = isNullable } 16 | 17 | internal 18 | fun asLeaf(symbol: ScalarSymbol): Leaf = 19 | Leaf(name, arguments, typeKindFromInstance(symbol.kind)) 20 | 21 | 22 | internal 23 | fun asNode(kind: Kind): Node = 24 | Node(name, arguments, typeKindFromInstance(kind)) 25 | 26 | private fun typeKindFromInstance(kind: Kind) = 27 | if (isNullable) kind.asNullable() else kind 28 | 29 | internal 30 | fun isNullable() = isNullable 31 | 32 | } 33 | 34 | -------------------------------------------------------------------------------- /dsl/src/main/kotlin/GraphBuilder.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.dsl 2 | 3 | import org.kotlinq.api.Fragment 4 | import org.kotlinq.api.Kotlinq 5 | 6 | 7 | internal 8 | class GraphBuilder(private val definition: SelectionSet) { 9 | 10 | fun build(typeName: String): Fragment { 11 | val context = Kotlinq.newContextBuilder() 12 | TypeBuilder({ context.register(it).ignore() }).apply(definition) 13 | return context.build(typeName) 14 | } 15 | 16 | 17 | } 18 | 19 | -------------------------------------------------------------------------------- /dsl/src/main/kotlin/GraphComponent.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.dsl 2 | 3 | import org.kotlinq.api.Adapter 4 | import org.kotlinq.api.Fragment 5 | import org.kotlinq.api.Kotlinq 6 | import org.kotlinq.api.ParsingAdapter 7 | import org.kotlinq.api.PropertyInfo 8 | import org.kotlinq.api.Kind 9 | 10 | sealed class GraphComponent( 11 | val name: String, 12 | val arguments: Map, 13 | val kind: Kind) 14 | 15 | 16 | class Node internal constructor( 17 | name: String, 18 | arguments: Map, 19 | kind: Kind) 20 | : GraphComponent(name, arguments, kind) { 21 | 22 | /** 23 | * Isolating all [kotlin.reflect] usages in DSL for eventually targeting Javascript 24 | */ 25 | private 26 | fun propertyInfo(kind: Kind) = 27 | PropertyInfo.propertyNamed(name) 28 | .arguments(arguments) 29 | .typeKind(kind) 30 | .build() 31 | 32 | /** 33 | * Fragment scope has an empty name for now, 34 | * interface enforcement is tricky when using strings only 35 | */ 36 | internal fun withFragmentSelection(fragments: Set, info: PropertyInfo = propertyInfo(kind)): Adapter = 37 | Kotlinq.adapterService.fragmentProperty(info, fragments) 38 | 39 | internal fun withFragmentSelection(info: FragmentContextBuilder.FragmentInfo): Adapter = 40 | withFragmentSelection(info.fragments, propertyInfo(info.typeKind)) 41 | 42 | internal fun withDefinition(definition: Fragment): Adapter = Kotlinq.adapterService 43 | .instanceProperty(propertyInfo(kind), definition) 44 | } 45 | 46 | class Leaf( 47 | name: String, 48 | arguments: Map, 49 | kind: Kind) 50 | : GraphComponent(name, arguments, kind) { 51 | 52 | internal fun toAdapter(): ParsingAdapter = Kotlinq.adapterService.scalarAdapters 53 | .newAdapter(PropertyInfo.propertyNamed(name) 54 | .typeKind(kind) 55 | .arguments(arguments) 56 | .build()) 57 | 58 | fun asList(): Leaf = Leaf(name, arguments, kind.asList()) 59 | fun asNullable(): Leaf = Leaf(name, arguments, kind.asNullable()) 60 | 61 | } 62 | 63 | -------------------------------------------------------------------------------- /dsl/src/main/kotlin/GraphQlDslObject.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.dsl 2 | 3 | @Retention(AnnotationRetention.RUNTIME) 4 | @Target(AnnotationTarget.CLASS) 5 | @DslMarker 6 | annotation class GraphQlDslObject -------------------------------------------------------------------------------- /dsl/src/main/kotlin/ScalarSymbol.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.dsl 2 | 3 | import org.kotlinq.api.Kind 4 | 5 | sealed class ScalarSymbol(internal val kind: Kind) { 6 | 7 | init { require(kind.isScalar) } 8 | 9 | val typeName = kind.isScalar 10 | 11 | internal 12 | object StringSymbol : ScalarSymbol(Kind.Scalar._String) 13 | 14 | internal 15 | object IntSymbol : ScalarSymbol(Kind.Scalar._Int) 16 | 17 | internal 18 | object BooleanSymbol : ScalarSymbol(Kind.Scalar._Boolean) 19 | 20 | internal 21 | object FloatSymbol : ScalarSymbol(Kind.Scalar._Float) 22 | } 23 | 24 | 25 | -------------------------------------------------------------------------------- /dsl/src/main/kotlin/SelectionSet.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.dsl 2 | 3 | 4 | typealias SelectionSet = TypeBuilder.() -> Unit 5 | 6 | -------------------------------------------------------------------------------- /dsl/src/main/kotlin/TypeBuilder.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.dsl 2 | 3 | import org.kotlinq.api.Adapter 4 | import org.kotlinq.api.Fragment 5 | import org.kotlinq.api.Kind 6 | import org.kotlinq.dsl.fields.FreeProperty 7 | 8 | @GraphQlDslObject 9 | class TypeBuilder internal constructor( 10 | private val bindableContext: (Adapter) -> Unit 11 | ) : DslExtensionScope { 12 | 13 | override fun String.listOf(nullTypeSymbol: Pair) = 14 | FreeProperty(this) 15 | .nullability(nullTypeSymbol.second) 16 | .asLeaf(nullTypeSymbol.first) 17 | .asList() 18 | .toAdapter() 19 | .let(bindableContext) 20 | 21 | override fun FreeProperty.listOf(definition: Fragment) = 22 | asNode(Kind.typeNamed(definition.typeName).asList()) 23 | .withDefinition(definition) 24 | .let(bindableContext) 25 | 26 | override fun String.invoke( 27 | typeSymbol: Pair, 28 | arguments: Map 29 | ) = FreeProperty(this, arguments, typeSymbol.second) 30 | .asLeaf(typeSymbol.first) 31 | .toAdapter() 32 | .let(bindableContext) 33 | 34 | override fun FreeProperty.on(context: Fragment) { 35 | bindableContext(asNode(Kind.typeNamed(context.typeName)) 36 | .withDefinition(context)) 37 | } 38 | 39 | 40 | override fun def(typeName: String, block: SelectionSet): Fragment = 41 | fragment(typeName, block) 42 | 43 | 44 | override fun String.invoke(vararg arguments: Pair): FreeProperty = 45 | FreeProperty(this, arguments.toMap()) 46 | 47 | override fun FreeProperty.rangeTo(block: FragmentSelection) { 48 | FragmentContextBuilder.fromBlock(block)?.also { 49 | asNode(it.typeKind).withFragmentSelection(it).let(bindableContext) 50 | } 51 | } 52 | 53 | override fun String.on(definition: Fragment) = 54 | FreeProperty(this) 55 | .asNode(Kind.typeNamed(definition.typeName)) 56 | .withDefinition(definition) 57 | .let(bindableContext) 58 | } 59 | 60 | -------------------------------------------------------------------------------- /dsl/src/main/kotlin/org/kotlinq/dsl/extensions/FreePropertyExtensionScope.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.dsl.extensions 2 | 3 | import org.kotlinq.api.Fragment 4 | import org.kotlinq.dsl.FragmentSelection 5 | import org.kotlinq.dsl.SelectionSet 6 | import org.kotlinq.dsl.fields.FreeProperty 7 | 8 | 9 | interface FreePropertyExtensionScope { 10 | 11 | /** 12 | * Convenience method for inline type definitions 13 | * 14 | * Example: 15 | * 16 | * ``` 17 | * query { 18 | * "search"("text" to "hello") on def("SearchResultConnection") { 19 | * "resultCount"(integer) 20 | * } 21 | * } 22 | * ``` 23 | * 24 | */ 25 | fun def(typeName: String, block: SelectionSet): Fragment 26 | 27 | 28 | /** 29 | * Call on a [FreeProperty] declaration to define 30 | * with fragment spread operations on propertyNamed types 31 | * 32 | * Example: 33 | * 34 | * ``` 35 | * query { 36 | * nodes("first" to 10) .. { 37 | * on("Human") { 38 | * "name"(string) 39 | * } 40 | * on("Robot") { 41 | * "modelNumber"(integer) 42 | * } 43 | * } 44 | * } 45 | * ``` 46 | * 47 | */ 48 | operator fun FreeProperty.rangeTo(block: FragmentSelection) 49 | 50 | /** 51 | * Call on a [FreeProperty] declaration to define 52 | * with fragment spread operations on propertyNamed types 53 | * 54 | * Example: 55 | * 56 | * ``` 57 | * query { 58 | * "nodes" .. { 59 | * on("Human") { 60 | * "name"(string) 61 | * } 62 | * on("Robot") { 63 | * "modelNumber"(integer) 64 | * } 65 | * } 66 | * } 67 | * ``` 68 | * 69 | */ 70 | operator fun String.rangeTo(block: FragmentSelection) = 71 | FreeProperty(this).rangeTo(block) 72 | 73 | /** 74 | * Use this when the GraphQl field is a *concrete* type 75 | * 76 | * Example: 77 | * 78 | * ``` 79 | * query { 80 | * "search"("text" to "hello") on searchResult() 81 | * } 82 | * 83 | * 84 | * fun searchResult() = 85 | * fragment("SearchResultConnection") { 86 | * "totalCount"(integer) 87 | * } 88 | * ``` 89 | * 90 | */ 91 | infix fun FreeProperty.on(context: Fragment) 92 | } 93 | -------------------------------------------------------------------------------- /dsl/src/main/kotlin/org/kotlinq/dsl/extensions/ListDeclarationExtensionScope.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.dsl.extensions 2 | 3 | import org.kotlinq.api.Fragment 4 | import org.kotlinq.dsl.FragmentSelection 5 | import org.kotlinq.dsl.ScalarSymbol 6 | import org.kotlinq.dsl.fields.FreeProperty 7 | 8 | 9 | interface ListDeclarationExtensionScope { 10 | 11 | /** 12 | * Declare a field returning a list of scalars 13 | * 14 | * ``` 15 | * query { 16 | * "field" listOf string 17 | * } 18 | * ``` 19 | */ 20 | infix fun String.listOf(typeSymbol: ScalarSymbol) = this listOf (typeSymbol to false) 21 | 22 | /** 23 | * Declare a field returning a list of objects 24 | * 25 | * ``` 26 | * query { 27 | * "field" listOf !string 28 | * } 29 | * ``` 30 | */ 31 | infix fun String.listOf(nullTypeSymbol: Pair) 32 | 33 | /** 34 | * Declare a field returning a list of objects 35 | * 36 | * ``` 37 | * query { 38 | * "node"("id" to 1234) listOf humanFragment() 39 | * } 40 | * ``` 41 | */ 42 | infix fun FreeProperty.listOf(definition: Fragment) 43 | 44 | /** 45 | * Declare a field returning a list of multiple fragment types (i.e. union or interface fields) 46 | * 47 | * ``` 48 | * query { 49 | * "node"("id" to 1234) listOf { 50 | * on..humanFragment() 51 | * on..robotFragment() 52 | * } 53 | * } 54 | * ``` 55 | */ 56 | fun listOf(definitions: FragmentSelection): FragmentSelection = { 57 | this.apply(definitions) 58 | this.flagField(isCollection = true) 59 | } 60 | } -------------------------------------------------------------------------------- /dsl/src/main/kotlin/org/kotlinq/dsl/extensions/NullabilityOperatorScope.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.dsl.extensions 2 | 3 | import org.kotlinq.dsl.fields.FreeProperty 4 | 5 | 6 | interface NullabilityOperatorScope { 7 | /** 8 | * Hint that a type may be null 9 | * 10 | * ``` 11 | * query { 12 | * !"search" on def("Human")..{ 13 | * "name"(string) 14 | * } 15 | * } 16 | * ``` 17 | */ 18 | operator fun String.not(): FreeProperty = FreeProperty(this, isNullable = true) 19 | 20 | /** 21 | * Hint that a type may be null 22 | * 23 | * ``` 24 | * query { 25 | * !"search"("type" to Type.HUMAN) on def("Human") { 26 | * "name"(string) 27 | * } 28 | * } 29 | * ``` 30 | */ 31 | operator fun FreeProperty.not(): FreeProperty = 32 | apply { nullability() } 33 | } -------------------------------------------------------------------------------- /dsl/src/main/kotlin/org/kotlinq/dsl/extensions/StringExtensionScope.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.dsl.extensions 2 | 3 | import org.kotlinq.api.Fragment 4 | import org.kotlinq.dsl.ScalarSymbol 5 | import org.kotlinq.dsl.fields.FreeProperty 6 | 7 | 8 | interface StringExtensionScope { 9 | 10 | /** kotlin.String type hint */ 11 | val string: ScalarSymbol get() = ScalarSymbol.StringSymbol 12 | /** kotlin.Int type hint */ 13 | val integer: ScalarSymbol get() = ScalarSymbol.IntSymbol 14 | /** kotlin.Boolean type hint */ 15 | val boolean: ScalarSymbol get() = ScalarSymbol.BooleanSymbol 16 | /** kotlin.Float type hint */ 17 | val float: ScalarSymbol get() = ScalarSymbol.FloatSymbol 18 | 19 | /** 20 | * Example: 21 | * 22 | * ``` 23 | * fragment("Person") { 24 | * "name"(string, mapOf("charset" to Charsets.UTF8)) 25 | * } 26 | * ``` 27 | */ 28 | operator fun String.invoke( 29 | typeSymbol: ScalarSymbol, 30 | arguments: Map = emptyMap() 31 | ) = invoke(typeSymbol to false, arguments) 32 | 33 | /** 34 | * Example: 35 | * 36 | * ``` 37 | * fragment("Person") { 38 | * "name"(string, "charset" to Charsets.UTF8) 39 | * } 40 | * ``` 41 | */ 42 | operator fun String.invoke( 43 | typeSymbol: ScalarSymbol, 44 | vararg arguments: Pair) = 45 | invoke(typeSymbol to false, *arguments) 46 | 47 | /** 48 | * Example: 49 | * 50 | * ``` 51 | * fragment("Person") { 52 | * "name"(!string, mapOf("charset" to Charsets.UTF8)) 53 | * } 54 | * ``` 55 | */ 56 | operator fun String.invoke( 57 | typeSymbol: Pair, 58 | arguments: Map = emptyMap()) 59 | 60 | /** 61 | * Example: 62 | * 63 | * ``` 64 | * fragment("Person") { 65 | * "name"(!string, "charset" to Charsets.UTF8) 66 | * } 67 | * ``` 68 | */ 69 | operator fun String.invoke( 70 | typeSymbol: Pair, 71 | vararg arguments: Pair) = 72 | invoke(typeSymbol, arguments.toMap()) 73 | 74 | /** 75 | * For arguments on fields returning objects 76 | * 77 | * 78 | * Example: 79 | * 80 | * 81 | * ``` 82 | * query { 83 | * "nodes"(mapOf("first" to 10)) on fragmentDefinition() 84 | * } 85 | * ``` 86 | */ 87 | operator fun String.invoke( 88 | arguments: Map = emptyMap() 89 | ): FreeProperty = FreeProperty(this, arguments) 90 | 91 | /** 92 | * For arguments on fields returning objects 93 | * 94 | * 95 | * Example: 96 | * 97 | * 98 | * ``` 99 | * query { 100 | * "nodes"("first" to 10) on fragmentDefinition() 101 | * } 102 | * ``` 103 | */ 104 | operator fun String.invoke(vararg arguments: Pair): FreeProperty = 105 | FreeProperty(this, arguments.toMap()) 106 | 107 | /** 108 | * For arguments on fields returning objects 109 | * 110 | * 111 | * Example: 112 | * 113 | * 114 | * ``` 115 | * query { 116 | * "nodes"(mapOf("first" to 10)) on fragmentDefinition() 117 | * } 118 | * ``` 119 | */ 120 | infix fun String.on(definition: Fragment) 121 | } -------------------------------------------------------------------------------- /dsl/src/test/kotlin/Exp.kt: -------------------------------------------------------------------------------- 1 | import org.junit.Test 2 | import kotlin.reflect.KFunction0 3 | 4 | 5 | class TestContext { 6 | 7 | val superSecretHashMap = mutableMapOf() 8 | 9 | operator fun KFunction0<(key: TestContext) -> Pair>.not() { 10 | this.call().invoke(this@TestContext).also { 11 | superSecretHashMap[it.first] = it.second 12 | } 13 | } 14 | 15 | operator fun KFunction0<(key: TestContext) -> Pair>.unaryMinus() { 16 | this().invoke(this@TestContext).also { 17 | superSecretHashMap[it.first] = it.second 18 | } 19 | } 20 | } 21 | 22 | fun String.extensionFunction(): (key: TestContext) -> Pair = { 23 | this to this::extensionFunction.name 24 | } 25 | 26 | fun dslFoo(block: TestContext.() -> Unit): TestContext { 27 | return TestContext().apply(block) 28 | } 29 | 30 | class Exp { 31 | 32 | // TODO this is how to express nullability with symbols, higher order extension functions:) 33 | @Test fun testExtAndExtProperties() { 34 | dslFoo { 35 | !"Hello"::extensionFunction 36 | -"World"::extensionFunction 37 | } 38 | .let { 39 | require(it.superSecretHashMap["Hello"] == "extensionFunction") 40 | require(it.superSecretHashMap["World"] == "extensionFunction") 41 | } 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /dsl/src/test/kotlin/FragmentContains.kt: -------------------------------------------------------------------------------- 1 | import com.google.common.truth.Truth.assertThat 2 | import org.junit.Test 3 | import org.kotlinq.dsl.fragment 4 | import org.kotlinq.dsl.query 5 | 6 | class FragmentContains { 7 | 8 | val query = query { 9 | "first" on def("First") { 10 | "second" on def("Second") { 11 | "third" on def("Third") { 12 | "fourth" on def("Fourth") { 13 | "Hello"(!string) 14 | } 15 | } 16 | } 17 | } 18 | } 19 | 20 | @Test fun `nested deep contains`() { 21 | 22 | val inner = fragment("Fourth") { 23 | "Hello"(!string) 24 | } 25 | 26 | assertThat(inner in query) 27 | .isTrue() 28 | } 29 | 30 | 31 | @Test fun `nested deep different not contains`() { 32 | val shouldntBeInner = fragment("Foo") { 33 | "bar"(string) 34 | } 35 | 36 | assertThat(shouldntBeInner in query).isFalse() 37 | } 38 | 39 | 40 | @Test fun `nested deep different not contains 2`() { 41 | val shouldntBeInner = fragment("Fourth") { 42 | "Hello"(string) // <-- Not nullable 43 | } 44 | 45 | assertThat(shouldntBeInner in query).isFalse() 46 | } 47 | 48 | 49 | @Test fun `nested deep different not contains 3`() { 50 | val subFragment = fragment("Second") { 51 | "third" on def("Third") { 52 | "fourth" on def("Fourth") { 53 | "Hello"(!string) 54 | } 55 | } 56 | } 57 | 58 | val subSubFragment = fragment("Third") { 59 | "fourth" on def("Fourth") { 60 | "Hello"(!string) 61 | } 62 | } 63 | 64 | val bottomFragment = fragment("Fourth") { 65 | "Hello"(!string) 66 | } 67 | 68 | assertThat(bottomFragment in subSubFragment).isTrue() 69 | assertThat(subSubFragment in query).isTrue() 70 | assertThat(subFragment in query).isTrue() 71 | } 72 | } 73 | 74 | -------------------------------------------------------------------------------- /gradle-compiler/README.md: -------------------------------------------------------------------------------- 1 | Not currently used, but if you're looking for a gradle plugin for parsing GraphQL schemas, look no further. 2 | -------------------------------------------------------------------------------- /gradle-compiler/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | group rootProject.group 3 | 4 | apply plugin: 'java' 5 | 6 | repositories { 7 | jcenter() 8 | } 9 | 10 | dependencies { 11 | compile dep.antlr4 12 | compile gradleApi() 13 | compile project(":kotlinq-core") 14 | compile dep.kotlinpoet 15 | testCompile dep.kotlinCompilerEmbeddable 16 | testCompile gradleTestKit() 17 | } 18 | 19 | -------------------------------------------------------------------------------- /gradle-compiler/src/main/java/com/prestongarno/kotlinq/core/org/antlr4/base/GraphQLBaseSchema.tokens: -------------------------------------------------------------------------------- 1 | LCURLY=1 2 | WS=2 3 | NAME=3 4 | TYPE_DEC=4 5 | COMMENT_ENTER=5 6 | COLON=6 7 | WORD=7 8 | BLOCK=8 9 | NEWLINE=9 10 | TYPE_LIT=10 11 | RCURLY=11 12 | OTHER=12 13 | UNION_WS=13 14 | EQ=14 15 | WS_UNION=15 16 | OR=16 17 | WS_SCALAR=17 18 | WS_TYPE=18 19 | IMPL_=19 20 | WS_TYPE_CTX=20 21 | COMMA=21 22 | BREAK=22 23 | STR=23 24 | '#'=5 25 | ':'=6 26 | 'implements'=19 27 | ','=21 28 | -------------------------------------------------------------------------------- /gradle-compiler/src/main/java/com/prestongarno/kotlinq/org/antlr4/definitions/GraphQLSchema.tokens: -------------------------------------------------------------------------------- 1 | T__0=1 2 | T__1=2 3 | T__2=3 4 | T__3=4 5 | T__4=5 6 | T__5=6 7 | T__6=7 8 | T__7=8 9 | T__8=9 10 | Name=10 11 | BooleanValue=11 12 | NullValue=12 13 | Null=13 14 | IntValue=14 15 | FloatValue=15 16 | Sign=16 17 | IntegerPart=17 18 | NonZeroDigit=18 19 | ExponentPart=19 20 | Digit=20 21 | StringValue=21 22 | Ignored=22 23 | ':'=1 24 | '('=2 25 | ')'=3 26 | '['=4 27 | ']'=5 28 | '!'=6 29 | '='=7 30 | '{'=8 31 | '}'=9 32 | 'null'=13 33 | '-'=16 34 | -------------------------------------------------------------------------------- /gradle-compiler/src/main/java/com/prestongarno/kotlinq/org/antlr4/definitions/GraphQLSchemaLexer.tokens: -------------------------------------------------------------------------------- 1 | T__0=1 2 | T__1=2 3 | T__2=3 4 | T__3=4 5 | T__4=5 6 | T__5=6 7 | T__6=7 8 | T__7=8 9 | T__8=9 10 | Name=10 11 | BooleanValue=11 12 | NullValue=12 13 | Null=13 14 | IntValue=14 15 | FloatValue=15 16 | Sign=16 17 | IntegerPart=17 18 | NonZeroDigit=18 19 | ExponentPart=19 20 | Digit=20 21 | StringValue=21 22 | Ignored=22 23 | ':'=1 24 | '('=2 25 | ')'=3 26 | '['=4 27 | ']'=5 28 | '!'=6 29 | '='=7 30 | '{'=8 31 | '}'=9 32 | 'null'=13 33 | '-'=16 34 | -------------------------------------------------------------------------------- /gradle-compiler/src/main/kotlin/com/prestongarno/kotlinq/compiler/ArgBuilderDef.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Preston Garno 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package com.prestongarno.kotlinq.compiler 19 | 20 | import com.prestongarno.kotlinq.core.ArgBuilder 21 | import com.prestongarno.kotlinq.core.ArgumentSpec 22 | import com.squareup.kotlinpoet.ClassName 23 | import com.squareup.kotlinpoet.CodeBlock 24 | import com.squareup.kotlinpoet.FunSpec 25 | import com.squareup.kotlinpoet.ParameterizedTypeName 26 | import com.squareup.kotlinpoet.TypeSpec 27 | 28 | 29 | fun notNullDelegateCode(arg: ScopedSymbol, targetName: String = "arguments"): CodeBlock { 30 | val paramType = arg.type.name.asTypeName().let { 31 | if (arg.isList) ParameterizedTypeName.get(ClassName("kotlin.collections", "List"), it) else it 32 | } 33 | return CodeBlock.of("$targetName.notNull<%T>(\"${arg.name}\", ${arg.name})", paramType) 34 | } 35 | 36 | internal class ArgumentSpecDef(val field: FieldDefinition, val context: ScopedDeclarationType) : KotlinTypeElement { 37 | 38 | val isInterface = field.isAbstract 39 | 40 | val name = (field.name[0].toUpperCase() + (if (field.name.length > 1) field.name.substring(1) else "") + "Args").let { 41 | if (isInterface) it.prepend("Base") else it 42 | } 43 | 44 | override fun toKotlin(): TypeSpec = if (isInterface) toInterface() else toClass() 45 | } 46 | 47 | private fun ArgumentSpecDef.toInterface(): TypeSpec = 48 | TypeSpec.interfaceBuilder(name) 49 | .addSuperinterface(ArgumentSpec::class) 50 | .addProperties( 51 | field.arguments.map(ArgumentDefinition::toKotlin) 52 | ).build() 53 | 54 | private fun ArgumentSpecDef.toClass(): TypeSpec = TypeSpec.classBuilder(name).apply { 55 | superclass(ArgBuilder::class) 56 | addSuperinterfaces(field.inheritsFrom.map { 57 | it.symtab[field.name]!!.name 58 | ClassName("", it.name).nestedClass("Base" + name) 59 | }).addProperties(field.arguments.map(ArgumentDefinition::toKotlin)) 60 | .build() 61 | // add constructor for non-nullable input arg arguments 62 | if (field.arguments.find { !it.nullable } != null) primaryConstructor(FunSpec.constructorBuilder() 63 | .addParameters(field.arguments.filterNot(ArgumentDefinition::nullable) 64 | .map { it.asParameter.toKotlin() }).build()) 65 | 66 | }.build() 67 | -------------------------------------------------------------------------------- /gradle-compiler/src/main/kotlin/com/prestongarno/kotlinq/compiler/Builtins.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Preston Garno 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package com.prestongarno.kotlinq.compiler 19 | 20 | enum class ScalarSymbols(val typeDef: ScalarType) { 21 | INT(IntType), 22 | BOOLEAN(BooleanType), 23 | FLOAT(FloatType), 24 | STRING(StringType); 25 | 26 | companion object { 27 | @JvmStatic val named: Map by lazy { 28 | values().map { Pair(it.typeDef.name, it) }.toMap() 29 | } 30 | } 31 | } 32 | 33 | internal fun String.prepend(prefix: String) = prefix + this -------------------------------------------------------------------------------- /gradle-compiler/src/main/kotlin/com/prestongarno/kotlinq/compiler/KotlinLangElement.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Preston Garno 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package com.prestongarno.kotlinq.compiler 19 | 20 | import com.squareup.kotlinpoet.ParameterSpec 21 | import com.squareup.kotlinpoet.PropertySpec 22 | import com.squareup.kotlinpoet.TypeSpec 23 | 24 | interface KotlinLangElement { 25 | fun toKotlin(): T 26 | } 27 | 28 | interface KotlinTypeElement : KotlinLangElement 29 | 30 | interface KotlinPropertyElement : KotlinLangElement 31 | 32 | interface KotlinParameterElement : KotlinLangElement 33 | 34 | 35 | interface NamedElement { 36 | val name: String 37 | } 38 | 39 | interface SymbolElement : NamedElement { 40 | val typeName: String 41 | } 42 | -------------------------------------------------------------------------------- /gradle-compiler/src/main/kotlin/com/prestongarno/kotlinq/compiler/Schema.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Preston Garno 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package com.prestongarno.kotlinq.compiler 19 | 20 | sealed class Schema 21 | 22 | class StringSchema(val source: String) : Schema() 23 | 24 | class FileSchema(val path: String) : Schema() 25 | 26 | -------------------------------------------------------------------------------- /gradle-compiler/src/main/resources/GraphQLBaseSchema.g4: -------------------------------------------------------------------------------- 1 | 2 | lexer grammar GraphQLBaseSchema; 3 | 4 | UNION_DEF: 'union' -> 5 | type(TYPE_LIT), pushMode(UNION); 6 | TYPE_DEF: 'type' -> 7 | type(TYPE_LIT), pushMode(TYPE); 8 | SCALAR_DEF: 'scalar' -> 9 | type(TYPE_LIT), pushMode(DEF_NAME_CTX); 10 | INTERFACE_DEF: 'interface' -> 11 | type(TYPE_LIT), pushMode(DEF_NAME_CTX); 12 | ENUM_DEF: 'enum' -> 13 | type(TYPE_LIT), pushMode(DEF_NAME_CTX); 14 | INPUT_DEF: 'input' -> 15 | type(TYPE_LIT), pushMode(DEF_NAME_CTX); 16 | 17 | LCURLY: '{' -> pushMode(CODE_0); 18 | 19 | WS: [ \t\r\n] -> skip; 20 | NAME: [a-zA-Z][a-zA-Z0-9]*; 21 | TYPE_DEC: [a-zA-Z][a-zA-Z0-9]*; 22 | 23 | COMMENT_ENTER: '#' -> skip, pushMode(COMMENT); 24 | COLON : ':'; 25 | WORD : ('a'..'z' | 'A'..'Z')+; 26 | BLOCK: (.)+?; 27 | NEWLINE : ('\r'? '\n' | '\r') -> skip; 28 | TYPE_LIT: [a-z]; 29 | 30 | mode CODE_0; 31 | 32 | CODE_0_LCURLY: '{' -> type(OTHER), pushMode(TYPE_SCOPE); 33 | RCURLY: '}' -> popMode; // Close for LCURLY 34 | CODE_0_OTHER: ~[{}]+ -> type(OTHER); 35 | 36 | mode TYPE_SCOPE; 37 | 38 | CODE_N_LCURLY: '{' -> type(OTHER), pushMode(CODE_0); 39 | CODE_N_RCURLY: '}' -> type(RCURLY), popMode, pushMode(DEFAULT_MODE); 40 | OTHER: ~[{}]+; 41 | 42 | mode UNION; 43 | 44 | UNION_NAME: NAME -> type(TYPE_DEC); 45 | UNION_WS: [ \t\r\n] -> skip; 46 | EQ: '=' -> skip, popMode, pushMode(UNION_CONTEXT); 47 | 48 | mode UNION_CONTEXT; 49 | 50 | WS_UNION: [ \t\r\n] -> skip; 51 | OR: WS*?'|'WS*? -> skip; 52 | BODY: NAME (OR NAME)+ -> type(BLOCK), popMode; 53 | 54 | mode DEF_NAME_CTX; 55 | 56 | WS_SCALAR: WS -> skip; 57 | SCALAR_NAME: NAME -> type(TYPE_DEC), popMode; 58 | 59 | mode TYPE; 60 | 61 | WS_TYPE: WS -> skip; 62 | IMPL_: 'implements' -> skip, pushMode(TYPE_IMPL_CONTEXT); 63 | TYPE_NAME: NAME -> type(TYPE_DEC); 64 | EXIT_TYPE: LCURLY -> type(LCURLY), popMode, pushMode(TYPE_SCOPE); 65 | 66 | mode TYPE_IMPL_CONTEXT; 67 | 68 | WS_TYPE_CTX: WS -> skip; 69 | COMMA: ',' -> skip; 70 | SUPERTYPE_NAME: NAME -> type(NAME); 71 | EXIT_IMPL: LCURLY -> type(LCURLY), popMode, pushMode(TYPE_SCOPE); 72 | 73 | 74 | mode COMMENT; 75 | 76 | BREAK: [\r\n] -> skip, popMode; 77 | BRACKET_INCLUDE: '{' -> type(STR); 78 | BRACKET_INCLUDE_CLOSE: '}' -> type(STR); 79 | STR: ~[\r\n]+ -> skip; 80 | -------------------------------------------------------------------------------- /gradle-compiler/src/main/resources/GraphQLBaseSchema.tokens: -------------------------------------------------------------------------------- 1 | LCURLY=1 2 | WS=2 3 | NAME=3 4 | TYPE_DEC=4 5 | COMMENT_ENTER=5 6 | COLON=6 7 | WORD=7 8 | BLOCK=8 9 | NEWLINE=9 10 | TYPE_LIT=10 11 | RCURLY=11 12 | OTHER=12 13 | UNION_WS=13 14 | EQ=14 15 | WS_UNION=15 16 | OR=16 17 | WS_SCALAR=17 18 | WS_TYPE=18 19 | IMPL_=19 20 | WS_TYPE_CTX=20 21 | COMMA=21 22 | BREAK=22 23 | STR=23 24 | '#'=5 25 | ':'=6 26 | 'implements'=19 27 | ','=21 28 | -------------------------------------------------------------------------------- /gradle-compiler/src/main/resources/GraphQLSchema.g4: -------------------------------------------------------------------------------- 1 | grammar GraphQLSchema; 2 | 3 | // TYPE, INPUT, INTERFACE 4 | blockDef 5 | : fieldDef+ 6 | ; 7 | 8 | enumDef 9 | : scalarName+ 10 | ; 11 | 12 | scalarName 13 | : Name 14 | ; 15 | 16 | fieldDef 17 | : fieldName fieldArgs? ':' typeSpec 18 | ; 19 | 20 | fieldArgs 21 | : '(' argument+ ')' 22 | ; 23 | 24 | fieldName 25 | : Name 26 | ; 27 | 28 | argument 29 | : Name ':' typeSpec nullable? defaultValue? 30 | ; 31 | 32 | 33 | typeSpec 34 | : (typeName|listType) nullable? 35 | ; 36 | 37 | listType 38 | : '[' typeSpec nullable? ']' 39 | ; 40 | 41 | nullable 42 | : '!' 43 | ; 44 | 45 | defaultValue 46 | : '=' value 47 | ; 48 | 49 | typeName 50 | : Name 51 | ; 52 | 53 | Name 54 | : [a-zA-Z][_0-9A-Za-z]* 55 | ; 56 | 57 | value 58 | : IntValue 59 | | FloatValue 60 | | StringValue 61 | | BooleanValue 62 | | NullValue 63 | | enumValue 64 | | arrayValue 65 | | objectValue 66 | ; 67 | 68 | enumValue 69 | : Name 70 | ; 71 | 72 | arrayValue 73 | : '[' value* ']' 74 | ; 75 | 76 | objectValue 77 | : '{' objectField* '}' 78 | ; 79 | 80 | objectField 81 | : Name ':' value 82 | ; 83 | 84 | BooleanValue 85 | : 'true' 86 | | 'false' 87 | ; 88 | 89 | NullValue 90 | : Null 91 | ; 92 | 93 | Null 94 | : 'null' 95 | ; 96 | 97 | IntValue 98 | : Sign? IntegerPart 99 | ; 100 | 101 | FloatValue 102 | : Sign? IntegerPart ('.' Digit+)? ExponentPart? 103 | ; 104 | 105 | Sign 106 | : '-' 107 | ; 108 | 109 | IntegerPart 110 | : '0' 111 | | NonZeroDigit 112 | | NonZeroDigit Digit+ 113 | ; 114 | 115 | NonZeroDigit 116 | : '1'.. '9' 117 | ; 118 | 119 | ExponentPart 120 | : ('e'|'E') Sign? Digit+ 121 | ; 122 | 123 | Digit 124 | : '0'..'9' 125 | ; 126 | 127 | StringValue 128 | : DoubleQuote (~(["\\\n\r\u2028\u2029])|EscapedChar)* DoubleQuote 129 | ; 130 | 131 | fragment EscapedChar 132 | : '\\' (["\\/bfnrt] | Unicode) 133 | ; 134 | 135 | fragment Unicode 136 | : 'u' Hex Hex Hex Hex 137 | ; 138 | 139 | fragment DoubleQuote 140 | : '"' 141 | ; 142 | 143 | fragment Hex 144 | : [0-9a-fA-F] 145 | ; 146 | 147 | Ignored 148 | : (Whitespace|Comma|LineTerminator|Comment|Directive) -> skip 149 | ; 150 | 151 | fragment Comment 152 | : '#' ~[\n\r\u2028\u2029]* 153 | ; 154 | 155 | fragment Directive 156 | : '@' ~[\n\r\u2028\u2029]* 157 | ; 158 | 159 | fragment LineTerminator 160 | : [\n\r\u2028\u2029] 161 | ; 162 | 163 | fragment Whitespace 164 | : [\t\u000b\f\u0020\u00a0] 165 | ; 166 | 167 | fragment Comma 168 | : ',' 169 | ; 170 | 171 | 172 | -------------------------------------------------------------------------------- /gradle-compiler/src/main/resources/META-INF/gradle-plugins/com.prestongarno.kotlinq.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2018 Preston Garno 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | # 17 | 18 | implementation-class=com.prestongarno.kotlinq.compiler.KotlinqPlugin 19 | 20 | -------------------------------------------------------------------------------- /gradle-compiler/src/test/kotlin/com/prestongarno/kotlinq/compiler/EnumCompileTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Preston Garno 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | @file:Suppress("UNUSED_VARIABLE") 19 | 20 | package com.prestongarno.kotlinq.compiler 21 | 22 | import com.prestongarno.kotlinq.core.schema.QEnumType 23 | import com.prestongarno.kotlinq.core.schema.QType 24 | import com.prestongarno.kotlinq.compiler.KTypeSubject.Companion.argumentsMatching 25 | import com.prestongarno.kotlinq.compiler.KTypeSubject.Companion.reifiedArgumentsMatching 26 | import com.prestongarno.kotlinq.core.properties.delegates.DelegateProvider 27 | import org.junit.After 28 | import org.junit.Before 29 | import org.junit.Test 30 | 31 | class EnumCompileTest : JavacTest() { 32 | 33 | lateinit var loader: KtqCompileWrapper 34 | 35 | @Before fun generateClasses() { 36 | loader = jvmCompileAndLoad(""" 37 | | 38 | |enum GraphQLEnum { HOT, NOT } 39 | | 40 | |type Foo { 41 | | enumProperty: GraphQLEnum 42 | |} 43 | | 44 | |type Bar { enumProp(inputObject: Foo): GraphQLEnum } 45 | """.trimMargin("|"), "") 46 | } 47 | 48 | @After fun kill() { 49 | loader.classLoader.close() 50 | } 51 | 52 | @Test fun `enum exists and has correct options`() = loader.loadClass("Foo") { 53 | this directlyImplements QType::class 54 | 55 | val enumClazz = loader.loadClass("GraphQLEnum") { 56 | this directlyImplements Enum::class 57 | this directlyImplements QEnumType::class 58 | } 59 | 60 | kprop("enumProperty") { gqlEnum -> 61 | gqlEnum requireReturns DelegateProvider.NoArgDelegate::class 62 | gqlEnum.returnType mustHave reifiedArgumentsMatching(enumClazz) 63 | } 64 | }.ignore() 65 | 66 | @Test fun `enum field with required arguments`() = loader.loadClass("Bar") { 67 | 68 | val enumClazz = loader.loadClass("GraphQLEnum") 69 | 70 | val propArgs = loader.loadClass("Bar\$EnumPropArgs") { 71 | kprop("inputObject") { prop -> 72 | prop requireReturns loader.loadClass("Foo") 73 | } 74 | } 75 | 76 | kprop("enumProp") { 77 | //it requireReturns EnumStub.OptionallyConfigured::class 78 | it.returnType mustHave argumentsMatching("GraphQLEnum", "Bar.EnumPropArgs") 79 | } 80 | }.ignore() 81 | } -------------------------------------------------------------------------------- /gradle-compiler/src/test/kotlin/com/prestongarno/kotlinq/compiler/InputTypes.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Preston Garno 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package com.prestongarno.kotlinq.compiler 19 | 20 | import com.prestongarno.kotlinq.compiler.KTypeSubject.Companion.reifiedArgumentsMatching 21 | import org.junit.Test 22 | 23 | class InputTypes : JavacTest() { 24 | 25 | @Test fun `input type with scalar fields is generated correctly to kotlin`() { 26 | val schema = """ 27 | | 28 | |type Query { 29 | | 30 | | search(args: InputDef!): [String] 31 | | 32 | |} 33 | | 34 | |input InputDef { 35 | | 36 | | stringArg: String 37 | | 38 | | intArg: Int 39 | | 40 | | floatArg: Float 41 | | 42 | | booleanArg: Boolean 43 | | 44 | |} 45 | """.trimMargin("|") 46 | 47 | jvmCompileAndLoad(schema, "com.prestongarno", System.out).apply { 48 | 49 | val inputTypeClass = loadClass("com.prestongarno.InputDef") { 50 | 51 | kprop("stringArg") { 52 | it requireReturns String::class 53 | } 54 | 55 | kprop("intArg") { 56 | it requireReturns Int::class 57 | } 58 | 59 | kprop("floatArg") { 60 | it requireReturns Float::class 61 | } 62 | 63 | kprop("booleanArg") { 64 | it requireReturns Boolean::class 65 | } 66 | 67 | } 68 | 69 | val argumentsClass = loadClass("com.prestongarno.Query\$SearchArgs") { 70 | this constructorParametersMatchExactly mapOf("args" to inputTypeClass) 71 | } 72 | 73 | loadClass("com.prestongarno.Query") { 74 | 75 | kprop("search") { 76 | it.returnType mustHave reifiedArgumentsMatching(argumentsClass) 77 | } 78 | 79 | } 80 | 81 | } 82 | 83 | } 84 | } -------------------------------------------------------------------------------- /gradle-compiler/src/test/kotlin/com/prestongarno/kotlinq/compiler/InterfaceMultiInheritance.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Preston Garno 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package com.prestongarno.kotlinq.compiler 19 | 20 | import com.google.common.truth.Truth.assertThat 21 | import org.junit.Ignore 22 | import org.junit.Test 23 | 24 | class InterfaceMultiInheritance : JavacTest() { 25 | 26 | @Ignore // TODO -> this is a very important test to have, leaving it in but disabled 27 | @Test fun `multi inherited field is assigned superinterfaces from all superinterface argbuilder types`() { 28 | 29 | val schema = """ 30 | | 31 | |interface Entity { 32 | | friends(query: String!, first: Int, after: ID): [Entity] 33 | |} 34 | | 35 | |scalar ID 36 | | 37 | |interface Actor { 38 | | friends(first: Int, after: ID): [Entity] 39 | |} 40 | | 41 | |type Wookie implements Entity, Actor { 42 | | friends(query: String!, first: Int, after: ID): [Entity] 43 | |} 44 | | 45 | |""".trimMargin("|") 46 | 47 | assertThat(compileOut(schema).minusPackageNames()).isEqualTo(""" 48 | | 49 | |interface Entity : QType, QInterface { 50 | | val friends: InterfaceListStub.ConfigurableQuery 51 | | 52 | | interface BaseFriendsArgs : ArgumentSpec { 53 | | abstract val query: String 54 | | 55 | | abstract var first: Int? 56 | | 57 | | abstract var after: ID? 58 | | } 59 | |} 60 | | 61 | | 62 | |object ID : CustomScalar 63 | | 64 | | 65 | |interface Actor : QType, QInterface { 66 | | val friends: InterfaceListStub.ConfigurableQuery 67 | | 68 | | interface BaseFriendsArgs : ArgumentSpec { 69 | | abstract var first: Int? 70 | | 71 | | abstract var after: ID? 72 | | } 73 | |} 74 | | 75 | | 76 | |object Wookie : QType, Entity, Actor { 77 | | override val friends: InterfaceListStub.ConfigurableQuery by QInterfaces.List.configured() 78 | | 79 | | class FriendsArgs(query: String) : ArgBuilder(), Entity.BaseFriendsArgs, Actor.BaseFriendsArgs { 80 | | override val query: String by arguments.notNull("query", query) 81 | | 82 | | override var first: Int? by arguments 83 | | 84 | | override var after: ID? by arguments 85 | | } 86 | |} 87 | |""".trimMargin("|")) 88 | 89 | jvmCompileAndLoad(schema, "com.star.wars").apply { 90 | loadClass("com.star.wars.Actor\$BaseFriendsArgs").isAbstract eq true 91 | } 92 | } 93 | } 94 | 95 | 96 | private fun String.minusPackageNames() = this.replace("com.prestongarno.kotlinq.", "") 97 | .replace("stubs.", "") 98 | .replace("core.", "") 99 | .replace("QSchemaType.", "") 100 | 101 | -------------------------------------------------------------------------------- /gradle-compiler/src/test/kotlin/com/prestongarno/kotlinq/compiler/InterfaceToKotlin.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Preston Garno 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package com.prestongarno.kotlinq.compiler 19 | 20 | import org.junit.Ignore 21 | import org.junit.Test 22 | 23 | class InterfaceToKotlin : JavacTest() { 24 | 25 | 26 | @Ignore 27 | @Test fun interfaceKotlinCodeGen() { 28 | val schema = """ 29 | | 30 | |interface Droid { 31 | | uuid: String! 32 | |} 33 | | 34 | |type Cyborg { 35 | | uuid: String! 36 | | humanName: String! 37 | |} 38 | | 39 | |type Astromech { uuid: String! } 40 | | 41 | """.trimMargin("|") 42 | 43 | val expect = """ 44 | | 45 | |interface Droid : com.prestongarno.kotlinq.core.schema.QType, com.prestongarno.kotlinq.core.schema.QInterface { 46 | | val uuid: com.prestongarno.kotlinq.core.schema.stubs.StringDelegate.Query 47 | |} 48 | | 49 | | 50 | |object Cyborg : com.prestongarno.kotlinq.core.schema.QType { 51 | | val uuid: com.prestongarno.kotlinq.core.schema.stubs.StringDelegate.Query by com.prestongarno.kotlinq.core.QSchemaType.QScalar.String.stub() 52 | | 53 | | val humanName: com.prestongarno.kotlinq.core.schema.stubs.StringDelegate.Query by com.prestongarno.kotlinq.core.QSchemaType.QScalar.String.stub() 54 | |} 55 | | 56 | | 57 | |object Astromech : com.prestongarno.kotlinq.core.schema.QType { 58 | | val uuid: com.prestongarno.kotlinq.core.schema.stubs.StringDelegate.Query by com.prestongarno.kotlinq.core.QSchemaType.QScalar.String.stub() 59 | |} 60 | |""".trimMargin("|") 61 | 62 | expect eq compileOut(schema) 63 | 64 | val loader = jvmCompileAndLoad(schema, "") 65 | 66 | loader.loadClass("Droid") { 67 | 68 | kprop("uuid") { 69 | isAbstract eq true 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /gradle-compiler/src/test/kotlin/com/prestongarno/kotlinq/compiler/JavacTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Preston Garno 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package com.prestongarno.kotlinq.compiler 19 | 20 | import com.google.common.io.Files 21 | import java.io.File 22 | import java.io.PrintStream 23 | 24 | open class JavacTest { 25 | 26 | protected fun jvmCompileAndLoad( 27 | schema: String, 28 | packageName: String = "", 29 | printer: PrintStream? = null, 30 | block: GraphQLCompiler.() -> Unit = { } 31 | ): KtqCompileWrapper { 32 | 33 | val tempDir = Files.createTempDir() 34 | 35 | val kotlinOut = File.createTempFile( 36 | "KotlinpoetOutGraphQL", ".kt" 37 | ).apply { deleteOnExit() } 38 | 39 | val compilation = GraphQLCompiler(schema = StringSchema(schema)) { 40 | 41 | this@GraphQLCompiler.packageName = packageName 42 | 43 | kotlinFileName = kotlinOut.name 44 | 45 | }.apply(GraphQLCompiler::compile) 46 | .apply(block) 47 | 48 | kotlinOut.writeText(compilation.toKotlinApi()) 49 | 50 | return JvmCompile.exe(kotlinOut, tempDir, printer) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /gradle-compiler/src/test/kotlin/com/prestongarno/kotlinq/compiler/JvmCompile.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Preston Garno 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package com.prestongarno.kotlinq.compiler 19 | 20 | import com.prestongarno.kotlinq.core.QSchemaType 21 | import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments 22 | import org.jetbrains.kotlin.cli.common.messages.MessageCollector 23 | import org.jetbrains.kotlin.cli.common.messages.MessageRenderer 24 | import org.jetbrains.kotlin.cli.common.messages.PrintingMessageCollector 25 | import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler 26 | import org.jetbrains.kotlin.config.Services 27 | import java.io.File 28 | import java.io.PrintStream 29 | import java.net.URLClassLoader 30 | import kotlin.reflect.KClass 31 | 32 | object JvmCompile { 33 | 34 | @JvmOverloads 35 | fun exe(input: File, buildDir: File, printStream: PrintStream? = null): KtqCompileWrapper { 36 | K2JVMCompiler().run { 37 | val args = K2JVMCompilerArguments().apply { 38 | freeArgs = mutableListOf(input.path) 39 | destination = buildDir.absolutePath 40 | loadBuiltInsFromDependencies = true 41 | includeRuntime = false 42 | noOptimize = true 43 | classpath = System.getProperty("java.class.path") 44 | .split(System.getProperty("path.separator")) 45 | .filter { 46 | it.asFile().exists() && it.asFile().canRead() 47 | }.joinToString(":") 48 | noStdlib = true 49 | noReflect = true 50 | noJdk = true 51 | skipRuntimeVersionCheck = true 52 | jvmTarget = "1.8" 53 | reportPerf = true 54 | } 55 | execImpl(printStream?.let { 56 | PrintingMessageCollector(it, MessageRenderer.PLAIN_RELATIVE_PATHS, true) 57 | } ?: MessageCollector.NONE, Services.EMPTY, args) 58 | }.code.also { exitCode -> 59 | require(exitCode == 0) 60 | } 61 | return KtqCompileWrapper(buildDir) 62 | } 63 | 64 | } 65 | 66 | class KtqCompileWrapper(root: File) { 67 | 68 | val classLoader = URLClassLoader( 69 | listOf(root.toURI().toURL()).toTypedArray(), 70 | this::class.java.classLoader) 71 | 72 | fun loadClass(name: String, block: KClass<*>.() -> Unit = emptyBlock()) = 73 | (classLoader.loadClass(name)).kotlin.apply(block) 74 | 75 | @Suppress("UNCHECKED_CAST") fun loadObject(name: String): QSchemaType = 76 | (classLoader.loadClass(name).kotlin as KClass).objectInstance!! 77 | 78 | @Suppress("UNCHECKED_CAST") fun loadInterface(name: String): KClass = 79 | (classLoader.loadClass(name).kotlin as KClass) 80 | 81 | 82 | } 83 | 84 | fun String.asFile() = File(this) 85 | -------------------------------------------------------------------------------- /gradle-compiler/src/test/kotlin/com/prestongarno/kotlinq/compiler/KeywordsAsSymbols.kt: -------------------------------------------------------------------------------- 1 | package com.prestongarno.kotlinq.compiler 2 | 3 | import org.junit.Test 4 | import kotlin.reflect.KClass 5 | 6 | 7 | class KeywordsAsSymbols { 8 | 9 | @Test fun `single line schema with keywords as symbols compiles successfully`() { 10 | 11 | 12 | // Get schema 13 | val schema = "scalar MyCustomScalar " + 14 | "type Foo implements FooBarEnterprise, Bar { enum(input: String!): Int bar: String! scalar: Int string: Foo type: Int d: Int } " + 15 | "type Input { enum: [[Int]] } " + 16 | "scalar FOO " + 17 | "interface FooBarEnterprise { d: Int } " + 18 | "union SampletypeUnion = Foo | Input " + 19 | "interface Bar { enum(input: String!): Int }" 20 | 21 | GraphQLCompiler(StringSchema(schema)).toKotlinApi() 22 | 23 | val allTypes = GraphQLCompiler(StringSchema(schema)).apply(GraphQLCompiler::compile).schemaTypes 24 | .map { it.name to it } 25 | .toMap() 26 | 27 | allTypes containsEntry "Foo" withType matching() 28 | allTypes containsEntry "FooBarEnterprise" withType matching() 29 | allTypes containsEntry "Bar" withType matching() 30 | allTypes containsEntry "FOO" withType matching() 31 | allTypes containsEntry "MyCustomScalar" withType matching() 32 | allTypes containsEntry "SampletypeUnion" withType matching() 33 | allTypes containsEntry "Input" withType matching() 34 | 35 | allTypes.entries.size eq 7 36 | 37 | (allTypes["Foo"]!! as TypeDef).apply { 38 | supertypes.forEachIndexed { index, iface -> 39 | when (index) { 40 | 0 -> require(iface == allTypes["FooBarEnterprise"]) 41 | 1 -> require(iface == allTypes["Bar"]) 42 | } 43 | } 44 | fields.forEachIndexed { index, field -> 45 | when (index) { 46 | 0 -> field.apply { 47 | requiresConfiguration eq true 48 | inheritsFrom.size eq 1 49 | inheritsFrom.first().name eq "Bar" 50 | arguments.size eq 1 51 | arguments.first().apply { 52 | isAbstract eq false 53 | type.name eq "String" 54 | name eq "input" 55 | nullable eq false 56 | } 57 | } 58 | } 59 | } 60 | } 61 | } 62 | 63 | @Test fun `github public api compilation test`() { 64 | 65 | val schema = this::class.java.classLoader 66 | .getResourceAsStream("graphql.schema.graphqls") 67 | .reader() 68 | .readText() 69 | 70 | GraphQLCompiler(StringSchema(schema)).apply { 71 | compile() 72 | schemaTypes.count() eq 312 73 | } 74 | } 75 | } 76 | 77 | private infix fun Map.containsEntry(name: String) = 78 | this[name] ?: throw IllegalArgumentException("No such type '$name'") 79 | 80 | private infix fun SchemaType.withType(clazz: KClass) = require(this::class == clazz) 81 | 82 | private inline fun matching() = T::class -------------------------------------------------------------------------------- /gradle-compiler/src/test/kotlin/com/prestongarno/kotlinq/compiler/PrimitiveFields.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Preston Garno 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package com.prestongarno.kotlinq.compiler 19 | 20 | import com.prestongarno.kotlinq.core.schema.QType 21 | import org.junit.Test 22 | 23 | class PrimitiveFields : JavacTest() { 24 | 25 | @Test fun `single integer field compiles and returns correct type`() { 26 | val loader = jvmCompileAndLoad(""" 27 | |type Definition { 28 | | value: Int 29 | |} 30 | """.trimMargin("|"), "com.test") 31 | 32 | 33 | loader.loadClass("com.test.Definition") { 34 | this directlyImplements QType::class 35 | 36 | kprop("value") { 37 | //it requireReturns IntDelegate.Query::class 38 | } 39 | } 40 | } 41 | 42 | @Test fun `string field returns correct type`() { 43 | val loader = jvmCompileAndLoad(""" 44 | |type Def2 { fieldValue: String 45 | |} 46 | |""".trimMargin("|")) 47 | 48 | loader.loadClass("Def2") { 49 | this directlyImplements QType::class 50 | 51 | kprop("fieldValue") { 52 | //it requireReturns StringDelegate.Query::class 53 | } 54 | } 55 | } 56 | 57 | @Test fun `float field returns correct type`() { 58 | val loader = jvmCompileAndLoad(""" 59 | |type Def35{ 60 | |floatfield: Float 61 | |} 62 | |""".trimMargin("|")) 63 | 64 | loader.loadClass("Def35") { 65 | this directlyImplements QType::class 66 | 67 | kprop("floatfield") { 68 | //it requireReturns FloatDelegate.Query::class 69 | } 70 | } 71 | 72 | } 73 | 74 | @Test fun `boolean field returns correctly`() { 75 | val schemaClass = jvmCompileAndLoad(""" 76 | |type StarWars { 77 | | boo: Boolean 78 | | } 79 | |""".trimMargin("|")) 80 | .loadObject("StarWars")::class 81 | 82 | schemaClass directlyImplements QType::class 83 | schemaClass.kprop("boo") { 84 | //it requireReturns BooleanDelegate.Query::class 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /gradle-compiler/src/test/kotlin/com/prestongarno/kotlinq/compiler/Test.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Preston Garno 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package com.prestongarno.kotlinq.compiler 19 | 20 | import com.google.common.truth.ThrowableSubject 21 | import com.google.common.truth.Truth.assertThat 22 | 23 | @Suppress("unused") 24 | /** F# expression redirect for single exp. return types */ 25 | fun Any?.ignore() = Unit 26 | 27 | @Suppress("NOTHING_TO_INLINE") 28 | inline infix fun Any?.eq(other: Any?) = assertThat(this).isEqualTo(other) 29 | @Suppress("NOTHING_TO_INLINE") 30 | inline infix fun Any?.notEq(other: Any?) = assertThat(this).isNotEqualTo(other) 31 | 32 | inline fun assertThrows(block: () -> Unit): ThrowableSubject { 33 | try { 34 | block() 35 | } catch (e: Throwable) { 36 | return if (e is T) { 37 | assertThat(e) 38 | } else { 39 | throw e 40 | } 41 | } 42 | throw AssertionError("Expected ${T::class.simpleName}") 43 | } 44 | 45 | fun compileOut(schema: String, block: (GraphQLCompiler.() -> Unit) = emptyBlock()): String = 46 | GraphQLCompiler(StringSchema(schema)) 47 | .apply(block) 48 | .toKotlinApi() 49 | 50 | fun emptyBlock(): T.() -> Unit = Block.empty() 51 | 52 | private object Block { 53 | 54 | private val value: Any.() -> Unit = { /* Nothing */ } 55 | 56 | @Suppress("UNCHECKED_CAST") fun empty(): T.() -> Unit = value as T.() -> Unit 57 | } 58 | -------------------------------------------------------------------------------- /gradle-compiler/src/test/kotlin/com/prestongarno/kotlinq/compiler/TypeAliasImports.kt: -------------------------------------------------------------------------------- 1 | package com.prestongarno.kotlinq.compiler 2 | 3 | import org.junit.Test 4 | import java.io.File 5 | 6 | class TypeAliasImports { 7 | 8 | @Test fun importsAreGenerated() { 9 | 10 | File("/home/preston/IdeaProjects/kotlinq/kotlinq-test-api/src/main/resources/starwars.graphqls") 11 | .reader() 12 | .readLines() 13 | .joinToString("\n") { it }.let { schema -> 14 | 15 | println(generateSequence { "=".repeat(30) }.take(10).joinToString(separator = "\n")) 16 | GraphQLCompiler(StringSchema(schema)) 17 | .apply(GraphQLCompiler::compile) 18 | .toKotlinApi() 19 | .let(::println) 20 | 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /gradle-compiler/src/test/kotlin/com/prestongarno/kotlinq/compiler/UnionToKotlin.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Preston Garno 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | @file:Suppress("UNUSED_VARIABLE") 19 | 20 | package com.prestongarno.kotlinq.compiler 21 | 22 | import com.google.common.truth.Truth.assertThat 23 | import org.junit.Ignore 24 | import org.junit.Test 25 | 26 | class UnionToKotlinTest : JavacTest() { 27 | 28 | // todo use class delegate branch for kotlinpoet so fqnames can be replaced with imports 29 | @Ignore 30 | @Test fun `string matching schema to kotlin`() { 31 | val schema = """ 32 | | 33 | |type Droid {uuid: String} 34 | | 35 | |union Actor = Jedi | Droid 36 | | 37 | |type Jedi {name: String} 38 | | 39 | """ 40 | val result = compileOut(schema.trimMargin("|")) 41 | 42 | val expect = """ 43 | 44 | object Droid : com.prestongarno.kotlinq.core.schema.QType { 45 | val uuid: com.prestongarno.kotlinq.core.schema.stubs.StringDelegate.Query by com.prestongarno.kotlinq.core.QSchemaType.QScalar.String.stub() 46 | } 47 | 48 | 49 | object Actor : com.prestongarno.kotlinq.core.schema.QUnionType by com.prestongarno.kotlinq.core.schema.QUnionType.new() { 50 | fun onJedi(init: () -> com.prestongarno.kotlinq.core.QModel) { 51 | on(init)} 52 | 53 | fun onDroid(init: () -> com.prestongarno.kotlinq.core.QModel) { 54 | on(init)} 55 | } 56 | 57 | 58 | object Jedi : com.prestongarno.kotlinq.core.schema.QType { 59 | val name: com.prestongarno.kotlinq.core.schema.stubs.StringDelegate.Query by com.prestongarno.kotlinq.core.QSchemaType.QScalar.String.stub() 60 | } 61 | 62 | """.trimIndent() 63 | 64 | assertThat(result).isEqualTo(expect) 65 | result eq expect 66 | 67 | val loader = jvmCompileAndLoad(schema.trimMargin("|"), "com.test").apply { 68 | 69 | loadClass("com.test.Actor") { 70 | 71 | func("onDroid") { it.parameters[0].toString() eq "graphQlInstance of fun com.test.Actor.onDroid(" + 72 | "() -> com.prestongarno.kotlinq.core.QModel): kotlin.Unit" } 73 | 74 | func("onJedi") { it.parameters[0].toString() eq "graphQlInstance of fun com.test.Actor.onJedi(" + 75 | "() -> com.prestongarno.kotlinq.core.QModel): kotlin.Unit" } 76 | 77 | } 78 | } 79 | } 80 | 81 | 82 | 83 | 84 | 85 | } 86 | -------------------------------------------------------------------------------- /gradle-compiler/src/test/resources/sample.schema.graphqls: -------------------------------------------------------------------------------- 1 | interface ActorFoo { 2 | avatar(size: Int): URLY! 3 | login: String! 4 | url: URLY! 5 | } 6 | interface SomeConflict { 7 | avatar(size: Int): URLY! 8 | foobar: String 9 | } 10 | 11 | scalar URLY 12 | 13 | type UserFoo implements ActorFoo { 14 | avatar(size: Int): URLY! 15 | login: String! 16 | url: URLY! 17 | } 18 | 19 | type OrganizationFoo implements ActorFoo SomeConflict { 20 | owner: UserFoo 21 | name: String 22 | members: [UserFoo] 23 | avatar(size: Int): URLY! 24 | login: String! 25 | url: URLY! 26 | foobar: String 27 | } 28 | -------------------------------------------------------------------------------- /gradle-compiler/src/test/resources/yelp.graphqls: -------------------------------------------------------------------------------- 1 | type Business { 2 | name: String 3 | id: String 4 | is_claimed: Boolean 5 | is_closed: Boolean 6 | url: String 7 | phone: String 8 | display_phone: String 9 | review_count: Int 10 | categories: [Category] 11 | rating: Float 12 | price: String 13 | location: Location 14 | coordinates: Coordinates 15 | photos: [String] 16 | hours: [Hours] 17 | reviews: [Review] 18 | } 19 | 20 | type Businesses { 21 | business: [Business] 22 | total: Int 23 | } 24 | 25 | type Category { 26 | title: String 27 | alias: String 28 | } 29 | 30 | type Coordinates { 31 | latitude: Float 32 | longitude: Float 33 | } 34 | 35 | type Hours { 36 | hours_type: String 37 | open: [OpenHours] 38 | is_open_now: Boolean 39 | } 40 | 41 | type Location { 42 | address1: String 43 | address2: String 44 | address3: String 45 | city: String 46 | state: String 47 | zip_code: String 48 | country: String 49 | formatted_address: String 50 | } 51 | 52 | type OpenHours { 53 | is_overnight: Boolean 54 | end: String 55 | start: String 56 | day: Int 57 | } 58 | #Represents the lack of understanding the developer team at yelp had when 59 | #they used a type system in order to spam strings by tens as args to fields... 60 | type Query { 61 | business(id: String!): Business 62 | business_match_best(name: String!, address1: String, address2: String, address3: String, city: String!, state: String!, country: String!, latitude: Float, longitude: Float, phone: String, postal_code: String): Business 63 | business_match_lookup(name: String!, address1: String, address2: String, address3: String, city: String!, state: String!, country: String!, latitude: Float, longitude: Float, phone: String, postal_code: String): Businesses 64 | reviews(business: String, locale: String): Reviews 65 | phone_search(phone: String): Businesses 66 | search(term: String, location: String, country: String, offset: Int, limit: Int, sort_by: String, locale: String, longitude: Float, latitude: Float, categories: String, open_now: Boolean, open_at: Int, price: String, attributes: String, radius: Float): Businesses 67 | } 68 | 69 | #This type represents a review. You know, like in that one south park episode? 70 | type Review { 71 | # The rating that someone gave, ranging 1->5, with 5 being the greatest rating 72 | rating: Int 73 | #The user that left the rating 74 | user: User 75 | #The body 76 | #of the review 77 | # usually somewhat longer 78 | #than single comment lines ;) 79 | text: String 80 | #The time it was posted 81 | time_created: String 82 | #hard link to the review 83 | url: String 84 | } 85 | 86 | type Reviews { 87 | review: [Review] 88 | total: Int 89 | } 90 | 91 | type User { 92 | image_url: String 93 | name: String 94 | } 95 | -------------------------------------------------------------------------------- /gradle/dependencies.gradle: -------------------------------------------------------------------------------- 1 | def versions = [ 2 | antlr : '4.7', 3 | autoService : '1.0-rc4', 4 | bintray : "1.8.0", 5 | klaxon : "2.1.4", 6 | kodein : "4.1.0", 7 | kotlinPoet : "0.7.0", 8 | kotlinVersion : "1.2.50", 9 | kotlinxCoroutines: "0.23.3" 10 | ] 11 | 12 | ext.dep = [ 13 | antlr4 : "org.antlr:antlr4-runtime:$versions.antlr", 14 | autoService : "com.google.auto.service:auto-service:$versions.autoService", 15 | jfrogLatestRelease : "org.jfrog.buildinfo:build-info-extractor-gradle:4.6.2", 16 | junit : 'junit:junit:4.12', 17 | klaxon : "com.beust:klaxon:$versions.klaxon", 18 | kodein : "com.github.salomonbrys.kodein:kodein:$versions.kodein", 19 | kotlinpoet : "com.squareup:kotlinpoet:$versions.kotlinPoet", 20 | kotlinTest : "org.jetbrains.kotlin:kotlin-test:$versions.kotlinVersion", 21 | kotlinCompilerEmbeddable: "org.jetbrains.kotlin:kotlin-compiler-embeddable:$versions.kotlinVersion", 22 | kotlinStdLib : "org.jetbrains.kotlin:kotlin-stdlib:$versions.kotlinVersion", 23 | kotlinGradlePlugin : "org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.kotlinVersion", 24 | kotlinGradlePluginApi : "org.jetbrains.kotlin:kotlin-gradle-plugin-api:$versions.kotlinVersion", 25 | kotlinReflect : "org.jetbrains.kotlin:kotlin-reflect:$versions.kotlinVersion", 26 | kotlinxCoroutines : "org.jetbrains.kotlinx:kotlinx-coroutines-android:$versions.kotlinxCoroutines", 27 | bintrayGradlePlugin : "com.jfrog.bintray.gradle:gradle-bintray-plugin:$versions.bintray", 28 | truth : 'com.google.truth:truth:0.36', 29 | ] 30 | 31 | ext.getProjectVersion = { 32 | return 33 | } 34 | 35 | -------------------------------------------------------------------------------- /gradle/deploy.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | apply plugin: 'maven' 3 | apply plugin: 'maven-publish' 4 | apply plugin: "com.jfrog.bintray" 5 | apply plugin: "com.jfrog.artifactory" 6 | 7 | publishing { 8 | publications { 9 | mavenJava(MavenPublication) { 10 | groupId project.group 11 | artifactId project.name 12 | version project.version.toString() 13 | from components.java 14 | if (project != rootProject) { 15 | artifact project.tasks.getByName('jarDoc') 16 | artifact project.tasks.getByName('jarSource') 17 | } 18 | 19 | pom.withXml { 20 | asNode().children().last() + { 21 | resolveStrategy = DELEGATE_FIRST 22 | name project.name 23 | description projectDescription 24 | url gitBaseUrl 25 | organization { 26 | name DEV_COMPANY 27 | url DEV_COMPANY_URL 28 | } 29 | issueManagement { 30 | system 'git' 31 | url "$gitBaseUrl/issues" 32 | } 33 | scm { 34 | url "${gitBaseUrl}.git" 35 | } 36 | licenses { 37 | license { 38 | name "Apache 2.0" 39 | url "$projLicenseUrl" 40 | comments "See $projLicenseUrl for more details." 41 | distribution "$projLicenseUrl" 42 | } 43 | } 44 | developers { 45 | developer { 46 | id DEV_URL 47 | name DEV_NAME 48 | organization DEV_COMPANY 49 | organizationUrl DEV_COMPANY_URL 50 | } 51 | } 52 | } 53 | } 54 | } 55 | } 56 | repositories { 57 | maven { 58 | url 'https://bintray.com/com/prestongarno/' 59 | } 60 | } 61 | } 62 | 63 | artifactory { 64 | contextUrl = 'http://oss.jfrog.org/artifactory' 65 | publish { 66 | repository { 67 | repoKey = 'oss-snapshot-local' 68 | username = BINTRAY_USER 69 | password = JFROG_KEY 70 | } 71 | defaults { 72 | publications('mavenJava') 73 | publishPom = true 74 | } 75 | } 76 | } 77 | 78 | 79 | bintray { 80 | user BINTRAY_USER 81 | key BINTRAY_KEY 82 | publications = ['mavenJava'] 83 | dryRun = version.endsWith("SNAPSHOT") || project.name == rootProject.name 84 | publish = false 85 | override = true 86 | 87 | pkg { 88 | repo = 'kotlinq' 89 | name = project.name 90 | desc = projectDescription 91 | licenses = ['Apache 2.0'] 92 | vcsUrl = "${gitBaseUrl}.git" 93 | githubRepo = "$gitBaseUrl" 94 | githubReleaseNotesFile = 'README.md' 95 | 96 | version { 97 | name = "$project.version" 98 | gpg { 99 | sign = true 100 | passphrase = GMAIL_GPG 101 | } 102 | mavenCentralSync { 103 | sync = false 104 | user = MAVEN2_USERNAME 105 | password = MAVEN2_PASSWORD 106 | close = '0' 107 | } 108 | } 109 | } 110 | } 111 | 112 | -------------------------------------------------------------------------------- /gradle/projectsnapshot.gradle: -------------------------------------------------------------------------------- 1 | // for deploying a full project snapshot 2 | 3 | apply plugin: 'java' 4 | 5 | group 'com.prestongarno' 6 | version = rootProject.findProperty("release") == "true" ? "0.4" : "0.4-SNAPSHOT" 7 | 8 | jar { 9 | from(subprojects.collect { it.tasks.withType(Jar) }.flatten() 10 | .collect { "$it.destinationDir/$it.archiveName" }) 11 | baseName rootProject.name 12 | archiveName "$baseName-$rootProject.version" + ".jar" 13 | } 14 | 15 | jar.dependsOn(subprojects.collect { it.tasks.withType(Jar) }) 16 | apply from: rootProject.file("gradle/deploy.gradle") 17 | 18 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prestongarno/kotlinq/a9e8dab66e2704aab1434f76cff7129d84494796/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.8-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /jvm/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | dependencies { 3 | compile project(':kotlinq-core') 4 | compile dep.kotlinReflect 5 | testCompile project(':kotlinq-dsl') 6 | } 7 | -------------------------------------------------------------------------------- /jvm/src/main/kotlin/Api.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.jvm 2 | 3 | 4 | /** 5 | * Create a [org.kotlinq.api.Fragment], but typed 6 | */ 7 | inline fun fragment( 8 | noinline init: (GraphQlResult) -> T, 9 | noinline block: TypedFragmentScope.() -> Unit = { /* nothing */ } 10 | ): ClassFragment = ClassFragment(T::class, init, block) 11 | 12 | 13 | inline operator fun ((GraphQlResult) -> T).invoke( 14 | noinline block: TypedFragmentScope.() -> Unit 15 | ): ClassFragment = ClassFragment(T::class, this, block) -------------------------------------------------------------------------------- /jvm/src/main/kotlin/ClassFragment.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.jvm 2 | 3 | import org.kotlinq.api.Fragment 4 | import org.kotlinq.api.Kotlinq 5 | import org.kotlinq.api.PropertyInfo 6 | import kotlin.reflect.KClass 7 | import kotlin.reflect.full.isSubclassOf 8 | import kotlin.reflect.full.memberProperties 9 | 10 | 11 | class ClassFragment @PublishedApi internal constructor( 12 | private val clazz: KClass<*>, 13 | internal val init: (GraphQlResult) -> T, 14 | block: TypedFragmentScope.() -> Unit = { }, 15 | private val fragment: Fragment = reflectionFragment(clazz, block) 16 | ) : Fragment by fragment { 17 | 18 | 19 | fun resolveFrom(map: Map) = 20 | ClassFragmentResolver(map, this).resolve() 21 | 22 | override fun equals(other: Any?) = fragment == other 23 | || (other as? ClassFragment<*>)?.let { it.fragment == fragment } == true 24 | 25 | override fun hashCode(): Int = 26 | clazz.hashCode() * 31 + fragment.hashCode() 27 | 28 | 29 | companion object { 30 | 31 | internal 32 | fun fragment( 33 | clazz: KClass<*>, 34 | init: (GraphQlResult) -> T, 35 | block: TypedFragmentScope.() -> Unit = { /* nothing */ } 36 | ) = ClassFragment(clazz, init, block) 37 | 38 | } 39 | } 40 | 41 | 42 | @Suppress("UNCHECKED_CAST") 43 | internal fun reflectionFragment(clazz: KClass<*>, 44 | block: TypedFragmentScope.() -> Unit = { /* nothing */ }): Fragment = 45 | 46 | clazz.memberProperties 47 | .filterNot(Reflekt::shouldBeIgnored) 48 | .mapNotNull { 49 | it with (it.returnType.scalarKind() ?: it.returnType.dataKind()) 50 | }.mapNotNull { (prop, kind) -> 51 | PropertyInfo.propertyNamed(prop.name).typeKind(kind).build().let { info -> 52 | if (kind.isScalar) 53 | Kotlinq.adapterService.scalarAdapters.newAdapter(info) 54 | else prop.returnType.rootType.clazz?.let { 55 | if (it.isSubclassOf(Data::class)) Kotlinq.adapterService.instanceProperty(info, reflectionFragment(it)) else null 56 | } 57 | } 58 | }.useWith(Kotlinq.newContextBuilder()) { builder -> 59 | this.forEach { builder.register(it) } 60 | TypedFragmentScope(builder).apply(block) 61 | builder.build(clazz.simpleName!!) 62 | } 63 | -------------------------------------------------------------------------------- /jvm/src/main/kotlin/ClassFragmentResolver.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.jvm 2 | 3 | import org.kotlinq.api.Adapter 4 | import org.kotlinq.api.Fragment 5 | import org.kotlinq.api.Kind 6 | import kotlin.reflect.KClass 7 | 8 | 9 | internal 10 | class ClassFragmentResolver( 11 | private val map: Map, 12 | private val fragment: ClassFragment 13 | ) { 14 | 15 | val isValid: Boolean by lazy { Validation.canResolve(map, fragment) } 16 | 17 | fun resolve(): T? = 18 | if (!isValid) null else fragment.init(map.toResult(fragment)) 19 | } 20 | 21 | 22 | // TODO extract to interface here for configuration 23 | internal 24 | object Validation { 25 | 26 | fun canResolve(map: Map, fragment: ClassFragment<*>): Boolean = 27 | fragment.graphQlInstance.properties.entries.all { 28 | isValidValue(it.value.propertyInfo.kind, map[it.key]) 29 | } 30 | 31 | private operator fun Fragment.get(property: String): Adapter? = 32 | graphQlInstance.properties[property] 33 | 34 | 35 | fun isValidValue(kind: Kind, value: Any?): Boolean { 36 | 37 | value ?: return kind is Kind.NullableKind 38 | 39 | // can't call Kind#isScalar since that counts "List" as a scalar 40 | if (value.isScalar()) return kind is Kind.Scalar 41 | && value.scalarKind()?.isTypeCompatible(kind) == true 42 | 43 | if (kind is Kind.ListKind) 44 | return (value as? List<*>)?.all { isValidValue(kind.wrapped, it) } ?: false 45 | 46 | // best we can do here, without reflection on the back end 47 | return (kind as? Kind.NullableKind) 48 | ?.wrapped 49 | ?.let { isValidValue(it, value) } 50 | ?: value.asStringMap() != null 51 | } 52 | 53 | 54 | private 55 | fun Any.isScalar() = scalarKind() != null 56 | 57 | private fun Any?.scalarKind() = when (this) { 58 | is Boolean -> Kind.bool 59 | is Int -> Kind.integer 60 | is String -> Kind.string 61 | is Float -> Kind.float 62 | else -> null 63 | } 64 | 65 | 66 | private fun String.getFrom(map: Map): Any? = map[this] 67 | 68 | } 69 | 70 | @Suppress("UNCHECKED_CAST") 71 | internal 72 | fun Any?.asStringMap(): Map? = 73 | if ((this as? Map<*, *>)?.entries?.all { it.key is String } == true) this as? Map else null 74 | 75 | @Suppress("UNCHECKED_CAST") 76 | internal inline fun Any?.asList(): List? = 77 | if ((this as? List<*>)?.none { !T::class.isInstance(it) } == true) this as List else null 78 | 79 | @Suppress("UNCHECKED_CAST") 80 | internal fun Any?.asList(clazz: KClass<*>): List? { 81 | return if ((this as? List<*>)?.none { !clazz.isInstance(it) } == true) this as List else null 82 | } 83 | -------------------------------------------------------------------------------- /jvm/src/main/kotlin/Common.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.jvm 2 | 3 | internal 4 | infix fun T.with(other: U?): Pair? = 5 | other?.let { this to it } 6 | 7 | internal 8 | fun use(value: T, block: T.() -> Unit) = value.apply(block) 9 | 10 | internal 11 | fun T.useWith(value: R, block: T.(R) -> Z): Z = this.block(value) 12 | 13 | internal 14 | fun Any?.ignore() = Unit 15 | 16 | internal 17 | fun List.contains(predicate: (T) -> Boolean) = find(predicate) != null 18 | 19 | internal 20 | fun List.none(predicate: (T) -> Boolean) = find(predicate) == null -------------------------------------------------------------------------------- /jvm/src/main/kotlin/Data.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.jvm 2 | 3 | import kotlin.properties.ReadOnlyProperty 4 | 5 | 6 | interface Data { 7 | 8 | val result: GraphQlResult 9 | 10 | @Suppress("UNCHECKED_CAST") // Should be totally safe I think? 11 | fun ReadOnlyProperty.asList(): ReadOnlyProperty> = 12 | (this as? GraphQlFieldDelegate)?.withReturnType() ?: this as ReadOnlyProperty> 13 | } 14 | 15 | /** 16 | * Convenience class to subclass [Data] easily 17 | */ 18 | abstract class GraphQlData(final override val result: GraphQlResult): Data { 19 | constructor(map: Map) : this(map.toResult()) 20 | } 21 | 22 | 23 | 24 | fun GraphQlResult.toData(): Data = object : Data { 25 | override val result: GraphQlResult get() = this@toData 26 | } 27 | 28 | operator fun Map.invoke(): Data = object : Data { 29 | override val result: GraphQlResult = toResult() 30 | } -------------------------------------------------------------------------------- /jvm/src/main/kotlin/FragmentField.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.jvm 2 | 3 | import kotlin.reflect.KProperty1 4 | 5 | 6 | class FragmentField internal constructor( 7 | internal val property: KProperty1, 8 | internal val arguments: Map = emptyMap()) 9 | -------------------------------------------------------------------------------- /jvm/src/main/kotlin/FragmentSpread.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.jvm 2 | 3 | import org.kotlinq.api.Fragment 4 | import org.kotlinq.api.FragmentContext 5 | import org.kotlinq.api.GraphVisitor 6 | import org.kotlinq.api.PropertyInfo 7 | 8 | class FragmentSpread internal constructor( 9 | internal val classFragments: Set>, 10 | override val propertyInfo: PropertyInfo 11 | ) : FragmentContext { 12 | 13 | override val fragments: Map = 14 | classFragments.map { it.typeName to it }.toMap() 15 | 16 | override fun getValue(): Any? { 17 | TODO("not implemented") 18 | } 19 | 20 | override fun accept(resolver: GraphVisitor) = 21 | resolver.visitFragmentContext(this) 22 | 23 | override fun isResolved(): Boolean { 24 | TODO("not implemented") 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /jvm/src/main/kotlin/GraphQlDsl.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.jvm 2 | 3 | @DslMarker 4 | @Retention(AnnotationRetention.RUNTIME) 5 | annotation class GraphQlDsl -------------------------------------------------------------------------------- /jvm/src/main/kotlin/GraphQlResult.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.jvm 2 | 3 | import org.kotlinq.api.GraphQlInstance 4 | import org.kotlinq.api.Kotlinq 5 | import kotlin.properties.ReadOnlyProperty 6 | 7 | 8 | internal fun Map.toResult(inst: GraphQlInstance) = GraphQlResult(this, inst) 9 | 10 | internal fun Map.toResult(fragment: ClassFragment<*>? = null) = 11 | toResult(fragment?.graphQlInstance ?: Kotlinq.newContextBuilder().build("${get("__typename")}").graphQlInstance) 12 | 13 | 14 | 15 | 16 | class GraphQlResult internal constructor( 17 | map: Map, 18 | graphQlInstance: GraphQlInstance 19 | ) { 20 | 21 | // copy in case of bad input (mutable map as Map) 22 | private val delegate: GraphQlFieldDelegate = GraphQlFieldDelegate(map.toMap(), graphQlInstance) 23 | 24 | operator fun invoke(): ReadOnlyProperty = 25 | delegate.withReturnType() 26 | 27 | fun string(): ReadOnlyProperty = 28 | delegate.withReturnType() 29 | 30 | fun integer(): ReadOnlyProperty = 31 | delegate.withReturnType() 32 | 33 | fun bool(): ReadOnlyProperty = 34 | delegate.withReturnType() 35 | 36 | fun floatingPoint(): ReadOnlyProperty = 37 | delegate.withReturnType() 38 | } 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /jvm/src/main/kotlin/InterfaceFragmentSpreadScope.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.jvm 2 | 3 | import org.kotlinq.api.PropertyInfo 4 | import org.kotlinq.jvm.ClassFragment.Companion.fragment 5 | import kotlin.reflect.KClass 6 | 7 | 8 | @GraphQlDsl 9 | class InterfaceFragmentSpreadScope internal constructor(internal val info: PropertyInfo) { 10 | 11 | private val fragments = mutableSetOf>() 12 | 13 | inline fun on( 14 | noinline init: (GraphQlResult) -> X, 15 | noinline block: (TypedFragmentScope.() -> Unit)? = null) { 16 | @Suppress("UNCHECKED_CAST") 17 | registerReifiedTypeAsFragmentOption(init, X::class as KClass, block) 18 | } 19 | 20 | fun on(fragment: ClassFragment) { 21 | fragments += fragment 22 | } 23 | 24 | @PublishedApi internal 25 | fun registerReifiedTypeAsFragmentOption( 26 | init: (GraphQlResult) -> X, clazz: KClass, 27 | fragmentBlock: (TypedFragmentScope.() -> Unit)? = null) { 28 | 29 | fragments += fragment(clazz, init, fragmentBlock ?: empty()) 30 | } 31 | 32 | internal 33 | fun build(block: InterfaceFragmentSpreadScope.() -> Unit): FragmentSpread = 34 | apply(block).let { 35 | FragmentSpread(fragments, info) 36 | } 37 | } 38 | 39 | internal fun empty(): TypedFragmentScope.() -> Unit = { } 40 | 41 | -------------------------------------------------------------------------------- /jvm/src/main/kotlin/Reflekt.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.jvm 2 | 3 | import org.kotlinq.api.Kind 4 | import org.kotlinq.api.PropertyInfo 5 | import org.kotlinq.jvm.annotations.Ignore 6 | import kotlin.coroutines.experimental.buildSequence 7 | import kotlin.reflect.KClass 8 | import kotlin.reflect.KProperty1 9 | import kotlin.reflect.KType 10 | import kotlin.reflect.full.createType 11 | import kotlin.reflect.full.findAnnotation 12 | import kotlin.reflect.full.isSubclassOf 13 | import kotlin.reflect.full.isSubtypeOf 14 | import kotlin.reflect.full.memberProperties 15 | import kotlin.reflect.full.starProjectedType 16 | 17 | // Internal extension functions for kotlin core reflection & kotlinq api 18 | 19 | internal 20 | object Reflekt { 21 | 22 | val dataKtype = Data::class.createType(nullable = true) 23 | 24 | val anyProperties: Set> = 25 | Any::class.memberProperties.toSet() 26 | 27 | fun shouldBeIgnored(property: KProperty1<*, *>): Boolean { 28 | return property.findAnnotation() != null || property in anyProperties 29 | } 30 | } 31 | 32 | internal 33 | fun KType.isCompatibleWith(value: Any?): Boolean { 34 | 35 | value ?: return isMarkedNullable 36 | 37 | if (this.isList) { 38 | val valueAsList = value as? List<*> ?: return false 39 | return listIsCompatible(valueAsList, 40 | arguments.first().type ?: Any::class.createType()) 41 | } else { 42 | return value::class.starProjectedType.isSubtypeOf(this) 43 | } 44 | } 45 | 46 | private fun listIsCompatible(value: List<*>, type: KType): Boolean { 47 | if (value.isEmpty()) return true 48 | return value.all(type::isCompatibleWith) 49 | } 50 | 51 | @PublishedApi internal 52 | fun KProperty1<*, *>.toPropertyInfo( 53 | typeName: String = this.returnType.rootType.clazz?.simpleName!!, 54 | args: Map = emptyMap() 55 | ): PropertyInfo { 56 | 57 | if (returnType.rootType.scalarKind() != null) 58 | throw IllegalArgumentException(this.toString()) 59 | 60 | return PropertyInfo 61 | .propertyNamed(name) 62 | .typeKind(wrap( 63 | Kind.typeNamed(typeName), 64 | returnType)) 65 | .arguments(args) 66 | .build() 67 | } 68 | 69 | 70 | internal 71 | fun KType.scalarKind(): Kind? { 72 | val name = (this.rootType.classifier as? KClass<*>)?.simpleName ?: return null 73 | return Kind.scalars.firstOrNull { it.classifier == name }?.let { 74 | wrap(it, this) 75 | } 76 | } 77 | 78 | internal 79 | fun wrap(kind: Kind, type: KType): Kind = buildSequence { 80 | var current = type 81 | while (current.isList) { 82 | if (current.isMarkedNullable) 83 | yield(Kind::asNullable) 84 | yield(Kind::asList) 85 | current.arguments.firstOrNull()?.type?.let { 86 | current = it 87 | } 88 | } 89 | }.toList() 90 | .reversed() 91 | .fold(kind) { acc, curr -> curr(acc) } 92 | 93 | internal 94 | fun KType.dataKind(): Kind? = 95 | if (!rootType.isSubtypeOf(Reflekt.dataKtype)) 96 | null 97 | else rootType.clazz?.simpleName 98 | ?.let(Kind.Companion::typeNamed) 99 | ?.let { wrap(it, this) } 100 | 101 | internal 102 | val KType.clazz: KClass<*>? 103 | get() = this.classifier as? KClass<*> 104 | 105 | internal 106 | val KType.rootType: KType 107 | get() { 108 | return if (isList) { 109 | var current: KType = this 110 | while (current.isList) 111 | current.arguments.firstOrNull()?.type?.let { current = it } 112 | current 113 | } else this 114 | } 115 | 116 | internal 117 | val KType.isList get() = this.clazz?.isList ?: false 118 | 119 | internal 120 | val KClass<*>.isList get() = isSubclassOf(List::class) 121 | -------------------------------------------------------------------------------- /jvm/src/main/kotlin/annotations/Ignore.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.jvm.annotations 2 | 3 | /** 4 | * Annotate properties which should be ignored when members of a GraphQl Typed Fragment 5 | */ 6 | @Target(AnnotationTarget.PROPERTY) 7 | @Retention(AnnotationRetention.RUNTIME) 8 | annotation class Ignore 9 | -------------------------------------------------------------------------------- /jvm/src/test/kotlin/ClassFragmentDsl.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.jvm 2 | 3 | import org.junit.Test 4 | 5 | 6 | class ClassFragmentDsl { 7 | 8 | class N2Entity(result: GraphQlResult) 9 | : Data by result.toData() { 10 | val x by result.integer() 11 | } 12 | 13 | class NestedEntity(result: GraphQlResult) 14 | : Data by result.toData() { 15 | val integerField by result.integer() 16 | val floatingPointArrayField by result.floatingPoint().asList() 17 | val foo by result().asList() 18 | } 19 | 20 | class RootEntity(result: GraphQlResult) 21 | : Data by result.toData() { 22 | val stringField by result.string() 23 | val nestedEntity by result() 24 | } 25 | 26 | 27 | @Test fun syntaxIsReadable() { 28 | 29 | val entityFragment = ::RootEntity { 30 | RootEntity::nestedEntity on ::NestedEntity { 31 | NestedEntity::foo spread ::N2Entity 32 | } 33 | } 34 | 35 | val actual = entityFragment.toGraphQl( 36 | pretty = true, 37 | idAndTypeName = false) 38 | 39 | val expect = """ 40 | |{ 41 | | nestedEntity { 42 | | floatingPointArrayField 43 | | foo { 44 | | x 45 | | } 46 | | integerField 47 | | } 48 | | stringField 49 | |} 50 | """.trimMargin("|") 51 | require(actual == expect) { "Not equal: <$expect> != <$actual>" } 52 | } 53 | 54 | @Test fun argumentsAreAddedToFragment() { 55 | 56 | val expect = """ 57 | |{ 58 | | nestedEntity(foo: "bar") { 59 | | floatingPointArrayField 60 | | foo { 61 | | x 62 | | } 63 | | integerField 64 | | } 65 | | stringField 66 | |} 67 | """.trimMargin("|") 68 | 69 | ::RootEntity { 70 | RootEntity::nestedEntity withArgs mapOf("foo" to "bar") on ::NestedEntity { 71 | NestedEntity::foo..::N2Entity{} 72 | } 73 | }.toGraphQl(pretty = true, idAndTypeName = false) 74 | .let { actual -> require(actual == expect) } 75 | } 76 | } -------------------------------------------------------------------------------- /jvm/src/test/kotlin/Common.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.test 2 | 3 | import com.google.common.truth.Subject 4 | import kotlin.reflect.KClass 5 | 6 | 7 | val KClass<*>.name get() = simpleName!! 8 | 9 | inline fun Subject<*, *>.isInstanceOf() = 10 | isInstanceOf(T::class.java) 11 | 12 | inline fun Subject<*, *>.isNotInstanceOf() = 13 | isNotInstanceOf(T::class.java) 14 | -------------------------------------------------------------------------------- /jvm/src/test/kotlin/EqualityTest.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.jvm 2 | 3 | import com.google.common.truth.Truth.assertThat 4 | import org.junit.Test 5 | import org.kotlinq.jvm.ClassFragment.Companion.fragment 6 | 7 | 8 | class EqualityTest { 9 | 10 | @Test fun simpleEquals() { 11 | class Foo(res: GraphQlResult): Data by res.toData() { 12 | val str by result.string() 13 | } 14 | 15 | assertThat(fragment(::Foo).hashCode()) 16 | .isEqualTo(fragment(::Foo).hashCode()) 17 | assertThat(fragment(::Foo)) 18 | .isEqualTo(fragment(::Foo)) 19 | 20 | } 21 | } -------------------------------------------------------------------------------- /jvm/src/test/kotlin/JsonDsl.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.jvm 2 | 3 | 4 | fun json(block: JsonScope.() -> Unit): Map = 5 | JsonScope(block).toMap() 6 | 7 | 8 | class JsonScope(block: JsonScope.() -> Unit) { 9 | 10 | 11 | private val map = mutableMapOf() 12 | 13 | init { this.block() } 14 | 15 | 16 | operator fun String.invoke(value: String) = putValue(this, value) 17 | operator fun String.invoke(value: Int) = putValue(this, value) 18 | operator fun String.invoke(value: Boolean) = putValue(this, value) 19 | operator fun String.invoke(value: Double) = putValue(this, value) 20 | operator fun String.invoke(list: List<*>) = putValue(this, list) 21 | 22 | operator fun String.invoke(vararg values: Any) = 23 | putValue(this, values.toList()) 24 | 25 | infix fun String.list(block: ArrayScope.() -> Unit) = 26 | putValue(this, ArrayScope(block).toList()) 27 | 28 | private fun putValue(key: String, value: Any) { map[key] = value } 29 | 30 | operator fun String.invoke(block: JsonScope.() -> Unit) { 31 | map[this] = JsonScope(block).toMap() 32 | } 33 | 34 | internal fun toMap() = map.toMap() 35 | } 36 | 37 | class ArrayScope(block: ArrayScope.() -> Unit) { 38 | 39 | 40 | private val objects = mutableListOf>() 41 | 42 | init { this.block() } 43 | 44 | fun add(block: JsonScope.() -> Unit) { 45 | objects.add(JsonScope(block).toMap()) 46 | } 47 | 48 | internal fun toList() = objects.toList() 49 | } -------------------------------------------------------------------------------- /jvm/src/test/kotlin/Tests.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.jvm 2 | 3 | import com.google.common.truth.Truth.assertThat 4 | import org.junit.Test 5 | import org.kotlinq.jvm.ClassFragment.Companion.fragment 6 | 7 | 8 | class Tests { 9 | 10 | abstract class Super : Data 11 | 12 | class Bar(graphQlResult: GraphQlResult) : Data by graphQlResult.toData() { 13 | val baz by result.integer().asList() 14 | } 15 | 16 | class Foo(result: GraphQlResult) : Data by result.toData() { 17 | val floatProp by result.bool() 18 | val fooProp by result().asList() 19 | } 20 | 21 | class Inner1(result: GraphQlResult) : Super(), Data by result.toData() { 22 | val innerProp1: String? by result.string() 23 | } 24 | 25 | class Inner2(result: GraphQlResult) : Super(), Data by result.toData() { 26 | val innerProp1 by result.string() 27 | val innerobject by result() 28 | } 29 | 30 | class Root(result: GraphQlResult) : Data by result.toData() { 31 | val abstractSelect by result().asList() 32 | } 33 | 34 | class UnionRoot(result: GraphQlResult) : Data by result.toData() { 35 | val whatever by result().asList() 36 | } 37 | 38 | 39 | @Test fun bar() { 40 | 41 | val jvmReflectFragment = fragment(::Foo) 42 | 43 | val expect = """ 44 | |{ 45 | | floatProp 46 | | fooProp { 47 | | baz 48 | | } 49 | |} 50 | """.trimMargin("|") 51 | 52 | assertThat(jvmReflectFragment.toGraphQl(pretty = true, idAndTypeName = false)) 53 | .isEqualTo(expect) 54 | } 55 | 56 | 57 | @Test fun fragmentSpreadSomehowWorks() { 58 | 59 | val fragment = fragment(::Root) { 60 | Root::abstractSelect..{ 61 | on(::Inner1) 62 | on(::Inner2) 63 | } 64 | } 65 | 66 | val expect = """ 67 | |{ 68 | | abstractSelect { 69 | | ... on Inner1 { 70 | | innerProp1 71 | | } 72 | | ... on Inner2 { 73 | | innerProp1 74 | | innerobject { 75 | | innerProp1 76 | | } 77 | | } 78 | | } 79 | |} 80 | """.trimMargin("|") 81 | 82 | assertThat(fragment.toGraphQl(pretty = true, idAndTypeName = false)) 83 | .isEqualTo(expect) 84 | } 85 | 86 | @Test fun fragmentSpreadUnionWorks() { 87 | 88 | val inner1Frag = fragment(::Inner1) 89 | 90 | val query = fragment(::UnionRoot) { 91 | UnionRoot::whatever union { 92 | on(::Root) { 93 | Root::abstractSelect..{ 94 | on(::Inner2) 95 | on(::Inner1) 96 | } 97 | } 98 | on(::Inner2) 99 | on(inner1Frag) 100 | } 101 | } 102 | 103 | val queryString = query.toGraphQl( 104 | pretty = true, 105 | idAndTypeName = false) 106 | 107 | 108 | val expect = """ 109 | |{ 110 | | whatever { 111 | | ... on Root { 112 | | abstractSelect { 113 | | ... on Inner2 { 114 | | innerProp1 115 | | innerobject { 116 | | innerProp1 117 | | } 118 | | } 119 | | ... on Inner1 { 120 | | innerProp1 121 | | } 122 | | } 123 | | } 124 | | ... on Inner2 { 125 | | innerProp1 126 | | innerobject { 127 | | innerProp1 128 | | } 129 | | } 130 | | ... on Inner1 { 131 | | innerProp1 132 | | } 133 | | } 134 | |} 135 | """.trimMargin("|") 136 | 137 | 138 | assertThat(queryString).isEqualTo(expect) 139 | } 140 | } 141 | 142 | -------------------------------------------------------------------------------- /jvm/src/test/kotlin/Types.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.jvm 2 | 3 | import com.google.common.truth.Truth.assertThat 4 | import org.junit.Test 5 | import org.kotlinq.api.Kind 6 | import org.kotlinq.jvm.Validation.isValidValue 7 | 8 | class Types { 9 | 10 | private object Vars { 11 | val foo: String = "" 12 | val bar: String? = null 13 | val baz: List = emptyList() 14 | } 15 | 16 | @Test fun compatibilityChecks() { 17 | assertThat(Vars::foo.returnType.isCompatibleWith(null)) 18 | .isFalse() 19 | assertThat(Vars::bar.returnType.isCompatibleWith(null)) 20 | .isTrue() 21 | assertThat(Vars::baz.returnType.isCompatibleWith(emptyList())) 22 | .isTrue() 23 | assertThat(Vars::baz.returnType.isCompatibleWith(emptyList())) 24 | .isTrue() 25 | } 26 | 27 | @Test fun validatorValuesTest() { 28 | assertThat(isValidValue(Kind.string, "")).isTrue() 29 | assertThat(isValidValue(Kind.integer, 1)).isTrue() 30 | assertThat(isValidValue(Kind.float, 1.0f)).isTrue() 31 | assertThat(isValidValue(Kind.bool, 1.0f)).isFalse() 32 | assertThat(isValidValue(Kind.bool, false)).isTrue() 33 | assertThat(isValidValue(Kind.typeNamed("Any"), emptyMap())).isTrue() 34 | assertThat(isValidValue(Kind.typeNamed("Foo").asNullable(), null)).isTrue() 35 | assertThat(isValidValue(Kind.typeNamed("Foo").asNullable().asList(), null)).isFalse() 36 | assertThat(isValidValue(Kind.typeNamed("Foo").asList(), null)).isFalse() 37 | assertThat(isValidValue(Kind.typeNamed("Foo").asList(), listOf(emptyMap()))).isTrue() 38 | 39 | val tripleKind = 40 | Kind.typeNamed("Foo") 41 | .asList() 42 | .asList() 43 | .asList() 44 | 45 | val tripleList = listOf(listOf(listOf(emptyMap()))) 46 | 47 | assertThat(isValidValue(tripleKind, tripleList)).isTrue() 48 | 49 | val tripleKind2 = 50 | Kind.typeNamed("...") 51 | .asNullable() 52 | .asList() 53 | .asList() 54 | 55 | val tripleNull = listOf(listOf(null)) 56 | 57 | assertThat( 58 | isValidValue(tripleKind2, tripleNull) 59 | ).isTrue() 60 | 61 | assertThat(isValidValue(Kind.bool.asNullable(), false)).isFalse() 62 | assertThat(isValidValue(Kind.bool.asNullable(), null)).isTrue() 63 | assertThat(isValidValue(Kind.bool.asList().asList().asList(), emptyList())).isTrue() 64 | 65 | assertThat( 66 | isValidValue(tripleKind2.asList(), tripleNull) 67 | ).isFalse() 68 | 69 | } 70 | 71 | } -------------------------------------------------------------------------------- /ktlint.xml: -------------------------------------------------------------------------------- 1 | 17 | 18 | 19 | 20 | 25 | 28 | 29 | 36 | 37 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | # Project information 2 | site_name: Kotlinq 3 | site_description: GraphQL DSLs for Kotlin 4 | site_author: Preston Garno 5 | site_url: http://prestongarno.github.io/kotlinq 6 | 7 | # Repository 8 | repo_name: prestongarno/kotlinq 9 | repo_url: https://github.com/prestongarno/kotlinq 10 | 11 | # Copyright 12 | copyright: 'Copyright © 2017 Preston Garno' 13 | 14 | # Theme directory 15 | theme: material 16 | 17 | docs_dir: docs 18 | 19 | # Options 20 | extra: 21 | palette: 22 | primary: #003366 23 | accent: pink 24 | social: 25 | - type: globe 26 | link: http://prestongarno.com 27 | - type: github-alt 28 | link: https://github.com/prestongarno 29 | - type: twitter 30 | link: https://twitter.com/preston_dev 31 | - type: linkedin 32 | link: https://linkedin.com/in/prestongarno 33 | 34 | # Extensions 35 | markdown_extensions: 36 | - markdown.extensions.admonition 37 | - markdown.extensions.codehilite(guess_lang=true) 38 | - markdown.extensions.def_list 39 | - markdown.extensions.footnotes 40 | - markdown.extensions.meta 41 | - markdown.extensions.toc(permalink=true) 42 | - pymdownx.arithmatex 43 | - pymdownx.betterem(smart_enable=all) 44 | - pymdownx.caret 45 | - pymdownx.critic 46 | - pymdownx.details 47 | - pymdownx.emoji: 48 | emoji_generator: !!python/name:pymdownx.emoji.to_svg 49 | - pymdownx.inlinehilite 50 | - pymdownx.magiclink 51 | - pymdownx.mark 52 | - pymdownx.smartsymbols 53 | - pymdownx.superfences 54 | - pymdownx.tasklist(custom_checkbox=true) 55 | - pymdownx.tilde 56 | 57 | # Page tree 58 | pages: 59 | - Intro: index.md 60 | - Overview: core.md 61 | - DSL Tutorial: dsl.md 62 | - Contributing: contributing.md 63 | - License: license.md 64 | 65 | # Google Analytics 66 | google_analytics: 67 | - !!python/object/apply:os.getenv ["GOOGLE_ANALYTICS_KEY"] 68 | - auto 69 | -------------------------------------------------------------------------------- /schema/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | apply plugin: 'java' 3 | 4 | dependencies { 5 | compile project(':kotlinq-core') 6 | } 7 | 8 | sourceSets { 9 | main { 10 | java { 11 | srcDirs = ['src/main/kotlin', 12 | 'src/main/java'] 13 | } 14 | resources { 15 | srcDirs = ['src/main/resources'] 16 | } 17 | } 18 | test { 19 | java { 20 | srcDirs = ['src/test/kotlin', 21 | 'src/main/java'] 22 | } 23 | resources { 24 | srcDirs = ['src/test/resources'] 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /schema/src/main/kotlin/org/kotlinq/Model.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq 2 | 3 | import org.kotlinq.api.BindableContext 4 | import org.kotlinq.api.Kotlinq 5 | 6 | 7 | open class Model(val graphQlTypeName: String, val model: T) { 8 | 9 | 10 | var instanceBuilder: BindableContext? = Kotlinq.newContextBuilder() 11 | 12 | internal val fragment by lazy { 13 | val value = instanceBuilder!!.build(graphQlTypeName) 14 | instanceBuilder = null 15 | value 16 | } 17 | 18 | fun toGraphQl(pretty: Boolean = false, inlineFragments: Boolean = true): String = 19 | fragment.graphQlInstance.toGraphQl(pretty = pretty, inlineFragments = inlineFragments) 20 | 21 | } -------------------------------------------------------------------------------- /schema/src/main/kotlin/org/kotlinq/delegates/CollectionStubs.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.delegates 2 | 3 | import org.kotlinq.Model 4 | import org.kotlinq.dsl.ArgBuilder 5 | import org.kotlinq.dsl.ArgumentSpec 6 | import org.kotlinq.dsl.DslBuilder 7 | import org.kotlinq.providers.GraphQlPropertyProvider 8 | 9 | 10 | class CollectionStub1( 11 | name: String, 12 | args: ArgumentSpec? = null 13 | ) : CollectionPropertyStub(name, args) { 14 | 15 | override fun withArguments(arguments: ArgumentSpec) 16 | : CollectionStub1 = CollectionStub1(name, arguments) 17 | 18 | operator fun > invoke(init: () -> Z) 19 | : GraphQlPropertyProvider> = collectionProvider(name, init) 20 | 21 | operator fun > invoke(init: () -> Z, block: BuilderBlock) 22 | : GraphQlPropertyProvider> = collectionProvider(name, init, block) 23 | } 24 | 25 | 26 | 27 | class CollectionStub2( 28 | name: String, 29 | args: ArgumentSpec? = null 30 | ) : CollectionPropertyStub(name, args) { 31 | 32 | override fun withArguments(arguments: ArgumentSpec) 33 | : CollectionStub2 = CollectionStub2(name, arguments) 34 | 35 | operator fun > invoke(init: () -> Z) 36 | : GraphQlPropertyProvider>> = collectionProvider(name, init) 37 | 38 | operator fun > invoke(init: () -> Z, block: BuilderBlock) 39 | : GraphQlPropertyProvider>> = collectionProvider(name, init, block) 40 | } 41 | 42 | 43 | 44 | 45 | class CollectionStubN>>> private constructor( 46 | name: String, 47 | args: ArgumentSpec? = null, 48 | token: Builder<*, T>? = null 49 | ) : CollectionPropertyStub(name, args) { 50 | 51 | override fun withArguments(arguments: ArgumentSpec) 52 | : CollectionStub1 = CollectionStub1(name, arguments) 53 | 54 | operator fun invoke(init: () -> X) 55 | : GraphQlPropertyProvider = 56 | collectionProvider(name, init as () -> Model<*>) 57 | 58 | operator fun invoke(init: () -> X, block: DslBuilder.() -> Unit) 59 | : GraphQlPropertyProvider = TODO() 60 | 61 | companion object { 62 | 63 | fun from(ancestor: CollectionStub2) 64 | : CollectionStubN>>>> 65 | = CollectionStubN(ancestor.name, ancestor.args) 66 | 67 | fun >>> explicit( 68 | name: String, 69 | args: ArgBuilder = ArgBuilder(), 70 | token: Builder 71 | ): CollectionStubN = CollectionStubN(name, args, token) 72 | } 73 | } 74 | 75 | 76 | 77 | 78 | private 79 | fun > collectionProvider(name: String, init: () -> Model<*>): GraphQlPropertyProvider = 80 | collectionProvider(name, init, { /*nothing */ }) 81 | 82 | @Suppress("UNCHECKED_CAST") 83 | private 84 | fun , U : List<*>> collectionProvider( 85 | name: String, 86 | init: () -> T, 87 | block: DslBuilder.() -> Unit 88 | ): GraphQlPropertyProvider = TODO() 89 | -------------------------------------------------------------------------------- /schema/src/main/kotlin/org/kotlinq/delegates/GraphQlProperty.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.delegates 2 | 3 | import org.kotlinq.Model 4 | import org.kotlinq.api.Adapter 5 | import kotlin.properties.ReadOnlyProperty 6 | import kotlin.reflect.KProperty 7 | 8 | 9 | interface GraphQlProperty : ReadOnlyProperty, T> 10 | 11 | private 12 | class GraphQlPropertyImpl( 13 | val name: String 14 | ) : GraphQlProperty { 15 | 16 | override fun getValue(thisRef: Model<*>, property: KProperty<*>): T? { 17 | return thisRef.fragment.graphQlInstance.properties[name]?.let { 18 | @Suppress("UNCHECKED_CAST") 19 | it.getValue() as? T 20 | } 21 | } 22 | 23 | } 24 | 25 | @Suppress("UNCHECKED_CAST") 26 | internal 27 | fun Adapter.bind(inst: Model<*>): ReadOnlyProperty, T> { 28 | inst.instanceBuilder?.register(this) 29 | TODO() 30 | } 31 | -------------------------------------------------------------------------------- /schema/src/main/kotlin/org/kotlinq/delegates/PredicateStub.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.delegates 2 | 3 | import org.kotlinq.dsl.ArgumentSpec 4 | import kotlin.reflect.KClass 5 | 6 | class PredicateStub( 7 | private val name: String, 8 | private val clazz: KClass 9 | ) { 10 | 11 | fun withArguments(arguments: A): T = GraphQlPropertyStub.create(clazz, name, arguments) 12 | 13 | } -------------------------------------------------------------------------------- /schema/src/main/kotlin/org/kotlinq/dsl/ArgBuilder.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.dsl 2 | 3 | 4 | interface ArgumentSpec { 5 | fun take(argument: Pair) 6 | } 7 | 8 | open 9 | class ArgBuilder : ArgumentSpec { 10 | private val args = mutableMapOf() 11 | 12 | override fun take(argument: Pair) { 13 | args[argument.first] = argument.second 14 | } 15 | } -------------------------------------------------------------------------------- /schema/src/main/kotlin/org/kotlinq/dsl/DslBuilder.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.dsl 2 | 3 | 4 | interface DslBuilder { 5 | // TODO: val default: T? 6 | fun config(block: A.() -> Unit) 7 | } -------------------------------------------------------------------------------- /schema/src/main/kotlin/org/kotlinq/providers/DeserializingProvider.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.providers 2 | 3 | import org.kotlinq.Model 4 | import org.kotlinq.dsl.ArgBuilder 5 | import kotlin.reflect.KProperty 6 | 7 | class DeserializingProviderImpl( 8 | override val name: String, 9 | val init: (java.io.InputStream) -> Z 10 | ) : DslBuilderProvider { 11 | 12 | private val args: ArgBuilder = ArgBuilder() 13 | 14 | override fun config(block: ArgBuilder.() -> Unit) = args.block() 15 | 16 | override operator fun provideDelegate(inst: Model<*>, property: KProperty<*>) = TODO() 17 | } 18 | -------------------------------------------------------------------------------- /schema/src/main/kotlin/org/kotlinq/providers/GraphQlPropertyProvider.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.providers 2 | 3 | import org.kotlinq.Model 4 | import org.kotlinq.dsl.ArgBuilder 5 | import org.kotlinq.dsl.DslBuilder 6 | import kotlin.properties.ReadOnlyProperty 7 | import kotlin.reflect.KProperty 8 | 9 | interface GraphQlPropertyProvider { 10 | operator fun provideDelegate(inst: Model<*>, property: KProperty<*>) 11 | : ReadOnlyProperty, Z> 12 | } 13 | 14 | interface DslBuilderProvider : DslBuilder, GraphQlPropertyProvider { 15 | val name: String 16 | } 17 | 18 | -------------------------------------------------------------------------------- /schema/src/main/kotlin/org/kotlinq/providers/Initialized.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.providers 2 | 3 | import org.kotlinq.Model 4 | import org.kotlinq.api.Kotlinq 5 | import org.kotlinq.delegates.bind 6 | import org.kotlinq.dsl.ArgBuilder 7 | import kotlin.properties.ReadOnlyProperty 8 | import kotlin.reflect.KProperty 9 | 10 | 11 | class InitializingProviderImpl>( 12 | override val name: String, 13 | val init: () -> Z 14 | ) : DslBuilderProvider { 15 | 16 | private val args: ArgBuilder = ArgBuilder() 17 | 18 | override fun config(block: ArgBuilder.() -> Unit) = args.block() 19 | 20 | override operator fun provideDelegate(inst: Model<*>, property: KProperty<*>) 21 | : ReadOnlyProperty, Z> = TODO() 22 | 23 | } 24 | -------------------------------------------------------------------------------- /schema/src/main/kotlin/org/kotlinq/providers/ParsingProvider.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.providers 2 | 3 | import org.kotlinq.Model 4 | import org.kotlinq.api.Kotlinq 5 | import org.kotlinq.delegates.bind 6 | import org.kotlinq.dsl.ArgBuilder 7 | import kotlin.reflect.KProperty 8 | 9 | 10 | class ParsingGraphQlPropertyProvider( 11 | override val name: String, 12 | val init: (String) -> Z? 13 | ) : DslBuilderProvider { 14 | 15 | val args: ArgBuilder = ArgBuilder() 16 | 17 | override fun config(block: ArgBuilder.() -> Unit) = args.block() 18 | 19 | override fun provideDelegate(inst: Model<*>, property: KProperty<*>) = TODO() 20 | } 21 | -------------------------------------------------------------------------------- /schema/src/main/kotlin/org/kotlinq/providers/Providers.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.providers 2 | 3 | import org.kotlinq.Model 4 | import org.kotlinq.delegates.GraphQlProperty 5 | 6 | 7 | interface PropertyProviders { 8 | 9 | 10 | fun deserializingProvider(name: String, init: (java.io.InputStream) -> Z) 11 | : DslBuilderProvider 12 | 13 | fun parsingProvider(name: String, init: (String) -> Z?) 14 | : DslBuilderProvider 15 | 16 | fun > initializingProvider(name: String, init: () -> Z) 17 | : DslBuilderProvider 18 | 19 | } 20 | 21 | -------------------------------------------------------------------------------- /schema/src/main/kotlin/org/kotlinq/static/ContextBuilder.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.static 2 | 3 | import org.kotlinq.Model 4 | import org.kotlinq.delegates.CollectionPropertyStub 5 | import org.kotlinq.delegates.CollectionStub1 6 | import org.kotlinq.delegates.CollectionStub2 7 | import org.kotlinq.delegates.CollectionStubN 8 | import org.kotlinq.delegates.GraphQlPropertyStub 9 | import org.kotlinq.delegates.InitializingStub 10 | import org.kotlinq.dsl.ArgBuilder 11 | import org.kotlinq.dsl.ArgumentSpec 12 | import org.kotlinq.static.PredicateProvider.Companion.using 13 | import kotlin.reflect.KClass 14 | import kotlin.reflect.KProperty 15 | 16 | 17 | class ContextBuilder { 18 | 19 | fun requiringArguments(): ConfiguredContextBuilder, B> = 20 | buildWithArguments(InitializingStub::class) 21 | 22 | fun asList(): CollectionPropertyBuilder = 23 | CollectionPropertyBuilder() 24 | 25 | fun asNullable(): NullableTypeProvider = NullableTypeProvider() 26 | 27 | companion object { 28 | fun schema(): ContextBuilder = ContextBuilder() 29 | } 30 | } 31 | 32 | class ConfiguredContextBuilder(private val clazz: KClass) { 33 | fun build(): PredicateProvider = using().build(clazz) 34 | } 35 | 36 | class CollectionPropertyBuilder { 37 | 38 | fun requiringArguments() 39 | : ConfiguredContextBuilder, A> = 40 | buildWithArguments(CollectionStub1::class) 41 | 42 | fun asList() = Nested(this) 43 | 44 | fun build(): Provider> = Provider.provideCollection(this) 45 | 46 | 47 | /* Builder for a property delegate returning a property providing a nested list */ 48 | class Nested (context: CollectionPropertyBuilder) { 49 | 50 | fun asList() = MultiDimensional.from(this) 51 | 52 | fun build(): Provider> = object : Provider> { 53 | override fun provideDelegate(inst: Any, property: KProperty<*>) = 54 | readOnly(CollectionStub2(property.name, ArgBuilder())) 55 | } 56 | 57 | fun requiringArguments() 58 | : ConfiguredContextBuilder, A> = 59 | buildWithArguments(CollectionStub2::class) 60 | } 61 | 62 | class MultiDimensional>>> (val builder: CollectionPropertyStub.Builder) { 63 | 64 | fun asList() = MultiDimensional(builder.asList()) 65 | 66 | fun build(): Provider> = object : Provider> { 67 | override fun provideDelegate(inst: Any, property: KProperty<*>) = 68 | readOnly(builder.build(property.name)) 69 | } 70 | 71 | fun requiringArguments() 72 | : ConfiguredContextBuilder>>>>, A> = 73 | buildWithArguments(CollectionStub2::class) 74 | 75 | companion object { 76 | internal fun from(nestedBuilder: Nested) = MultiDimensional( 77 | CollectionPropertyStub.Builder, List>>>>(listOf())) 78 | } 79 | } 80 | 81 | } 82 | 83 | class NullableTypeProvider { 84 | fun requiringArguments(): ConfiguredContextBuilder, A> = TODO() 85 | fun build(): GraphQlPropertyStub.Disjoint = TODO() 86 | } 87 | 88 | 89 | @Suppress("UNCHECKED_CAST") 90 | internal 91 | fun buildWithArguments(clazz: KClass) 92 | : ConfiguredContextBuilder = 93 | ConfiguredContextBuilder(clazz as KClass) 94 | -------------------------------------------------------------------------------- /schema/src/main/kotlin/org/kotlinq/static/Static.kt: -------------------------------------------------------------------------------- 1 | package org.kotlinq.static 2 | 3 | import org.kotlinq.delegates.CollectionPropertyStub 4 | import org.kotlinq.delegates.CollectionStub1 5 | import org.kotlinq.delegates.CollectionStubN 6 | import org.kotlinq.delegates.DeserializingStub 7 | import org.kotlinq.delegates.EnumStub 8 | import org.kotlinq.delegates.GraphQlPropertyStub 9 | import org.kotlinq.delegates.InitializingStub 10 | import org.kotlinq.delegates.PredicateStub 11 | import org.kotlinq.dsl.ArgBuilder 12 | import org.kotlinq.dsl.ArgumentSpec 13 | import kotlin.properties.ReadOnlyProperty 14 | import kotlin.reflect.KClass 15 | import kotlin.reflect.KProperty 16 | 17 | 18 | internal 19 | fun readOnly(value: T): ReadOnlyProperty = ReadOnlyImpl(value) 20 | 21 | interface Provider { 22 | operator fun provideDelegate(inst: Any, property: KProperty<*>): ReadOnlyProperty 23 | 24 | companion object { 25 | 26 | internal 27 | fun provideCollection(builder: CollectionPropertyBuilder): Provider> = 28 | 29 | object : Provider> { 30 | 31 | override fun provideDelegate(inst: Any, property: KProperty<*>) 32 | : ReadOnlyProperty> = 33 | readOnly(CollectionStub1(property.name, ArgBuilder())) 34 | } 35 | 36 | internal 37 | fun >>> nDimensional(builder: CollectionPropertyStub.Builder) = 38 | object : Provider> { 39 | override fun provideDelegate(inst: Any, property: KProperty<*>) = 40 | readOnly(builder.build(property.name, ArgBuilder())) 41 | } 42 | 43 | } 44 | } 45 | 46 | interface ConfiguredProvider : Provider> { 47 | override operator fun provideDelegate(inst: Any, property: KProperty<*>) 48 | : ReadOnlyProperty> 49 | } 50 | 51 | interface PredicateProvider { 52 | 53 | operator fun provideDelegate(inst: Any, property: KProperty<*>): ReadOnlyProperty> 54 | 55 | companion object { 56 | 57 | internal fun using() = Builder() 58 | 59 | internal class Builder { 60 | fun build(clazz: KClass): PredicateProvider = 61 | createPredicateProvider(clazz) 62 | } 63 | 64 | private 65 | fun createPredicateProvider(clazz: KClass) = object : PredicateProvider { 66 | override fun provideDelegate(inst: Any, property: KProperty<*>) = 67 | readOnly(PredicateStub(property.name, clazz)) 68 | } 69 | 70 | } 71 | } 72 | 73 | internal 74 | fun deserialized(): Provider = 75 | StubProvider(DeserializingStub::class) 76 | 77 | @Suppress("UNCHECKED_CAST") 78 | internal 79 | fun initialized(): Provider> = 80 | StubProvider(InitializingStub::class) as StubProvider> 81 | 82 | internal 83 | fun > enumMapper(): Provider> = EnumProvider() 84 | 85 | 86 | private 87 | class ReadOnlyImpl(val value: T) : ReadOnlyProperty { 88 | override operator fun getValue(thisRef: Any, property: KProperty<*>): T = value 89 | } 90 | 91 | private 92 | class StubProvider(val dslClass: KClass) : Provider { 93 | 94 | override operator fun provideDelegate( 95 | inst: Any, 96 | property: KProperty<*> 97 | ): ReadOnlyProperty = 98 | readOnly(GraphQlPropertyStub.create(dslClass, property.name)) 99 | } 100 | 101 | private 102 | class EnumProvider> : Provider> { 103 | @Suppress("UNCHECKED_CAST") 104 | override fun provideDelegate(inst: Any, property: KProperty<*>): ReadOnlyProperty> = 105 | readOnly(GraphQlPropertyStub.create(EnumStub::class as KClass>, property.name)) 106 | } -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | 2 | rootProject.name = 'kotlinq' 3 | include 'core' 4 | include 'dsl' 5 | include 'jvm' 6 | //include 'kotlinq-http' 7 | //TODO finish core, untyped DSL, and DSL before compiler refactor 8 | //include 'kotlinq-gradle' 9 | //include 'kotlinq-schema' 10 | 11 | project(':core').name = rootProject.name + '-core' 12 | project(':dsl').name = rootProject.name + '-dsl' 13 | project(':jvm').name = rootProject.name + '-jvm' 14 | --------------------------------------------------------------------------------