├── .github ├── FUNDING.yml └── workflows │ ├── documentation.yml │ └── swift.yml ├── .gitignore ├── .jazzy.yml ├── .swiftpm └── xcode │ └── package.xcworkspace │ └── contents.xcworkspacedata ├── LICENSE ├── Package.resolved ├── Package.swift ├── README.md ├── Sources ├── CContext │ ├── CContext.c │ └── include │ │ ├── CContext.h │ │ └── CContext.h.gyb ├── GraphZahl │ ├── Resolution │ │ ├── InputResolvable │ │ │ ├── Implementations │ │ │ │ ├── Array+InputResolvable.swift │ │ │ │ ├── GraphQLInputObject.swift │ │ │ │ ├── JSONOptional.swift │ │ │ │ ├── Optional+InputResolvable.swift │ │ │ │ └── RawRepresentable+InputResolvable.swift │ │ │ └── InputResolvable.swift │ │ ├── OutputResolvable │ │ │ ├── DelegatedOutputResolvable.swift │ │ │ ├── Implementations │ │ │ │ ├── Array+OutputResolvable.swift │ │ │ │ ├── Connection │ │ │ │ │ ├── ConnectionProtocol.swift │ │ │ │ │ ├── ConnectionWrapper.swift │ │ │ │ │ ├── ContextBasedConnection.swift │ │ │ │ │ ├── ContextBasedConnectionWrapper.swift │ │ │ │ │ ├── EdgeProtocol.swift │ │ │ │ │ ├── File.swift │ │ │ │ │ ├── FixedPageSizeIndexedConnection.swift │ │ │ │ │ ├── IndexConnectionWrapper.swift │ │ │ │ │ ├── IndexCursorTranslator.swift │ │ │ │ │ ├── IndexedConnection.swift │ │ │ │ │ ├── PageInfo.swift │ │ │ │ │ └── StandardEdge.swift │ │ │ │ ├── Future+OutputResolvable.swift │ │ │ │ ├── Object │ │ │ │ │ ├── GraphQLObject+OutputResolvable.swift │ │ │ │ │ ├── GraphQLObject+resolve.swift │ │ │ │ │ ├── GraphQLObject.swift │ │ │ │ │ └── PropertyWrappers │ │ │ │ │ │ ├── CustomGraphQLProperty.swift │ │ │ │ │ │ ├── Ignore.swift │ │ │ │ │ │ ├── Inline.swift │ │ │ │ │ │ ├── InlineAsInterface.swift │ │ │ │ │ │ ├── LazyInline.swift │ │ │ │ │ │ ├── LazyInlineAsInterface.swift │ │ │ │ │ │ └── PropertyResult.swift │ │ │ │ └── Optional+OutputResolvable.swift │ │ │ ├── Node.swift │ │ │ ├── Output.swift │ │ │ └── OutputResolvable.swift │ │ ├── Resolution.swift │ │ ├── Resolvable │ │ │ ├── ConcreteResolvable.swift │ │ │ ├── Implementations │ │ │ │ ├── Array+Resolvable.swift │ │ │ │ ├── Future+Resolvable.swift │ │ │ │ └── Optional+Resolvable.swift │ │ │ ├── KeyPathListable.swift │ │ │ ├── Resolvable.swift │ │ │ └── ValueResolvable.swift │ │ ├── Root │ │ │ ├── EmptyRootType.swift │ │ │ ├── GraphQLRootType.swift │ │ │ ├── GraphQLSchema+perform.swift │ │ │ ├── GraphQLSchema+resolve.swift │ │ │ ├── GraphQLSchema.swift │ │ │ ├── MandatoryRootType.swift │ │ │ ├── MutationType.swift │ │ │ └── QueryType.swift │ │ ├── Scalar │ │ │ ├── Enum │ │ │ │ ├── GraphQLEnum.swift │ │ │ │ └── KeyPath+GraphQLEnum.swift │ │ │ ├── Scalar │ │ │ │ ├── GraphQLScalar+InputResolvable.swift │ │ │ │ ├── GraphQLScalar+OutputResolvable.swift │ │ │ │ ├── GraphQLScalar.swift │ │ │ │ ├── Implementations │ │ │ │ │ ├── Bool+Scalar.swift │ │ │ │ │ ├── Double+Scalar.swift │ │ │ │ │ ├── Float+Scalar.swift │ │ │ │ │ ├── Int+Scalar.swift │ │ │ │ │ ├── String+Scalar.swift │ │ │ │ │ └── UUID+Scalar.swift │ │ │ │ └── RawRepresentable+GraphQLScalar.swift │ │ │ ├── ScalarTypeError.swift │ │ │ ├── Type │ │ │ │ └── ScalarType.swift │ │ │ └── Value │ │ │ │ ├── IDValue.swift │ │ │ │ ├── ScalarValue+decode.swift │ │ │ │ ├── ScalarValue+init.swift │ │ │ │ └── ScalarValue.swift │ │ └── Union │ │ │ └── GraphQLUnionType.swift │ └── Utils │ │ ├── ContextKey+ViewerContext.swift │ │ ├── Dictionary+getOrPut.swift │ │ ├── Lock.swift │ │ ├── Method Dispatching │ │ ├── Default Value │ │ │ ├── Argument+defaultValue.swift │ │ │ └── Argument+defaultValue.swift.gyb │ │ ├── MethodInfo+call.swift │ │ ├── MethodInfo+call.swift.gyb │ │ ├── Register Usage │ │ │ ├── RegisterUsage.swift │ │ │ ├── RegisterUsage.swift.gyb │ │ │ ├── Resolvable │ │ │ │ ├── MethodCallResolvable.swift │ │ │ │ ├── MethodCallResolvable.swift.gyb │ │ │ │ └── SpecialCases.swift │ │ │ └── Resolve │ │ │ │ ├── FunctionResultDecoder.swift │ │ │ │ └── resolveArguments.swift │ │ ├── typeInfo.swift │ │ └── typeInfo.swift.gyb │ │ ├── MethodInfo+resolve.swift │ │ ├── PropertyInfo+resolve.swift │ │ ├── String+camelCasing.swift │ │ └── String+deletingPrefix.swift └── GraphZahlTests │ ├── CallingTests.swift │ └── SchemaResolutionTests.swift ├── demo ├── helloworld.png ├── nestedobject.png ├── object.png └── urlscalar.png ├── logo.png └── useGYB /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [nerdsupremacist] 2 | -------------------------------------------------------------------------------- /.github/workflows/documentation.yml: -------------------------------------------------------------------------------- 1 | name: Documentation 2 | 3 | on: 4 | push: 5 | branches: [ develop ] 6 | 7 | jobs: 8 | deploy_docs: 9 | runs-on: macos-latest 10 | steps: 11 | - uses: actions/checkout@v1 12 | - name: Publish Jazzy Docs 13 | uses: steven0351/publish-jazzy-docs@v1.1.1 14 | with: 15 | personal_access_token: ${{ secrets.ACCESS_TOKEN }} 16 | config: .jazzy.yml 17 | -------------------------------------------------------------------------------- /.github/workflows/swift.yml: -------------------------------------------------------------------------------- 1 | name: Swift 2 | 3 | on: 4 | push: 5 | branches: [ develop ] 6 | pull_request: 7 | branches: [ develop ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: macos-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Build 17 | run: swift build -v 18 | - name: Run tests 19 | run: swift test -v 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 9 | *.xcscmblueprint 10 | *.xccheckout 11 | 12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 13 | build/ 14 | DerivedData/ 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | 28 | ## App packaging 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | # Packages/ 41 | # Package.pins 42 | # Package.resolved 43 | # *.xcodeproj 44 | # 45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 46 | # hence it is not needed unless you have added a package configuration file to your project 47 | # .swiftpm 48 | 49 | .build/ 50 | 51 | # CocoaPods 52 | # 53 | # We recommend against adding the Pods directory to your .gitignore. However 54 | # you should judge for yourself, the pros and cons are mentioned at: 55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 56 | # 57 | # Pods/ 58 | # 59 | # Add this line if you want to avoid checking in source code from the Xcode workspace 60 | # *.xcworkspace 61 | 62 | # Carthage 63 | # 64 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 65 | # Carthage/Checkouts 66 | 67 | Carthage/Build/ 68 | 69 | # Accio dependency management 70 | Dependencies/ 71 | .accio/ 72 | 73 | # fastlane 74 | # 75 | # It is recommended to not store the screenshots in the git repo. 76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 77 | # For more information about the recommended setup visit: 78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 79 | 80 | fastlane/report.xml 81 | fastlane/Preview.html 82 | fastlane/screenshots/**/*.png 83 | fastlane/test_output 84 | 85 | # Code Injection 86 | # 87 | # After new code Injection tools there's a generated folder /iOSInjectionProject 88 | # https://github.com/johnno1962/injectionforxcode 89 | 90 | iOSInjectionProject/ 91 | -------------------------------------------------------------------------------- /.jazzy.yml: -------------------------------------------------------------------------------- 1 | module: GraphZahl 2 | author: Mathias Quintero 3 | theme: fullwidth 4 | output: ./docs 5 | documentation: ./*.md 6 | author_url: https://github.com/nerdsupremacist 7 | github_url: https://github.com/nerdsupremacist/GraphZahl 8 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Mathias Quintero 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "ContextKit", 6 | "repositoryURL": "https://github.com/nerdsupremacist/ContextKit.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "d68c64dd4ed4c616bb5fd58f9d19d939a1735f20", 10 | "version": "0.2.1" 11 | } 12 | }, 13 | { 14 | "package": "CRuntime", 15 | "repositoryURL": "https://github.com/nerdsupremacist/CRuntime.git", 16 | "state": { 17 | "branch": null, 18 | "revision": "95f911318d8c885f6fc05e971471f94adfd39405", 19 | "version": "2.1.2" 20 | } 21 | }, 22 | { 23 | "package": "CwlDemangle", 24 | "repositoryURL": "https://github.com/nerdsupremacist/CwlDemangle.git", 25 | "state": { 26 | "branch": null, 27 | "revision": "ca1ffdc206f76dc5f217f27ea2a0bbd2241a4a92", 28 | "version": "0.1.1-beta.1" 29 | } 30 | }, 31 | { 32 | "package": "GraphQL", 33 | "repositoryURL": "https://github.com/nerdsupremacist/GraphQL.git", 34 | "state": { 35 | "branch": null, 36 | "revision": "253b6efa4fdac99a0feef631023232729864a7c1", 37 | "version": "0.12.1-beta.6" 38 | } 39 | }, 40 | { 41 | "package": "Runtime", 42 | "repositoryURL": "https://github.com/nerdsupremacist/Runtime.git", 43 | "state": { 44 | "branch": null, 45 | "revision": "32cc77be43c34d95d3fe3d21035b21472e03542a", 46 | "version": "2.1.2-beta.13" 47 | } 48 | }, 49 | { 50 | "package": "swift-nio", 51 | "repositoryURL": "https://github.com/apple/swift-nio.git", 52 | "state": { 53 | "branch": null, 54 | "revision": "cf6f2b1807219643270616e03de697b96c46384c", 55 | "version": "2.21.0" 56 | } 57 | } 58 | ] 59 | }, 60 | "version": 1 61 | } 62 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "GraphZahl", 8 | platforms: [ 9 | .macOS(.v10_15) 10 | ], 11 | products: [ 12 | .library(name: "GraphZahl", 13 | targets: ["GraphZahl"]), 14 | ], 15 | dependencies: [ 16 | .package(url: "https://github.com/nerdsupremacist/GraphQL.git", from: "0.12.1-beta.6"), 17 | .package(url: "https://github.com/nerdsupremacist/Runtime.git", from: "2.1.2-beta.13"), 18 | .package(url: "https://github.com/nerdsupremacist/ContextKit.git", from: "0.2.1"), 19 | ], 20 | targets: [ 21 | .target( 22 | name: "GraphZahl", 23 | dependencies: ["GraphQL", "Runtime", "CContext", "ContextKit"] 24 | ), 25 | 26 | .testTarget( 27 | name: "GraphZahlTests", 28 | dependencies: ["GraphZahl"]), 29 | 30 | .target(name: "CContext"), 31 | ] 32 | ) 33 | -------------------------------------------------------------------------------- /Sources/CContext/CContext.c: -------------------------------------------------------------------------------- 1 | 2 | void set_self_pointer(void *pointer) { 3 | #ifdef __aarch64__ 4 | __asm__("mov x20, x0"); 5 | #endif 6 | #ifdef __x86_64__ 7 | __asm__("movq %rdi, %r13"); 8 | #endif 9 | } 10 | -------------------------------------------------------------------------------- /Sources/CContext/include/CContext.h: -------------------------------------------------------------------------------- 1 | // This file was automatically generated and should not be edited. 2 | 3 | #ifndef selfcontext_h 4 | #define selfcontext_h 5 | 6 | 7 | void set_self_pointer(void *pointer); 8 | 9 | 10 | struct int_function_result { 11 | long res0; 12 | }; 13 | 14 | 15 | struct float_function_result { 16 | double res0; 17 | }; 18 | 19 | 20 | struct int_int_function_result { 21 | long res0; 22 | long res1; 23 | }; 24 | 25 | 26 | struct int_float_function_result { 27 | long res0; 28 | double res1; 29 | }; 30 | 31 | 32 | struct float_float_function_result { 33 | double res0; 34 | double res1; 35 | }; 36 | 37 | 38 | struct int_int_int_function_result { 39 | long res0; 40 | long res1; 41 | long res2; 42 | }; 43 | 44 | 45 | struct int_int_float_function_result { 46 | long res0; 47 | long res1; 48 | double res2; 49 | }; 50 | 51 | 52 | struct int_float_float_function_result { 53 | long res0; 54 | double res1; 55 | double res2; 56 | }; 57 | 58 | 59 | struct float_float_float_function_result { 60 | double res0; 61 | double res1; 62 | double res2; 63 | }; 64 | 65 | 66 | struct int_int_int_int_function_result { 67 | long res0; 68 | long res1; 69 | long res2; 70 | long res3; 71 | }; 72 | 73 | 74 | struct int_int_int_float_function_result { 75 | long res0; 76 | long res1; 77 | long res2; 78 | double res3; 79 | }; 80 | 81 | 82 | struct int_int_float_float_function_result { 83 | long res0; 84 | long res1; 85 | double res2; 86 | double res3; 87 | }; 88 | 89 | 90 | struct int_float_float_float_function_result { 91 | long res0; 92 | double res1; 93 | double res2; 94 | double res3; 95 | }; 96 | 97 | 98 | struct float_float_float_float_function_result { 99 | double res0; 100 | double res1; 101 | double res2; 102 | double res3; 103 | }; 104 | 105 | 106 | struct int_int_int_int_float_function_result { 107 | long res0; 108 | long res1; 109 | long res2; 110 | long res3; 111 | double res4; 112 | }; 113 | 114 | 115 | struct int_int_int_float_float_function_result { 116 | long res0; 117 | long res1; 118 | long res2; 119 | double res3; 120 | double res4; 121 | }; 122 | 123 | 124 | struct int_int_float_float_float_function_result { 125 | long res0; 126 | long res1; 127 | double res2; 128 | double res3; 129 | double res4; 130 | }; 131 | 132 | 133 | struct int_float_float_float_float_function_result { 134 | long res0; 135 | double res1; 136 | double res2; 137 | double res3; 138 | double res4; 139 | }; 140 | 141 | 142 | struct int_int_int_int_float_float_function_result { 143 | long res0; 144 | long res1; 145 | long res2; 146 | long res3; 147 | double res4; 148 | double res5; 149 | }; 150 | 151 | 152 | struct int_int_int_float_float_float_function_result { 153 | long res0; 154 | long res1; 155 | long res2; 156 | double res3; 157 | double res4; 158 | double res5; 159 | }; 160 | 161 | 162 | struct int_int_float_float_float_float_function_result { 163 | long res0; 164 | long res1; 165 | double res2; 166 | double res3; 167 | double res4; 168 | double res5; 169 | }; 170 | 171 | 172 | struct int_int_int_int_float_float_float_function_result { 173 | long res0; 174 | long res1; 175 | long res2; 176 | long res3; 177 | double res4; 178 | double res5; 179 | double res6; 180 | }; 181 | 182 | 183 | struct int_int_int_float_float_float_float_function_result { 184 | long res0; 185 | long res1; 186 | long res2; 187 | double res3; 188 | double res4; 189 | double res5; 190 | double res6; 191 | }; 192 | 193 | 194 | struct int_int_int_int_float_float_float_float_function_result { 195 | long res0; 196 | long res1; 197 | long res2; 198 | long res3; 199 | double res4; 200 | double res5; 201 | double res6; 202 | double res7; 203 | }; 204 | 205 | 206 | #endif 207 | -------------------------------------------------------------------------------- /Sources/CContext/include/CContext.h.gyb: -------------------------------------------------------------------------------- 1 | // This file was automatically generated and should not be edited. 2 | 3 | #ifndef selfcontext_h 4 | #define selfcontext_h 5 | 6 | %{ 7 | def _combos(elements, start_idx, length): 8 | # ignore elements before start_idx 9 | for i in range(start_idx, len(elements)): 10 | elem, count = elements[i] 11 | if count == 0: 12 | continue 13 | # base case: only one element needed 14 | if length == 1: 15 | yield (elem,) 16 | else: 17 | # need more than one elem: mutate the list and recurse 18 | elements[i] = (elem, count - 1) 19 | # when we recurse, we ignore elements before this one 20 | # this ensures we find combinations, not permutations 21 | for combo in _combos(elements, i, length - 1): 22 | yield (elem,) + combo 23 | # fix the list 24 | elements[i] = (elem, count) 25 | 26 | 27 | def combos(elements, length): 28 | if length == 0: 29 | return [[]] 30 | elements = list(elements.items()) 31 | return _combos(elements, 0, length) 32 | 33 | allowedResults = 8 34 | 35 | allowedTypes = { "int" : "long", "float" : "double" } 36 | allowedResultMap = { "int" : 4, "float" : 4 } 37 | }% 38 | 39 | void set_self_pointer(void *pointer); 40 | 41 | % for numberOfResults in range(1, allowedResults + 1): 42 | % for combination in combos(allowedResultMap, numberOfResults): 43 | 44 | %{ name = "_".join(combination) }% 45 | struct ${name}_function_result { 46 | % for index, item in enumerate(combination): 47 | ${allowedTypes[item]} res${index}; 48 | % end 49 | }; 50 | 51 | % end 52 | % end 53 | 54 | #endif 55 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/InputResolvable/Implementations/Array+InputResolvable.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import GraphQL 4 | 5 | /** 6 | # Conditional Conformance 7 | 8 | All arrays of values that can be translated to a GraphQL Value, can themselves be translated to a GraphQL Value 9 | */ 10 | extension Array: ValueResolvable where Element: ValueResolvable { 11 | 12 | public func map() throws -> Map { 13 | return .array(try map { try $0.map() }) 14 | } 15 | 16 | } 17 | 18 | /** 19 | # Conditional Conformance 20 | 21 | All arrays of values that are GraphQL Inputs, can be Inputs themselves 22 | */ 23 | extension Array: InputResolvable where Element: InputResolvable { 24 | 25 | public static func resolve(using context: inout Resolution.Context) throws -> GraphQLInputType { 26 | return GraphQLNonNull(GraphQLList(try context.resolve(type: Element.self))) 27 | } 28 | 29 | public static func create(from map: Map) throws -> Array { 30 | return try map.arrayValue().map { try Element.create(from: $0) } 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/InputResolvable/Implementations/GraphQLInputObject.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import GraphQL 4 | import NIO 5 | import Runtime 6 | 7 | private var propertiesForType = [Int : [String : PropertyInfo]]() 8 | 9 | /** 10 | # GraphQLInputObject 11 | 12 | A GraphQL Input Object is a Struct that can be used as an input for a method. 13 | 14 | ## Example 15 | 16 | If want to provide an API for sorting search results. We can create a struct for those options. 17 | 18 | ```swift 19 | enum Order: String, CaseIterable, GraphQLEnum { 20 | case ascending 21 | case descending 22 | } 23 | 24 | struct SortOptions: GraphQLInputType { 25 | let field: KeyPath 26 | let order: Order 27 | } 28 | 29 | class Query: QueryType { 30 | func search(term: String, sortOptions: SortOptions?) -> [SearchResult] { 31 | ... 32 | } 33 | } 34 | ``` 35 | 36 | ## Note 37 | 38 | Input types can be useful to group arguments. We can also use them to group arguments that should always be provided together. Like in our search example, we don't need the user to specify how to sort. But when the user wants the Results sorted by a field, we need to know if they should be sorted in an ascending or descending order. That's why the Input Object makes sense in this scenario. 39 | 40 | ## Details 41 | 42 | - A GraphQL Input Object is Input Resolvable and can be used in any method that should be available in GraphQL 43 | - An Input Object has to be a struct. If it's not a struct, an error will be thrown. 44 | - All the properties in the struct have to be Input Resolvable as well. Otherwise an error will be thrown. 45 | */ 46 | public protocol GraphQLInputObject: InputResolvable, ConcreteResolvable, ValueResolvable, KeyPathListable { } 47 | 48 | extension GraphQLInputObject { 49 | 50 | /** 51 | - Warning: default implementation from `GraphZahl`. Do not override unless you know exactly what you are doing. 52 | */ 53 | public static func resolve(using context: inout Resolution.Context) throws -> GraphQLInputType { 54 | let kind = try typeInfo(of: Self.self, .kind) 55 | guard kind == .struct else { throw Resolution.Error.inputObjectIsNotAStruct(type: Self.self) } 56 | 57 | let fields = try properties().mapValues { property -> InputObjectField in 58 | guard let type = property.type as? InputResolvable.Type else { 59 | throw Resolution.Error.invalidPropertyInInputObject(name: property.name, type: property.type, ownerType: Self.self) 60 | } 61 | return InputObjectField(type: try context.resolve(type: type)) 62 | } 63 | 64 | return GraphQLNonNull(try GraphQLInputObjectType(name: concreteTypeName, fields: fields)) 65 | } 66 | 67 | /** 68 | - Warning: default implementation from `GraphZahl`. Do not override unless you know exactly what you are doing. 69 | */ 70 | public static func create(from map: Map) throws -> Self { 71 | let dictionary = try map.dictionaryValue() 72 | return try createInstance { property in 73 | guard let type = property.type as? InputResolvable.Type else { 74 | throw Resolution.Error.invalidPropertyInInputObject(name: property.name, type: property.type, ownerType: Self.self) 75 | } 76 | let value = dictionary[property.name.deleting(prefix: "_")] 77 | if let value = value { 78 | return try type.create(from: value) 79 | } else { 80 | return try type.createFromMissingKey() 81 | } 82 | } 83 | } 84 | 85 | /** 86 | - Warning: default implementation from `GraphZahl`. Do not override unless you know exactly what you are doing. 87 | */ 88 | public func map() throws -> Map { 89 | let dictionary = try Self.properties().compactMapValues { property -> Map? in 90 | guard let value = try property.get(from: self) as? ValueResolvable else { 91 | throw Resolution.Error.invalidPropertyInInputObject(name: property.name, type: property.type, ownerType: Self.self) 92 | } 93 | return try value.map() 94 | } 95 | return .dictionary(dictionary) 96 | } 97 | 98 | private static func properties() throws -> [String : PropertyInfo] { 99 | return try propertiesForType.getOrPut(unsafeBitCast(Self.self, to: Int.self)) { 100 | let properties = try typeInfo(of: Self.self, .properties) 101 | return Dictionary(properties.map { ($0.name.deleting(prefix: "_"), $0) }) { first, _ in first } 102 | } 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/InputResolvable/Implementations/JSONOptional.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import GraphQL 4 | 5 | public enum JSONOptional { 6 | case missingKey 7 | case null 8 | case some(Wrapped) 9 | } 10 | 11 | extension JSONOptional { 12 | 13 | public func map(_ transform: (Wrapped) throws -> T) rethrows -> JSONOptional { 14 | switch self { 15 | case .missingKey: 16 | return .missingKey 17 | case .null: 18 | return .null 19 | case .some(let wrapped): 20 | return .some(try transform(wrapped)) 21 | } 22 | } 23 | 24 | public func flatMap(_ transform: (Wrapped) throws -> JSONOptional) rethrows -> JSONOptional { 25 | switch self { 26 | case .missingKey: 27 | return .missingKey 28 | case .null: 29 | return .null 30 | case .some(let wrapped): 31 | return try transform(wrapped) 32 | } 33 | } 34 | 35 | } 36 | 37 | extension JSONOptional: Resolvable where Wrapped: Resolvable { } 38 | 39 | extension JSONOptional: InputResolvable where Wrapped: InputResolvable { 40 | public static func resolve(using context: inout Resolution.Context) throws -> GraphQLInputType { 41 | return try context.resolve(type: Optional.self) 42 | } 43 | 44 | public static func create(from map: Map) throws -> JSONOptional { 45 | switch map { 46 | case .null: 47 | return .null 48 | default: 49 | return .some(try Wrapped.create(from: map)) 50 | } 51 | } 52 | 53 | public static func createFromMissingKey() throws -> JSONOptional { 54 | return .missingKey 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/InputResolvable/Implementations/Optional+InputResolvable.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import GraphQL 4 | 5 | /** 6 | # Conditional Conformance 7 | 8 | All optionals of values that can be translated to a GraphQL Value, can themselves be translated to a GraphQL Value 9 | */ 10 | extension Optional: ValueResolvable where Wrapped: ValueResolvable { 11 | 12 | public func map() throws -> Map { 13 | return try map { try $0.map() } ?? .null 14 | } 15 | 16 | } 17 | 18 | /** 19 | # Conditional Conformance 20 | 21 | All optionals of values that are GraphQL Inputs, can be Inputs themselves 22 | */ 23 | extension Optional: InputResolvable where Wrapped: InputResolvable { 24 | 25 | public static func resolve(using context: inout Resolution.Context) throws -> GraphQLInputType { 26 | switch try context.resolve(type: Wrapped.self) { 27 | case let resolved as GraphQLNonNull: 28 | guard let type = resolved.ofType as? GraphQLInputType else { fatalError() } 29 | return type 30 | case let type: 31 | return type 32 | } 33 | } 34 | 35 | public static func create(from map: Map) throws -> Optional { 36 | switch map { 37 | case .null: 38 | return .none 39 | default: 40 | return try Wrapped.create(from: map) 41 | } 42 | } 43 | } 44 | 45 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/InputResolvable/Implementations/RawRepresentable+InputResolvable.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import GraphQL 4 | 5 | /** 6 | # Conditional Conformance 7 | 8 | All raw representables of values that can be translated to a GraphQL Value, can themselves be translated to a GraphQL Value 9 | */ 10 | extension RawRepresentable where Self: ValueResolvable, RawValue: ValueResolvable { 11 | 12 | /** 13 | - Warning: default implementation from `GraphZahl`. Do not override unless you know exactly what you are doing. 14 | */ 15 | public func map() throws -> Map { 16 | return try rawValue.map() 17 | } 18 | 19 | } 20 | 21 | /** 22 | # Conditional Conformance 23 | 24 | All raw representables of values that are GraphQL Inputs, can be Inputs themselves 25 | */ 26 | extension RawRepresentable where Self: InputResolvable, RawValue: InputResolvable { 27 | 28 | /** 29 | - Warning: default implementation from `GraphZahl`. Do not override unless you know exactly what you are doing. 30 | */ 31 | public static func create(from map: Map) throws -> Self { 32 | let rawValue = try RawValue.create(from: map) 33 | guard let value = Self(rawValue: rawValue) else { 34 | throw Resolution.Error.cannotInstantiateTypeFromRawValue(Self.self, rawValue: rawValue) 35 | } 36 | return value 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/InputResolvable/InputResolvable.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import GraphQL 4 | 5 | /** 6 | - Attention: this is a low level API. Do not use this directly unless you know what you're doing. 7 | 8 | # InputResolvable 9 | 10 | Input Resolvable describes an Object that can be used as an Argument to a GraphQL Field. 11 | 12 | It means that for a method to be available in GraphQL, all the arguments have to conform to `InputResolvable` 13 | 14 | Input Resolvable Types are: 15 | - Scalars 16 | - Enums 17 | - Input Objects 18 | - Optionals and Arrays through Conditional Conformance 19 | */ 20 | public protocol InputResolvable: Resolvable { 21 | /** 22 | - Warning: never call this method directly. Always use: `context.resolve(type: ...)` 23 | 24 | Creates the Definition that will be used from GraphQL 25 | 26 | - Parameter context: Resolution Context where all the type relationships are stored. Use the context to resolve nested types. 27 | 28 | - Returns: A `GraphQLInputType` describing this Type.. 29 | */ 30 | static func resolve(using context: inout Resolution.Context) throws -> GraphQLInputType 31 | 32 | /** 33 | Instantiates this type from a GraphQL Value. Very similar to decodin with `init(from: Decoder)` 34 | 35 | - Parameter map: Object describing the value . 36 | 37 | - Returns: An instance of the Type 38 | 39 | ## Note 40 | 41 | This function is a static function instead of an initializer. 42 | The reason for that is that this protocol is often implemented in extensions of standard types. 43 | Since one of them is KeyPath, which is a class type. 44 | This can't be an initializer, since required initializers can't be defined in an extension. 45 | */ 46 | static func create(from map: Map) throws -> Self 47 | 48 | static func createFromMissingKey() throws -> Self 49 | } 50 | 51 | extension InputResolvable { 52 | 53 | public static func createFromMissingKey() throws -> Self { 54 | return try create(from: .null) 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/OutputResolvable/DelegatedOutputResolvable.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import ContextKit 4 | import NIO 5 | import GraphQL 6 | 7 | public protocol DelegatedOutputResolvable: OutputResolvable { 8 | associatedtype Output: OutputResolvable 9 | 10 | func resolve(source: Any, 11 | arguments: [String : Map], 12 | context: MutableContext, 13 | eventLoop: EventLoopGroup) throws -> Output 14 | } 15 | 16 | extension DelegatedOutputResolvable { 17 | 18 | /** 19 | - Warning: default implementation from `GraphZahl`. Do not override unless you know exactly what you are doing. 20 | */ 21 | public static var additionalArguments: [String : InputResolvable.Type] { 22 | return Output.additionalArguments 23 | } 24 | 25 | /** 26 | - Warning: default implementation from `GraphZahl`. Do not override unless you know exactly what you are doing. 27 | */ 28 | public static func reference(using context: inout Resolution.Context) throws -> GraphQLOutputType { 29 | return try context.reference(for: Output.self) 30 | } 31 | 32 | /** 33 | - Warning: default implementation from `GraphZahl`. Do not override unless you know exactly what you are doing. 34 | */ 35 | public static func resolve(using context: inout Resolution.Context) throws -> GraphQLOutputType { 36 | return try context.resolve(type: Output.self) 37 | } 38 | 39 | /** 40 | - Warning: default implementation from `GraphZahl`. Do not override unless you know exactly what you are doing. 41 | */ 42 | public func resolve(source: Any, 43 | arguments: [String : Map], 44 | context: MutableContext, 45 | eventLoop: EventLoopGroup) throws -> GraphZahl.Output { 46 | 47 | return try resolve(source: source, arguments: arguments, context: context, eventLoop: eventLoop) 48 | .resolve(source: source, arguments: arguments, context: context, eventLoop: eventLoop) 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/OutputResolvable/Implementations/Array+OutputResolvable.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import GraphQL 4 | import NIO 5 | import ContextKit 6 | 7 | /** 8 | # Conditional Conformance 9 | 10 | All arrays of values that are GraphQL Outputs, can be outputs themselves 11 | */ 12 | extension Array: OutputResolvable where Element: OutputResolvable { 13 | 14 | public static var additionalArguments: [String : InputResolvable.Type] { 15 | return Element.additionalArguments 16 | } 17 | 18 | public static func reference(using context: inout Resolution.Context) throws -> GraphQLOutputType { 19 | return GraphQLNonNull(GraphQLList(try context.reference(for: Element.self))) 20 | } 21 | 22 | public static func resolve(using context: inout Resolution.Context) throws -> GraphQLOutputType { 23 | return GraphQLNonNull(GraphQLList(try context.resolve(type: Element.self))) 24 | } 25 | 26 | public func resolve(source: Any, 27 | arguments: [String : Map], 28 | context: MutableContext, 29 | eventLoop: EventLoopGroup) throws -> Output { 30 | 31 | return .array(try map { try $0.resolve(source: source, arguments: arguments, context: context, eventLoop: eventLoop) }) 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/OutputResolvable/Implementations/Connection/ConnectionProtocol.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import NIO 4 | import GraphQL 5 | import ContextKit 6 | 7 | /** 8 | # ConnectionProtocol 9 | 10 | Describes an Object used for Cursor-Based Pagination. 11 | This API is modeled after standards set by [Relay](https://relay.dev/docs/en/pagination-container) and [GraphQL](https://graphql.org/learn/pagination/). 12 | 13 | It relies on the usage of the arguments: `first`, `after`, `last` and `before`. 14 | - `first`: Number of items that will be fetched at the start of the list 15 | - `after`: Cursor of the last element in the page that comes before. Signals that all items in the requested page should be strictly after the element with that cursor. The element with the cursor should be excluded 16 | - `last`: Number of items that will be fetched at the end of the list 17 | - `before`: Cursor of the first element in the page that comes after. Signals that all items in the requested page should be strictly before the element with that cursor. The element with the cursor should be excluded 18 | 19 | ## Note 20 | 21 | This is a bare bones implementation of Paging, and is not particularly efficient. 22 | 23 | If you notice that your methods are all sharing a lot of logic together, perhaps you should take a look at `ContextBasedConnection` insttead. 24 | */ 25 | public protocol ConnectionProtocol: OutputResolvable, ConcreteResolvable { 26 | /** 27 | Type of Item in the List. Has to conform to `OutputResolvable` 28 | */ 29 | associatedtype Node 30 | 31 | /** 32 | Type of Edges in the List. 33 | Edges may contain additional information. 34 | 35 | If no edge type is provided, it will default to the Standard Implementation. 36 | */ 37 | associatedtype Edge: EdgeProtocol = StandardEdge where Edge.Node == Node 38 | 39 | /** 40 | Computes the number of items in the list. 41 | 42 | - Returns: A future with the computed count. 43 | */ 44 | func totalCount() -> EventLoopFuture 45 | 46 | /** 47 | Computes information about the current page. 48 | This Information includes the start and end cursor and whether or not there's adjacent pages. 49 | 50 | - Parameters: 51 | - first: Number of items that will be fetched at the start of the list 52 | - after: Cursor of the last element in the page that comes before. Signals that all items in the requested page should be strictly after the element with that cursor. The element with the cursor should be excluded 53 | - last: Number of items that will be fetched at the end of the list 54 | - before: Cursor of the first element in the page that comes after. Signals that all items in the requested page should be strictly before the element with that cursor. The element with the cursor should be excluded 55 | - eventLoop: Event Loop Group that can be used to create the futures 56 | 57 | - Returns: A future with the computed Page Info 58 | */ 59 | func pageInfo(first: Int?, after: String?, last: Int?, before: String?, eventLoop: EventLoopGroup) -> EventLoopFuture 60 | 61 | 62 | /** 63 | Computes the edges for the current page 64 | 65 | - Parameters: 66 | - first: Number of items that will be fetched at the start of the list 67 | - after: Cursor of the last element in the page that comes before. Signals that all items in the requested page should be strictly after the element with that cursor. The element with the cursor should be excluded 68 | - last: Number of items that will be fetched at the end of the list 69 | - before: Cursor of the first element in the page that comes after. Signals that all items in the requested page should be strictly before the element with that cursor. The element with the cursor should be excluded 70 | - eventLoop: Event Loop Group that can be used to create the futures 71 | 72 | - Returns: A future with an array of edges. 73 | */ 74 | func edges(first: Int?, after: String?, last: Int?, before: String?, eventLoop: EventLoopGroup) -> EventLoopFuture<[Edge?]?> 75 | } 76 | 77 | extension ConnectionProtocol { 78 | 79 | public static var concreteTypeName: String { 80 | return "\(Node.concreteTypeName)\(String(describing: Self.self))" 81 | } 82 | 83 | /** 84 | - Warning: default implementation from `GraphZahl`. Do not override unless you know exactly what you are doing. 85 | */ 86 | public static var additionalArguments: [String : InputResolvable.Type] { 87 | return ConnectionWrapper.additionalArguments 88 | } 89 | 90 | /** 91 | - Warning: default implementation from `GraphZahl`. Do not override unless you know exactly what you are doing. 92 | */ 93 | public static func resolve(using context: inout Resolution.Context) throws -> GraphQLOutputType { 94 | return try context.resolve(type: ConnectionWrapper.self) 95 | } 96 | 97 | /** 98 | - Warning: default implementation from `GraphZahl`. Do not override unless you know exactly what you are doing. 99 | */ 100 | public func resolve(source: Any, 101 | arguments: [String : Map], 102 | context: MutableContext, 103 | eventLoop: EventLoopGroup) throws -> EventLoopFuture { 104 | 105 | let first = try Optional.create(from: arguments["first"] ?? .null) 106 | let after = try Optional.create(from: arguments["after"] ?? .null) 107 | let last = try Optional.create(from: arguments["last"] ?? .null) 108 | let before = try Optional.create(from: arguments["before"] ?? .null) 109 | 110 | let connection = ConnectionWrapper(connection: self, 111 | eventLoop: eventLoop, 112 | first: first, 113 | after: after, 114 | last: last, 115 | before: before) 116 | 117 | return eventLoop.next().makeSucceededFuture(connection) 118 | } 119 | 120 | } 121 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/OutputResolvable/Implementations/Connection/ConnectionWrapper.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import NIO 4 | import GraphQL 5 | import ContextKit 6 | 7 | class ConnectionWrapper { 8 | private let connection: Connection 9 | private let eventLoop: EventLoopGroup 10 | 11 | private let first: Int? 12 | private let after: String? 13 | private let last: Int? 14 | private let before: String? 15 | 16 | init(connection: Connection, 17 | eventLoop: EventLoopGroup, 18 | first: Int?, 19 | after: String?, 20 | last: Int?, 21 | before: String?) { 22 | 23 | self.connection = connection 24 | self.eventLoop = eventLoop 25 | self.first = first 26 | self.after = after 27 | self.last = last 28 | self.before = before 29 | } 30 | } 31 | 32 | extension ConnectionWrapper { 33 | 34 | private func pageInfo() -> EventLoopFuture { 35 | return connection.pageInfo(first: first, after: after, last: last, before: before, eventLoop: eventLoop) 36 | } 37 | 38 | private func edges() -> EventLoopFuture<[Connection.Edge?]?> { 39 | return connection.edges(first: first, after: after, last: last, before: before, eventLoop: eventLoop) 40 | } 41 | 42 | private func totalCount() -> EventLoopFuture { 43 | return connection.totalCount() 44 | } 45 | 46 | } 47 | 48 | extension ConnectionWrapper: ConcreteResolvable { 49 | 50 | static var concreteTypeName: String { 51 | return Connection.concreteTypeName 52 | } 53 | 54 | } 55 | 56 | extension ConnectionWrapper: OutputResolvable { 57 | 58 | static var additionalArguments: [String : InputResolvable.Type] { 59 | return [ 60 | "first" : Int?.self, 61 | "after" : String?.self, 62 | "last" : Int?.self, 63 | "before" : String?.self, 64 | ] 65 | } 66 | 67 | static func resolve(using context: inout Resolution.Context) throws -> GraphQLOutputType { 68 | let fields = [ 69 | "pageInfo" : GraphQLField(type: try context.reference(for: PageInfo.self)) { (receiver, args, context, eventLoop, _) -> Future in 70 | return try (receiver as! ConnectionWrapper) 71 | .pageInfo() 72 | .resolve(source: receiver, arguments: try args.dictionaryValue(), context: context as! MutableContext, eventLoop: eventLoop) 73 | .convert(eventLoopGroup: eventLoop) 74 | }, 75 | "edges" : GraphQLField(type: try context.reference(for: [Connection.Edge?]?.self)) { (receiver, args, context, eventLoop, _) -> Future in 76 | return try (receiver as! ConnectionWrapper) 77 | .edges() 78 | .resolve(source: receiver, arguments: try args.dictionaryValue(), context: context as! MutableContext, eventLoop: eventLoop) 79 | .convert(eventLoopGroup: eventLoop) 80 | }, 81 | "totalCount" : GraphQLField(type: try context.reference(for: Int.self)) { (receiver, args, context, eventLoop, _) -> Future in 82 | return try (receiver as! ConnectionWrapper) 83 | .totalCount() 84 | .resolve(source: receiver, arguments: try args.dictionaryValue(), context: context as! MutableContext, eventLoop: eventLoop) 85 | .convert(eventLoopGroup: eventLoop) 86 | }, 87 | ] 88 | 89 | return GraphQLNonNull( 90 | try GraphQLObjectType(name: concreteTypeName, fields: fields) 91 | ) 92 | } 93 | 94 | func resolve(source: Any, arguments: [String : Map], context: MutableContext, eventLoop: EventLoopGroup) throws -> Output { 95 | return .unsafeReceiver(self) 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/OutputResolvable/Implementations/Connection/ContextBasedConnection.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import NIO 4 | import GraphQL 5 | import ContextKit 6 | 7 | /** 8 | # ContextBasedConnection 9 | 10 | Describes an Object used for Cursor-Based Pagination. 11 | This API is modeled after standards set by [Relay](https://relay.dev/docs/en/pagination-container) and [GraphQL](https://graphql.org/learn/pagination/). 12 | 13 | It relies on the usage of the arguments: `first`, `after`, `last` and `before`. 14 | - `first`: Number of items that will be fetched at the start of the list 15 | - `after`: Cursor of the last element in the page that comes before. Signals that all items in the requested page should be strictly after the element with that cursor. The element with the cursor should be excluded 16 | - `last`: Number of items that will be fetched at the end of the list 17 | - `before`: Cursor of the first element in the page that comes after. Signals that all items in the requested page should be strictly before the element with that cursor. The element with the cursor should be excluded 18 | 19 | ## Note 20 | 21 | This is very similar to `ConnectionProtocol`. 22 | 23 | `ContextBasedConnection` describes the same functionality of `ConnectionProtocol`but attemps to compute certain context information about a page only once. 24 | 25 | A type implementing this protocol will first compute a context object that the other methods can use, instead of the raw arguments `first`, `after`, `last` and `before`. 26 | It is intended for scenarios where `totalCount`, `pageInfo` and `edges` all rely on very similar information. 27 | */ 28 | public protocol ContextBasedConnection: OutputResolvable, ConcreteResolvable { 29 | /** 30 | Type of Item in the List. Has to conform to `OutputResolvable` 31 | */ 32 | associatedtype Node 33 | 34 | /** 35 | Type of Edges in the List. 36 | Edges may contain additional information. 37 | 38 | If no edge type is provided, it will default to the Standard Implementation. 39 | */ 40 | associatedtype Edge: EdgeProtocol = StandardEdge where Edge.Node == Node 41 | 42 | /** 43 | Context that describes some computations that your other methods need 44 | */ 45 | associatedtype Context 46 | 47 | /** 48 | Converts the raw page arguments into the required context object. 49 | 50 | - Parameters: 51 | - first: Number of items that will be fetched at the start of the list 52 | - after: Cursor of the last element in the page that comes before. Signals that all items in the requested page should be strictly after the element with that cursor. The element with the cursor should be excluded 53 | - last: Number of items that will be fetched at the end of the list 54 | - before: Cursor of the first element in the page that comes after. Signals that all items in the requested page should be strictly before the element with that cursor. The element with the cursor should be excluded 55 | - eventLoop: Event Loop Group that can be used to create the futures 56 | 57 | - Returns: A future with the computed Context object 58 | */ 59 | func context(first: Int?, after: String?, last: Int?, before: String?, eventLoop: EventLoopGroup) -> EventLoopFuture 60 | 61 | /** 62 | Computes the number of items in the list based on the context. 63 | 64 | - Parameters: 65 | - context: Precomputed Context 66 | - eventLoop: Event Loop Group that can be used to create the futures 67 | 68 | - Returns: A future with the computed count. 69 | */ 70 | func totalCount(context: Context, eventLoop: EventLoopGroup) -> EventLoopFuture 71 | 72 | /** 73 | Computes information about the current page based on the context.. 74 | This Information includes the start and end cursor and whether or not there's adjacent pages. 75 | 76 | - Parameters: 77 | - context: Precomputed Context 78 | - eventLoop: Event Loop Group that can be used to create the futures 79 | 80 | - Returns: A future with the computed Page Info 81 | */ 82 | func pageInfo(context: Context, eventLoop: EventLoopGroup) -> EventLoopFuture 83 | 84 | /** 85 | Computes the edges for the current page 86 | 87 | - Parameters: 88 | - context: Precomputed Context 89 | - eventLoop: Event Loop Group that can be used to create the futures 90 | 91 | - Returns: A future with an array of edges. 92 | */ 93 | func edges(context: Context, eventLoop: EventLoopGroup) -> EventLoopFuture<[Edge?]?> 94 | } 95 | 96 | extension ContextBasedConnection { 97 | 98 | public static var concreteTypeName: String { 99 | return "\(Node.concreteTypeName)\(String(describing: Self.self))" 100 | } 101 | /** 102 | - Warning: default implementation from `GraphZahl`. Do not override unless you know exactly what you are doing. 103 | */ 104 | public static var additionalArguments: [String : InputResolvable.Type] { 105 | return ContextBasedConnectionWrapper.additionalArguments 106 | } 107 | 108 | /** 109 | - Warning: default implementation from `GraphZahl`. Do not override unless you know exactly what you are doing. 110 | */ 111 | public static func resolve(using context: inout Resolution.Context) throws -> GraphQLOutputType { 112 | return try context.resolve(type: ContextBasedConnectionWrapper.self) 113 | } 114 | 115 | /** 116 | - Warning: default implementation from `GraphZahl`. Do not override unless you know exactly what you are doing. 117 | */ 118 | public func resolve(source: Any, arguments: [String : Map], context: MutableContext, eventLoop: EventLoopGroup) throws -> Output { 119 | let first = try Optional.create(from: arguments["first"] ?? .null) 120 | let after = try Optional.create(from: arguments["after"] ?? .null) 121 | let last = try Optional.create(from: arguments["last"] ?? .null) 122 | let before = try Optional.create(from: arguments["before"] ?? .null) 123 | 124 | let connection = ContextBasedConnectionWrapper(connection: self, 125 | eventLoop: eventLoop, 126 | first: first, 127 | after: after, 128 | last: last, 129 | before: before) 130 | 131 | return .unsafeReceiver(connection) 132 | } 133 | 134 | } 135 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/OutputResolvable/Implementations/Connection/ContextBasedConnectionWrapper.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import NIO 4 | import GraphQL 5 | import ContextKit 6 | 7 | class ContextBasedConnectionWrapper { 8 | private let connection: Connection 9 | private let eventLoop: EventLoopGroup 10 | 11 | private var cachedContext: EventLoopFuture? 12 | private let first: Int? 13 | private let after: String? 14 | private let last: Int? 15 | private let before: String? 16 | 17 | init(connection: Connection, 18 | eventLoop: EventLoopGroup, 19 | first: Int?, 20 | after: String?, 21 | last: Int?, 22 | before: String?) { 23 | 24 | self.connection = connection 25 | self.eventLoop = eventLoop 26 | self.first = first 27 | self.after = after 28 | self.last = last 29 | self.before = before 30 | } 31 | } 32 | 33 | extension ContextBasedConnectionWrapper { 34 | 35 | private func context() -> EventLoopFuture { 36 | if let context = cachedContext { 37 | return context 38 | } 39 | 40 | let context = connection.context(first: first, after: after, last: last, before: before, eventLoop: eventLoop) 41 | cachedContext = context 42 | return context 43 | } 44 | 45 | private func pageInfo() -> EventLoopFuture { 46 | return context().flatMap { self.connection.pageInfo(context: $0, eventLoop: self.eventLoop) } 47 | } 48 | 49 | private func edges() -> EventLoopFuture<[Connection.Edge?]?> { 50 | return context().flatMap { self.connection.edges(context: $0, eventLoop: self.eventLoop) } 51 | } 52 | 53 | private func totalCount() -> EventLoopFuture { 54 | return context().flatMap { self.connection.totalCount(context: $0, eventLoop: self.eventLoop) } 55 | } 56 | 57 | } 58 | 59 | extension ContextBasedConnectionWrapper: ConcreteResolvable { 60 | 61 | static var concreteTypeName: String { 62 | return Connection.concreteTypeName 63 | } 64 | 65 | } 66 | 67 | extension ContextBasedConnectionWrapper: OutputResolvable { 68 | 69 | static var additionalArguments: [String : InputResolvable.Type] { 70 | return [ 71 | "first" : Int?.self, 72 | "after" : String?.self, 73 | "last" : Int?.self, 74 | "before" : String?.self, 75 | ] 76 | } 77 | 78 | static func resolve(using context: inout Resolution.Context) throws -> GraphQLOutputType { 79 | let fields = [ 80 | "pageInfo" : GraphQLField(type: try context.reference(for: PageInfo.self)) { (receiver, args, context, eventLoop, _) -> Future in 81 | return try (receiver as! ContextBasedConnectionWrapper) 82 | .pageInfo() 83 | .resolve(source: receiver, arguments: try args.dictionaryValue(), context: context as! MutableContext, eventLoop: eventLoop) 84 | .convert(eventLoopGroup: eventLoop) 85 | }, 86 | "edges" : GraphQLField(type: try context.reference(for: [Connection.Edge?]?.self)) { (receiver, args, context, eventLoop, _) -> Future in 87 | return try (receiver as! ContextBasedConnectionWrapper) 88 | .edges() 89 | .resolve(source: receiver, arguments: try args.dictionaryValue(), context: context as! MutableContext, eventLoop: eventLoop) 90 | .convert(eventLoopGroup: eventLoop) 91 | }, 92 | "totalCount" : GraphQLField(type: try context.reference(for: Int.self)) { (receiver, args, context, eventLoop, _) -> Future in 93 | return try (receiver as! ContextBasedConnectionWrapper) 94 | .totalCount() 95 | .resolve(source: receiver, arguments: try args.dictionaryValue(), context: context as! MutableContext, eventLoop: eventLoop) 96 | .convert(eventLoopGroup: eventLoop) 97 | }, 98 | ] 99 | 100 | return GraphQLNonNull( 101 | try GraphQLObjectType(name: concreteTypeName, fields: fields) 102 | ) 103 | } 104 | 105 | func resolve(source: Any, arguments: [String : Map], context: MutableContext, eventLoop: EventLoopGroup) throws -> Output { 106 | return .unsafeReceiver(self) 107 | } 108 | 109 | } 110 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/OutputResolvable/Implementations/Connection/EdgeProtocol.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | 4 | /** 5 | # EdgeProtocol 6 | 7 | Describes an Edge during Paging. An Edge contains the Node that is the value, the cursor of that value, and any additional information. 8 | 9 | ## Note 10 | 11 | An Edge is `OutputResolvable`. Any implementations of it, should be including the `node` and the `cursor` in the output. 12 | It is best if you delegate the implementation of `OutputResolvable` to `GraphQLObject` instead. 13 | */ 14 | public protocol EdgeProtocol: OutputResolvable { 15 | associatedtype Node: OutputResolvable & ConcreteResolvable 16 | 17 | /** 18 | Value of the Edge 19 | */ 20 | var node: Node? { get } 21 | 22 | /** 23 | Cursor to this value. Used to get a page before or after this value 24 | */ 25 | var cursor: String { get } 26 | } 27 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/OutputResolvable/Implementations/Connection/File.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Mathias.Quintero on 5/28/20. 6 | // 7 | 8 | import Foundation 9 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/OutputResolvable/Implementations/Connection/FixedPageSizeIndexedConnection.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import NIO 4 | 5 | public protocol FixedPageSizeIndexedConnection: IndexedConnection { 6 | func pageSize(eventLoop: EventLoopGroup) -> EventLoopFuture 7 | func page(at index: Int, eventLoop: EventLoopGroup) -> EventLoopFuture<[Node?]> 8 | } 9 | 10 | extension FixedPageSizeIndexedConnection { 11 | 12 | public func defaultPageSize(eventLoop: EventLoopGroup) -> EventLoopFuture { 13 | return pageSize(eventLoop: eventLoop).map(Optional.some) 14 | } 15 | 16 | public func nodes(offset: Int, size: Int, eventLoop: EventLoopGroup) -> EventLoopFuture<[Node?]?> { 17 | return totalCount(eventLoop: eventLoop) 18 | .and(pageSize(eventLoop: eventLoop)) 19 | .flatMap { totalCount, pageSize in 20 | let end = offset + size 21 | let firstPage = offset / pageSize 22 | let lastPage = (end - 1) / pageSize 23 | let pages = firstPage...lastPage 24 | 25 | let skipOnFirst = offset.quotientAndRemainder(dividingBy: pageSize).remainder 26 | let skipOnLast = min( 27 | (pageSize - end.quotientAndRemainder(dividingBy: pageSize).remainder).quotientAndRemainder(dividingBy: pageSize).remainder, 28 | totalCount - end 29 | ) 30 | 31 | let futures = pages 32 | .map { self.page(at: $0, eventLoop: eventLoop) } 33 | 34 | return EventLoopFuture 35 | .whenAllSucceed(futures, on: eventLoop.next()) 36 | .map { $0.flatMap { $0 } } 37 | .map { $0.dropFirst(skipOnFirst).dropLast(skipOnLast) } 38 | } 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/OutputResolvable/Implementations/Connection/IndexConnectionWrapper.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import NIO 4 | 5 | private let lock = Lock() 6 | private var translators: [ConnectionIdentifier : IndexCursorTranslator] = [:] 7 | 8 | private func withTranslator(for connectionIdentifier: ConnectionIdentifier, 9 | using type: IndexCursorTranslator.Type, 10 | perform: (inout IndexCursorTranslator) throws -> T) rethrows -> T { 11 | 12 | return try lock.withLock { 13 | return try perform(&translators[connectionIdentifier, default: type.init()]) 14 | } 15 | } 16 | 17 | private func withTranslator(for connectionIdentifier: ConnectionIdentifier, 18 | using type: IndexCursorTranslator.Type, 19 | perform: (IndexCursorTranslator) throws -> T) rethrows -> T { 20 | 21 | let translator = lock.withLock { 22 | return translators[connectionIdentifier, default: type.init()] 23 | } 24 | return try perform(translator) 25 | } 26 | 27 | private struct ConnectionIdentifier: Hashable { 28 | let type: Int 29 | let connection: AnyHashable 30 | } 31 | 32 | struct IndexedConnectionWrapper: ContextBasedConnection, ConcreteResolvable { 33 | static var concreteTypeName: String { 34 | return "\(Node.concreteTypeName)Connection" 35 | } 36 | 37 | typealias Node = Connection.Node 38 | 39 | struct Context { 40 | let totalCount: Int 41 | let offset: Int 42 | let size: Int 43 | } 44 | 45 | private let connection: Connection 46 | private let identifier: ConnectionIdentifier 47 | 48 | init(connection: Connection) { 49 | self.connection = connection 50 | self.identifier = ConnectionIdentifier(type: unsafeBitCast(Connection.self as Any.Type, to: Int.self), 51 | connection: AnyHashable(connection.identifier)) 52 | } 53 | 54 | private func cursor(for index: Int) throws -> String { 55 | return try withTranslator(for: identifier, using: Connection.Translator.self) { translator in 56 | return try translator.cursor(for: index) 57 | } 58 | } 59 | 60 | private func index(for cursor: String) throws -> Int { 61 | return try withTranslator(for: identifier, using: Connection.Translator.self) { translator in 62 | return try translator.index(for: cursor) 63 | } 64 | } 65 | 66 | func context(first: Int?, after: String?, last: Int?, before: String?, eventLoop: EventLoopGroup) -> EventLoopFuture { 67 | return connection 68 | .totalCount(eventLoop: eventLoop) 69 | .and(connection.defaultPageSize(eventLoop: eventLoop)) 70 | .flatMapThrowing { totalCount, defaultPageSize in 71 | let perPage = defaultPageSize ?? totalCount 72 | 73 | if perPage == 0 { 74 | return Context(totalCount: totalCount, offset: 0, size: 0) 75 | } 76 | 77 | let first = first == nil && last == nil ? perPage : first 78 | var start = 0 79 | var end = totalCount 80 | 81 | if let after = try after.map(self.index(for:)) { 82 | start = max(start, after + 1) 83 | } 84 | 85 | if let before = try before.map(self.index(for:)) { 86 | end = min(before, end) 87 | } 88 | 89 | if let first = first { 90 | end = min(start + first, end) 91 | } 92 | 93 | if let last = last { 94 | start = max(start, end - last) 95 | } 96 | 97 | return Context(totalCount: totalCount, offset: start, size: max(end - start, 0)) 98 | } 99 | } 100 | 101 | func totalCount(context: Context, eventLoop: EventLoopGroup) -> EventLoopFuture { 102 | return eventLoop.next().makeSucceededFuture(context.totalCount) 103 | } 104 | 105 | func pageInfo(context: Context, eventLoop: EventLoopGroup) -> EventLoopFuture { 106 | return eventLoop.next().submit { 107 | let hasNextPage = context.offset + context.size < context.totalCount 108 | let hasPreviousPage = context.offset > 0 109 | 110 | return PageInfo(hasNextPage: hasNextPage, 111 | hasPreviousPage: hasPreviousPage, 112 | startCursor: try self.cursor(for: context.offset), 113 | endCursor: try self.cursor(for: context.offset + context.size - 1)) 114 | } 115 | } 116 | 117 | func edges(context: Context, eventLoop: EventLoopGroup) -> EventLoopFuture<[StandardEdge?]?> { 118 | if context.size < 1 { 119 | return eventLoop.next().makeSucceededFuture([]) 120 | } 121 | 122 | return connection 123 | .nodes(offset: context.offset, size: context.size, eventLoop: eventLoop) 124 | .flatMapThrowing { nodes in 125 | try nodes?.enumerated().map { StandardEdge(node: $0.element, cursor: try self.cursor(for: context.offset + $0.offset)) } 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/OutputResolvable/Implementations/Connection/IndexCursorTranslator.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | 4 | public protocol IndexCursorTranslator { 5 | init() 6 | mutating func cursor(for index: Int) throws -> String 7 | mutating func index(for cursor: String) throws -> Int 8 | } 9 | 10 | public struct StandardIndexCursorTranslator: IndexCursorTranslator { 11 | private var cursors: [String : Int] = [:] 12 | 13 | enum Error: Swift.Error { 14 | case unrecognizedCursor(String, for: StandardIndexCursorTranslator) 15 | } 16 | 17 | public init() { } 18 | 19 | public mutating func cursor(for index: Int) throws -> String { 20 | var string = index.obfuscate() 21 | 22 | // Remove clashes 23 | while let clashingIndex = cursors[string], clashingIndex != index { 24 | string = string.obfuscate() 25 | } 26 | 27 | if cursors[string] == nil { 28 | cursors[string] = index 29 | } 30 | 31 | return string 32 | } 33 | 34 | public func index(for cursor: String) throws -> Int { 35 | guard let index = cursors[cursor] else { 36 | throw Error.unrecognizedCursor(cursor, for: self) 37 | } 38 | return index 39 | } 40 | } 41 | 42 | extension Hashable { 43 | 44 | fileprivate func obfuscate() -> String { 45 | var hasher = Hasher() 46 | hash(into: &hasher) 47 | return withUnsafeBytes(of: hasher.finalize()) { Data(buffer: $0.bindMemory(to: UInt8.self)).base64EncodedString() } 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/OutputResolvable/Implementations/Connection/IndexedConnection.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import NIO 4 | import GraphQL 5 | import ContextKit 6 | 7 | public protocol IndexedConnection: OutputResolvable, ConcreteResolvable { 8 | associatedtype Node: ConcreteResolvable & OutputResolvable 9 | associatedtype Identifier: Hashable 10 | associatedtype Translator: IndexCursorTranslator = StandardIndexCursorTranslator 11 | 12 | var identifier: Identifier { get } 13 | 14 | func totalCount(eventLoop: EventLoopGroup) -> EventLoopFuture 15 | func defaultPageSize(eventLoop: EventLoopGroup) -> EventLoopFuture 16 | func nodes(offset: Int, size: Int, eventLoop: EventLoopGroup) -> EventLoopFuture<[Node?]?> 17 | } 18 | 19 | extension IndexedConnection { 20 | 21 | public func defaultPageSize(eventLoop: EventLoopGroup) -> EventLoopFuture { 22 | return eventLoop.next().makeSucceededFuture(nil) 23 | } 24 | 25 | } 26 | 27 | extension IndexedConnection { 28 | 29 | public static var concreteTypeName: String { 30 | return IndexedConnectionWrapper.concreteTypeName 31 | } 32 | /** 33 | - Warning: default implementation from `GraphZahl`. Do not override unless you know exactly what you are doing. 34 | */ 35 | public static var additionalArguments: [String : InputResolvable.Type] { 36 | return IndexedConnectionWrapper.additionalArguments 37 | } 38 | 39 | /** 40 | - Warning: default implementation from `GraphZahl`. Do not override unless you know exactly what you are doing. 41 | */ 42 | public static func resolve(using context: inout Resolution.Context) throws -> GraphQLOutputType { 43 | return try context.resolve(type: IndexedConnectionWrapper.self) 44 | } 45 | 46 | /** 47 | - Warning: default implementation from `GraphZahl`. Do not override unless you know exactly what you are doing. 48 | */ 49 | public func resolve(source: Any, arguments: [String : Map], context: MutableContext, eventLoop: EventLoopGroup) throws -> Output { 50 | try IndexedConnectionWrapper(connection: self).resolve(source: source, arguments: arguments, context: context, eventLoop: eventLoop) 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/OutputResolvable/Implementations/Connection/PageInfo.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | 4 | /** 5 | # Page Info 6 | 7 | This is the information about the current Page. 8 | This Information includes the start and end cursor and whether or not there's adjacent pages. 9 | */ 10 | public class PageInfo: GraphQLObject { 11 | let hasNextPage: Bool 12 | let hasPreviousPage: Bool 13 | 14 | let startCursor: String? 15 | let endCursor: String? 16 | 17 | /** 18 | Initializer for Page Info 19 | 20 | - Parameters: 21 | - hasNextPage: is there a page after this one 22 | - hasPreviousPage: is there a page before this one 23 | - startCursor: What's the cursor of the first item. (`nil` if empty) 24 | - endCursor: What's the cursor of the last item. (`nil` if empty) 25 | */ 26 | public init(hasNextPage: Bool, 27 | hasPreviousPage: Bool, 28 | startCursor: String?, 29 | endCursor: String?) { 30 | 31 | self.hasNextPage = hasNextPage 32 | self.hasPreviousPage = hasPreviousPage 33 | self.startCursor = startCursor 34 | self.endCursor = endCursor 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/OutputResolvable/Implementations/Connection/StandardEdge.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import NIO 4 | import GraphQL 5 | import ContextKit 6 | 7 | /** 8 | # Standard Edge 9 | 10 | This is the default implementation of an Edge. It has the node and cursor and nothing else. 11 | */ 12 | public struct StandardEdge: EdgeProtocol, ConcreteResolvable { 13 | public let node: Node? 14 | public let cursor: String 15 | 16 | /** 17 | Initializer for an Edge 18 | 19 | - Parameters: 20 | - node: Value the edge is pointing to 21 | - cursor: Cursor for this Value 22 | */ 23 | public init(node: Node?, cursor: String) { 24 | self.node = node 25 | self.cursor = cursor 26 | } 27 | } 28 | 29 | extension StandardEdge { 30 | public static var concreteTypeName: String { 31 | return "\(Node.concreteTypeName)Edge" 32 | } 33 | } 34 | 35 | extension StandardEdge { 36 | 37 | public static var additionalArguments: [String : InputResolvable.Type] { 38 | return Node?.additionalArguments 39 | } 40 | 41 | public static func resolve(using context: inout Resolution.Context) throws -> GraphQLOutputType { 42 | let fields = [ 43 | "node" : GraphQLField(type: try context.reference(for: Optional.self)) { (receiver, args, context, eventLoop, _) -> Future in 44 | return try (receiver as! StandardEdge) 45 | .node 46 | .resolve(source: receiver, arguments: try args.dictionaryValue(), context: context as! MutableContext, eventLoop: eventLoop) 47 | .convert(eventLoopGroup: eventLoop) 48 | }, 49 | "cursor" : GraphQLField(type: try context.reference(for: String.self)) { (receiver, args, context, eventLoop, _) -> Future in 50 | return try (receiver as! StandardEdge) 51 | .cursor 52 | .resolve(source: receiver, arguments: try args.dictionaryValue(), context: context as! MutableContext,eventLoop: eventLoop) 53 | .convert(eventLoopGroup: eventLoop) 54 | }, 55 | ] 56 | 57 | return GraphQLNonNull( 58 | try GraphQLObjectType(name: concreteTypeName, fields: fields) 59 | ) 60 | } 61 | 62 | public func resolve(source: Any, arguments: [String : Map], context: MutableContext, eventLoop: EventLoopGroup) throws -> Output { 63 | return .unsafeReceiver(self) 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/OutputResolvable/Implementations/Future+OutputResolvable.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import GraphQL 4 | import NIO 5 | import ContextKit 6 | 7 | /** 8 | # Conditional Conformance 9 | 10 | All Futures of values that are GraphQL Outputs, can be outputs themselves 11 | */ 12 | extension EventLoopFuture: OutputResolvable where Value: OutputResolvable { 13 | 14 | public static var additionalArguments: [String : InputResolvable.Type] { 15 | return Value.additionalArguments 16 | } 17 | 18 | public static func reference(using context: inout Resolution.Context) throws -> GraphQLOutputType { 19 | return try context.reference(for: Value.self) 20 | } 21 | 22 | public static func resolve(using context: inout Resolution.Context) throws -> GraphQLOutputType { 23 | return try context.resolve(type: Value.self) 24 | } 25 | 26 | public func resolve(source: Any, arguments: [String : Map], context: MutableContext, eventLoop: EventLoopGroup) throws -> Output { 27 | let future = flatMapThrowing { try $0.resolve(source: source, arguments: arguments, context: context, eventLoop: eventLoop) } 28 | return .future(future) 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/OutputResolvable/Implementations/Object/GraphQLObject+OutputResolvable.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import GraphQL 4 | import NIO 5 | import ContextKit 6 | 7 | extension GraphQLObject { 8 | 9 | /** 10 | - Warning: default implementation from `GraphZahl`. Do not override unless you know exactly what you are doing. 11 | */ 12 | public static var additionalArguments: [String : InputResolvable.Type] { 13 | return [:] 14 | } 15 | 16 | /** 17 | - Warning: default implementation from `GraphZahl`. Do not override unless you know exactly what you are doing. 18 | */ 19 | public static func resolve(using context: inout Resolution.Context) throws -> GraphQLOutputType { 20 | return GraphQLNonNull(try resolveObject(using: &context)) 21 | } 22 | 23 | /** 24 | - Warning: default implementation from `GraphZahl`. Do not override unless you know exactly what you are doing. 25 | */ 26 | public func resolve(source: Any, 27 | arguments: [String : Map], 28 | context: MutableContext, 29 | eventLoop: EventLoopGroup) throws -> Output { 30 | 31 | return .object(self) 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/OutputResolvable/Implementations/Object/GraphQLObject+resolve.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import GraphQL 4 | import Runtime 5 | import NIO 6 | 7 | extension GraphQLObject { 8 | 9 | static func resolveObject(using context: inout Resolution.Context) throws -> GraphQLObjectType { 10 | let inheritance = try typeInfo(of: Self.self, .inheritance) 11 | let fieldsAndInterfaces = try self.fieldsAndInterfaces(using: &context) 12 | 13 | let interfaces = try inheritance 14 | .compactMap { $0 as? GraphQLObject.Type } 15 | .map { try context.resolveInterface(object: $0) } + fieldsAndInterfaces.interfaces 16 | 17 | let type = try GraphQLObjectType(name: concreteTypeName, 18 | fields: fieldsAndInterfaces.fields, 19 | interfaces: interfaces.distinct(by: \.name)) { value, _, _ in value is Self } 20 | 21 | return type 22 | } 23 | 24 | static func fieldsAndInterfaces(using context: inout Resolution.Context) throws -> FieldsAndInterfaces { 25 | let (typeProperties, typeMethods) = try typeInfo(of: Self.self, .properties, .methods) 26 | 27 | let isNode: Bool 28 | if let type = Self.self as? Node.Type { 29 | context.append(type: type) 30 | isNode = true 31 | } else { 32 | isNode = false 33 | } 34 | let nodeFields = isNode ? ["id" : GraphQLNodeIdField] : [:] 35 | 36 | let gettersThatShouldBeIgnored = Set(typeProperties.filter { $0.type is CustomGraphQLProperty.Type }.map { $0.name.deleting(prefix: "_") }) 37 | let propertyResults = try typeProperties.compactMap { try $0.resolve(for: Self.self, using: &context) } 38 | let properties = propertyResults.reduce([:]) { dictionary, result in 39 | return dictionary.merging(result.fieldMap) { first, _ in first } 40 | } 41 | 42 | let methodMap = Dictionary(typeMethods.map { ($0.methodName.deleting(prefix: "$"), $0) }) { first, _ in first } 43 | let methods = try methodMap.filter { !gettersThatShouldBeIgnored.contains($0.key) }.compactMapValues { try $0.resolve(for: Self.self, using: &context) } 44 | 45 | let fields = properties 46 | .merging(methods) { $1 } 47 | .merging(nodeFields) { $1 } 48 | 49 | return FieldsAndInterfaces(interfaces: propertyResults.flatMap(\.interfaces) + [isNode ? GraphQLNode : nil].compactMap { $0 }, fields: fields) 50 | } 51 | 52 | } 53 | 54 | struct FieldsAndInterfaces { 55 | let interfaces: [GraphQLInterfaceType] 56 | let fields: GraphQLFieldMap 57 | } 58 | 59 | extension Sequence { 60 | 61 | fileprivate func distinct(by id: (Element) -> T) -> [Element] { 62 | var set = Set() 63 | var result = [Element]() 64 | for element in self { 65 | let elementId = id(element) 66 | if !set.contains(elementId) { 67 | set.formUnion([elementId]) 68 | result.append(element) 69 | } 70 | } 71 | return result 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/OutputResolvable/Implementations/Object/GraphQLObject.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | 4 | /** 5 | # GraphQLObject 6 | 7 | A GraphQL Object is a Class that can be used as an output in GraphQL. 8 | 9 | It behaves as follows: 10 | - All properties of types that are `OutputResolvable` (can be outputs in GraphQL) will be added to the Schema. 11 | - All methods where all arguments are of types that are `InputResolvable` (can be inputs in GraphQL) and the return type is `OutputResolvable` (can be outputs in GraphQL) will be added to the Schema. 12 | 13 | ## Example 14 | 15 | If we are implementing a Todo App, a Todo and User in our API would be two classes: 16 | 17 | ```swift 18 | class Author: GraphQLObject { 19 | var name: String 20 | 21 | func store() { ... } 22 | 23 | func todos() -> [Todo] { ... } 24 | } 25 | 26 | class Todo: GraphQLObject { 27 | var title: String 28 | var completed: Bool 29 | 30 | func store() { ... } 31 | 32 | func author() -> User { ... } 33 | } 34 | ``` 35 | 36 | The previous code would then be exported to GraphQL like so: 37 | 38 | ```graphql 39 | type User { 40 | name: String! 41 | todos: [Todo!]! 42 | } 43 | 44 | type Todo { 45 | title: String! 46 | completed: Boolean! 47 | author: User! 48 | } 49 | ``` 50 | 51 | As can be seen in the example: 52 | - All the properties are scalar values that can be outputs in GraphQL. 53 | - All the properties are included. 54 | - And all the functions that return GraphQL Values are also included 55 | - The store functions return `Void`, which is not a GraphQL Output. These methods are therefore not included. 56 | */ 57 | public protocol GraphQLObject : AnyObject, OutputResolvable, ConcreteResolvable, KeyPathListable { 58 | /** 59 | Loads object from source. 60 | 61 | - Warning: default implementation from `GraphZahl`. Do not override unless you know exactly what you are doing. 62 | 63 | - Parameters: 64 | - source: In most cases it's just an instance of `Self`, except with Root Types, where it's an instance of the `ViewerContext` 65 | 66 | - Returns: the object that will be used for method dispatching 67 | */ 68 | static func object(from source: Any) -> AnyObject 69 | } 70 | 71 | extension GraphQLObject { 72 | 73 | /** 74 | - Warning: default implementation from `GraphZahl`. Do not override unless you know exactly what you are doing. 75 | */ 76 | public static func object(from source: Any) -> AnyObject { 77 | return source as AnyObject 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/OutputResolvable/Implementations/Object/PropertyWrappers/CustomGraphQLProperty.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import Runtime 4 | 5 | protocol CustomGraphQLProperty { 6 | static func resolve(with property: PropertyInfo, 7 | for receiverType: GraphQLObject.Type, 8 | using context: inout Resolution.Context) throws -> PropertyResult 9 | } 10 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/OutputResolvable/Implementations/Object/PropertyWrappers/Ignore.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import Runtime 4 | 5 | @propertyWrapper 6 | public struct Ignore { 7 | public var wrappedValue: T 8 | 9 | public init(wrappedValue: T) { 10 | self.wrappedValue = wrappedValue 11 | } 12 | } 13 | 14 | extension Ignore: CustomGraphQLProperty { 15 | 16 | static func resolve(with property: PropertyInfo, 17 | for receiverType: GraphQLObject.Type, 18 | using context: inout Resolution.Context) throws -> PropertyResult { 19 | return .ignore 20 | } 21 | 22 | } 23 | 24 | extension Ignore: Encodable where T: Encodable { 25 | 26 | public func encode(to encoder: Encoder) throws { 27 | try wrappedValue.encode(to: encoder) 28 | } 29 | 30 | } 31 | 32 | extension Ignore: Decodable where T: Decodable { 33 | 34 | public init(from decoder: Decoder) throws { 35 | self.init(wrappedValue: try T(from: decoder)) 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/OutputResolvable/Implementations/Object/PropertyWrappers/Inline.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import GraphQL 4 | import Runtime 5 | 6 | @propertyWrapper 7 | public struct Inline { 8 | public var wrappedValue: Wrapped 9 | 10 | public init(wrappedValue: Wrapped) { 11 | self.wrappedValue = wrappedValue 12 | } 13 | } 14 | 15 | extension Inline: CustomGraphQLProperty { 16 | 17 | static func resolve(with property: PropertyInfo, 18 | for receiverType: GraphQLObject.Type, 19 | using context: inout Resolution.Context) throws -> PropertyResult { 20 | 21 | let fields = try Wrapped.fieldsAndInterfaces(using: &context).fields.mapValues { field in 22 | return GraphQLField( 23 | type: field.type, 24 | description: field.description, 25 | deprecationReason: field.deprecationReason, 26 | args: field.args 27 | ) { source, arguments, context, eventLoop, info in 28 | let object = receiverType.object(from: source) 29 | let result = try property.get(from: object) as! Self 30 | return try field.resolve!(result.wrappedValue, arguments, context, eventLoop, info) 31 | } 32 | } 33 | 34 | return .interfaces([], fields: fields) 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/OutputResolvable/Implementations/Object/PropertyWrappers/InlineAsInterface.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import GraphQL 4 | import Runtime 5 | 6 | @propertyWrapper 7 | public struct InlineAsInterface { 8 | public var wrappedValue: Wrapped 9 | 10 | public init(wrappedValue: Wrapped) { 11 | self.wrappedValue = wrappedValue 12 | } 13 | } 14 | 15 | extension InlineAsInterface: CustomGraphQLProperty { 16 | 17 | static func resolve(with property: PropertyInfo, 18 | for receiverType: GraphQLObject.Type, 19 | using context: inout Resolution.Context) throws -> PropertyResult { 20 | 21 | let object = try context.resolve(object: Wrapped.self) 22 | let interface = try context.resolveInterface(object: Wrapped.self) 23 | 24 | let fields = object.fields.mapValues { field in 25 | return GraphQLField( 26 | type: field.type, 27 | description: field.description, 28 | deprecationReason: field.deprecationReason, 29 | args: Dictionary( 30 | uniqueKeysWithValues: field.args.map { ($0.name, $0.type) } 31 | ) 32 | .mapValues { GraphQLArgument(type: $0) } 33 | ) { source, arguments, context, eventLoop, info in 34 | 35 | let object = receiverType.object(from: source) 36 | let result = try property.get(from: object) as! Self 37 | return try field.resolve!(result.wrappedValue, arguments, context, eventLoop, info) 38 | } 39 | } 40 | 41 | return .interfaces([interface] + object.interfaces, fields: fields) 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/OutputResolvable/Implementations/Object/PropertyWrappers/LazyInline.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import GraphQL 4 | import Runtime 5 | import NIO 6 | 7 | @propertyWrapper 8 | public class LazyInline { 9 | private let load: () -> EventLoopFuture 10 | fileprivate lazy var future: EventLoopFuture = { 11 | return load() 12 | }() 13 | 14 | public var wrappedValue: Wrapped { 15 | return try! future.wait() 16 | } 17 | 18 | public init(_ load: @escaping () -> EventLoopFuture) { 19 | self.load = load 20 | } 21 | } 22 | 23 | extension LazyInline: CustomGraphQLProperty { 24 | 25 | static func resolve(with property: PropertyInfo, 26 | for receiverType: GraphQLObject.Type, 27 | using context: inout Resolution.Context) throws -> PropertyResult { 28 | 29 | let fields = try Wrapped.fieldsAndInterfaces(using: &context).fields.mapValues { field in 30 | return GraphQLField( 31 | type: field.type, 32 | description: field.description, 33 | deprecationReason: field.deprecationReason, 34 | args: field.args 35 | ) { source, arguments, context, eventLoop, info in 36 | 37 | let object = receiverType.object(from: source) 38 | let result = try property.get(from: object) as! Self 39 | return result 40 | .future 41 | .flatMapThrowing { try field.resolve!($0, arguments, context, eventLoop, info) } 42 | .flatMap { $0 } 43 | } 44 | } 45 | 46 | return .interfaces([], fields: fields) 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/OutputResolvable/Implementations/Object/PropertyWrappers/LazyInlineAsInterface.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import GraphQL 4 | import Runtime 5 | import NIO 6 | 7 | @propertyWrapper 8 | public class LazyInlineAsInterface { 9 | private let load: () -> EventLoopFuture 10 | fileprivate lazy var future: EventLoopFuture = { 11 | return load() 12 | }() 13 | 14 | public var wrappedValue: Wrapped { 15 | return try! future.wait() 16 | } 17 | 18 | public init(_ load: @escaping () -> EventLoopFuture) { 19 | self.load = load 20 | } 21 | } 22 | 23 | extension LazyInlineAsInterface: CustomGraphQLProperty { 24 | 25 | static func resolve(with property: PropertyInfo, 26 | for receiverType: GraphQLObject.Type, 27 | using context: inout Resolution.Context) throws -> PropertyResult { 28 | 29 | let object = try context.resolve(object: Wrapped.self) 30 | let interface = try context.resolveInterface(object: Wrapped.self) 31 | 32 | let fields = object.fields.mapValues { field in 33 | return GraphQLField( 34 | type: field.type, 35 | description: field.description, 36 | deprecationReason: field.deprecationReason, 37 | args: Dictionary( 38 | uniqueKeysWithValues: field.args.map { ($0.name, $0.type) } 39 | ) 40 | .mapValues { GraphQLArgument(type: $0) } 41 | ) { source, arguments, context, eventLoop, info in 42 | 43 | let object = receiverType.object(from: source) 44 | let result = try property.get(from: object) as! Self 45 | return result 46 | .future 47 | .flatMapThrowing { try field.resolve!($0, arguments, context, eventLoop, info) } 48 | .flatMap { $0 } 49 | } 50 | } 51 | 52 | return .interfaces([interface] + object.interfaces, fields: fields) 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/OutputResolvable/Implementations/Object/PropertyWrappers/PropertyResult.swift: -------------------------------------------------------------------------------- 1 | 2 | import GraphQL 3 | 4 | enum PropertyResult { 5 | case field(String, GraphQLField) 6 | case interfaces([GraphQLInterfaceType], fields: [String : GraphQLField]) 7 | case ignore 8 | } 9 | 10 | extension PropertyResult { 11 | 12 | var fieldMap: [String : GraphQLField] { 13 | switch self { 14 | case .field(let name, let field): 15 | return [name : field] 16 | case .interfaces(_, let fields): 17 | return fields 18 | case .ignore: 19 | return [:] 20 | } 21 | } 22 | 23 | var interfaces: [GraphQLInterfaceType] { 24 | guard case .interfaces(let interface, _) = self else { return [] } 25 | return interface 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/OutputResolvable/Implementations/Optional+OutputResolvable.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import GraphQL 4 | import NIO 5 | import ContextKit 6 | 7 | /** 8 | # Conditional Conformance 9 | 10 | All optionals of values that are GraphQL Outputs, can be outputs themselves 11 | */ 12 | extension Optional: OutputResolvable where Wrapped: OutputResolvable { 13 | 14 | public static var additionalArguments: [String : InputResolvable.Type] { 15 | return Wrapped.additionalArguments 16 | } 17 | 18 | public static func reference(using context: inout Resolution.Context) throws -> GraphQLOutputType { 19 | switch try context.reference(for: Wrapped.self) { 20 | case let resolved as GraphQLNonNull: 21 | guard let type = resolved.ofType as? GraphQLOutputType else { fatalError() } 22 | return type 23 | case let type: 24 | return type 25 | } 26 | } 27 | 28 | public static func resolve(using context: inout Resolution.Context) throws -> GraphQLOutputType { 29 | switch try context.resolve(type: Wrapped.self) { 30 | case let resolved as GraphQLNonNull: 31 | guard let type = resolved.ofType as? GraphQLOutputType else { fatalError() } 32 | return type 33 | case let type: 34 | return type 35 | } 36 | } 37 | 38 | public func resolve(source: Any, 39 | arguments: [String : Map], 40 | context: MutableContext, 41 | eventLoop: EventLoopGroup) throws -> Output { 42 | 43 | guard let value = self else { return .map(.null) } 44 | return try value.resolve(source: source, arguments: arguments, context: context, eventLoop: eventLoop) 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/OutputResolvable/Node.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import NIO 4 | import ContextKit 5 | import GraphQL 6 | 7 | public protocol Node: GraphQLObject { 8 | func id(context: MutableContext, eventLoop: EventLoopGroup) -> EventLoopFuture 9 | 10 | static func find(id: String, context: MutableContext, eventLoop: EventLoopGroup ) -> EventLoopFuture 11 | } 12 | 13 | let GraphQLNodeIdField: GraphQLField = { 14 | return GraphQLField(type: GraphQLNonNull(GraphQLID), 15 | description: "The id of the object", 16 | deprecationReason: nil, 17 | args: [:]) { (source, _, context, eventLoop, _) -> Future in 18 | 19 | return (source as! Node).id(context: context as! MutableContext, eventLoop: eventLoop).map { $0 } 20 | } 21 | }() 22 | 23 | let GraphQLNode: GraphQLInterfaceType = { 24 | let fields = [ 25 | "id" : GraphQLNodeIdField 26 | ] 27 | return try! GraphQLInterfaceType(name: "Node", fields: fields, resolveType: nil) 28 | }() 29 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/OutputResolvable/Output.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import GraphQL 4 | import NIO 5 | 6 | public indirect enum Output { 7 | case map(Map) 8 | case array([Output]) 9 | case future(EventLoopFuture) 10 | case object(GraphQLObject) 11 | case unsafeReceiver(Any) 12 | } 13 | 14 | extension Output { 15 | 16 | func convert(eventLoopGroup: EventLoopGroup) -> EventLoopFuture { 17 | switch self { 18 | case .map(.null): 19 | return eventLoopGroup.next().makeSucceededFuture(nil) 20 | case .map(let map): 21 | return eventLoopGroup.next().makeSucceededFuture(map) 22 | case .array(let array): 23 | let futures = array.map { $0.convert(eventLoopGroup: eventLoopGroup) } 24 | return Future.whenAllSucceed(futures, on: eventLoopGroup.next()).map { $0 } 25 | case .future(let future): 26 | return future.flatMap { $0.convert(eventLoopGroup: eventLoopGroup) } 27 | case .object(let object): 28 | return eventLoopGroup.next().makeSucceededFuture(object) 29 | case .unsafeReceiver(let receiver): 30 | return eventLoopGroup.next().makeSucceededFuture(receiver) 31 | } 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/OutputResolvable/OutputResolvable.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import GraphQL 4 | import NIO 5 | import ContextKit 6 | 7 | /** 8 | - Attention: this is a low level API. Do not use this directly unless you know what you're doing. 9 | 10 | # OutputResolvable 11 | 12 | Output Resolvable describes an Object that can be used as an Output in GraphQL. 13 | 14 | It means that for a method or property to be available in GraphQL, all the output has to conform to `OutputResolvable` 15 | 16 | Output Resolvable Types are: 17 | - Scalars 18 | - Enums 19 | - Objects 20 | - Root Types 21 | - Optionals, Futures and Arrays through Conditional Conformance 22 | */ 23 | public protocol OutputResolvable: Resolvable { 24 | 25 | /** 26 | Additional Arguments you will need when resolving the value in `resolve(source:arguments:context:eventLoop)`. 27 | */ 28 | static var additionalArguments: [String : InputResolvable.Type] { get } 29 | 30 | /** 31 | - Warning: never call this method directly. Always use: `context.reference(for: ...)` 32 | 33 | Creates a Reference to the GraphQL Type 34 | 35 | - Parameter context: Resolution Context where all the type relationships are stored. Use the context to resolve nested types. 36 | 37 | - Returns: A `GraphQLOutputType` describing the Reference to the Type.. 38 | */ 39 | static func reference(using context: inout Resolution.Context) throws -> GraphQLOutputType 40 | 41 | /** 42 | - Warning: never call this method directly. Always use: `context.resolve(type: ...)` 43 | 44 | Creates the Definition that will be used from GraphQL 45 | 46 | - Parameter context: Resolution Context where all the type relationships are stored. Use the context to resolve nested types. 47 | 48 | - Returns: A `GraphQLOutputType` describing this Type.. 49 | */ 50 | static func resolve(using context: inout Resolution.Context) throws -> GraphQLOutputType 51 | 52 | /** 53 | Resolves the value that can be outputed to GraphQL 54 | 55 | - Parameters: 56 | - source: Parent of this type 57 | - arguments: Arguments given to the parent field 58 | - context: Context, containing additional runtime data about the resuts. You may also include additional information here for other nested types to use 59 | - eventLoop: Event Loop Group that can be used for creating futures 60 | 61 | - Returns: A Future with a value that can be evaluated by GraphQL. 62 | */ 63 | func resolve(source: Any, arguments: [String : Map], context: MutableContext, eventLoop: EventLoopGroup) throws -> Output 64 | } 65 | 66 | extension OutputResolvable { 67 | 68 | static func additionalGraphqlArguments(using context: inout Resolution.Context) throws -> [String : GraphQLArgument] { 69 | return try additionalArguments 70 | .mapValues { try context.resolve(type: $0) } 71 | .mapValues { GraphQLArgument(type: $0) } 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/Resolvable/ConcreteResolvable.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import GraphQL 4 | 5 | /** 6 | # Concrete Resolvable 7 | 8 | A Resolvable that is required to have a type name 9 | */ 10 | public protocol ConcreteResolvable: Resolvable { 11 | /** 12 | Name that should be used in GraphQL 13 | */ 14 | static var concreteTypeName: String { get } 15 | } 16 | 17 | extension ConcreteResolvable { 18 | 19 | /** 20 | - Warning: default implementation from `GraphZahl`. Do not override unless you know exactly what you are doing. 21 | */ 22 | public static var typeName: String? { 23 | return concreteTypeName 24 | } 25 | 26 | public static var concreteTypeName: String { 27 | return String(describing: Self.self) 28 | } 29 | 30 | } 31 | 32 | extension ConcreteResolvable where Self: OutputResolvable { 33 | 34 | /** 35 | - Warning: default implementation from `GraphZahl`. Do not override unless you know exactly what you are doing. 36 | */ 37 | public static func reference(using context: inout Resolution.Context) throws -> GraphQLOutputType { 38 | return GraphQLNonNull(GraphQLTypeReference(concreteTypeName)) 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/Resolvable/Implementations/Array+Resolvable.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | 4 | /** 5 | # Conditional Conformance 6 | 7 | All arrays of values that are resolved, can be resolved 8 | */ 9 | extension Array: Resolvable where Element: Resolvable { } 10 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/Resolvable/Implementations/Future+Resolvable.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import NIO 4 | 5 | /** 6 | # Conditional Conformance 7 | 8 | All futures of values that are resolved, can be resolved 9 | */ 10 | extension EventLoopFuture: Resolvable where Value: Resolvable { } 11 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/Resolvable/Implementations/Optional+Resolvable.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | 4 | /** 5 | # Conditional Conformance 6 | 7 | All optionals of values that are resolved, can be resolved 8 | */ 9 | extension Optional: Resolvable where Wrapped: Resolvable { } 10 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/Resolvable/KeyPathListable.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import Runtime 4 | 5 | private var keyPaths = [Int : [String : PropertyInfo]]() 6 | 7 | /** 8 | # Key Path Listable 9 | 10 | A type whose Key Paths can be used in GraphQL 11 | */ 12 | public protocol KeyPathListable { } 13 | 14 | extension KeyPathListable { 15 | 16 | subscript(checkedMirrorDescendant key: String) -> T { 17 | get { 18 | return try! keyPaths[unsafeBitCast(Self.self, to: Int.self)]![key]!.get(from: self) 19 | } 20 | set { 21 | try! keyPaths[unsafeBitCast(Self.self, to: Int.self)]![key]!.set(value: newValue, on: &self) 22 | } 23 | } 24 | 25 | } 26 | 27 | extension KeyPathListable { 28 | 29 | static func resolve() throws -> [String : KeyPath] { 30 | let properties = try keyPaths.getOrPut(unsafeBitCast(Self.self, to: Int.self)) { 31 | let keysAndValues = try typeInfo(of: Self.self, .properties).map { ($0.name, $0) } 32 | return Dictionary(keysAndValues) { $1 } 33 | } 34 | 35 | // TODO: If T is Any or a protocol, we should allow other types as well 36 | return properties 37 | .filter { $0.value.type == T.self } 38 | .mapValues { \Self.[checkedMirrorDescendant: $0.name] } 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/Resolvable/Resolvable.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | 4 | /** 5 | # Resolvable 6 | 7 | A Type with a possible type name that will be used in GraphQL 8 | */ 9 | public protocol Resolvable { 10 | /** 11 | Name that should be used in GraphQL 12 | */ 13 | static var typeName: String? { get } 14 | } 15 | 16 | extension Resolvable { 17 | 18 | /** 19 | - Warning: default implementation from `GraphZahl`. Do not override unless you know exactly what you are doing. 20 | */ 21 | public static var typeName: String? { 22 | return nil 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/Resolvable/ValueResolvable.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import GraphQL 4 | 5 | /** 6 | # Value Resolvable 7 | 8 | A type that can be turned into a GraphQL Value. 9 | Generally used to convert default values into something that can be added to the Schema 10 | */ 11 | public protocol ValueResolvable: Resolvable { 12 | func map() throws -> Map 13 | } 14 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/Root/EmptyRootType.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import GraphQL 4 | import NIO 5 | 6 | /** 7 | # EmptyRootType 8 | 9 | A Root Type that signals that it's empty and should not be resolved 10 | */ 11 | public final class EmptyRootType : GraphQLRootType { 12 | public func resolve(source: Any, arguments: [String : Map], eventLoop: EventLoopGroup) -> EventLoopFuture { 13 | return eventLoop.next().makeSucceededFuture(nil) 14 | } 15 | 16 | required public init(viewerContext: ViewerContext) { 17 | // No-op 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/Root/GraphQLRootType.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | 4 | /** 5 | # GraphQL Root Type 6 | 7 | An GraphQL Object that is at the Root of a Schema. Can either be a Query Type or a Mutation Type 8 | 9 | The only additional requirement for a `GraphQLType` is that it can be initialized with a `ViewerContext`. 10 | The `ViewerContext` in this scenario refers to a Context Object, that tells the Query/Mutation Type what it needs to know about the User who is requesting the data. 11 | 12 | ## Example 13 | 14 | If we are building a Todo App and want allow users to request their Todos. We can use the User as a ViewerContext 15 | 16 | ```swift 17 | class Query: GraphQLRootType { 18 | let user: User 19 | 20 | func myTodos() -> [Todo] { ... } 21 | 22 | init(viewerContext user: User) { 23 | self.user = user 24 | } 25 | } 26 | ``` 27 | 28 | This is then translated into: 29 | 30 | ```graphql 31 | type Query { 32 | myTodos: [Todo!]! 33 | } 34 | ``` 35 | */ 36 | public protocol GraphQLRootType : GraphQLObject { 37 | /** 38 | Type that tells the Query/Mutation type everything about the User. 39 | This defaults to Void, to signal that all requests are treated the same, and this API does not compute anything different on a by user basis. 40 | */ 41 | associatedtype ViewerContext = Void 42 | 43 | /** 44 | Initializes the type based on the Viewer Context. 45 | 46 | - Parameter viewerContext: Viewer Context for this Type 47 | */ 48 | init(viewerContext: ViewerContext) 49 | } 50 | 51 | extension GraphQLRootType { 52 | 53 | /** 54 | - Warning: default implementation from `GraphZahl`. Do not override unless you know exactly what you are doing. 55 | */ 56 | public static func object(from source: Any) -> AnyObject { 57 | if let viewerContext = source as? ViewerContext { 58 | return Self(viewerContext: viewerContext) 59 | } 60 | return source as AnyObject 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/Root/GraphQLSchema+perform.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import GraphQL 4 | import NIO 5 | import ContextKit 6 | 7 | private let defaultEventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) 8 | private var schemaCache = [String : GraphQL.GraphQLSchema]() 9 | 10 | extension GraphQLSchema { 11 | 12 | public static func prepare(viewerContext: ViewerContext? = nil) throws { 13 | _ = try schemaCache.getOrPut(String(describing: Self.self), default: try resolve(viewerContext: viewerContext)) 14 | } 15 | 16 | } 17 | 18 | extension GraphQLSchema { 19 | 20 | /** 21 | Performs a GraphQL Request 22 | 23 | - Parameters: 24 | - request: Query Text 25 | - viewerContext: Viewer Context 26 | - variableValues; Values that should be given as variables to the Query. (defaults to empty) 27 | - eventLoop; Event Loop Group where the query should be resolved 28 | 29 | - Returns: A future with the result with data and errors for the query 30 | */ 31 | public static func perform(request: String, 32 | viewerContext: ViewerContext, 33 | variableValues: [String : Map] = [:], 34 | eventLoopGroup: EventLoopGroup? = nil) throws -> Future { 35 | 36 | let schema = try schemaCache.getOrPut(String(describing: Self.self), default: try resolve(viewerContext: viewerContext)) 37 | 38 | let context = MutableContext { 39 | Self.viewerContext ~> viewerContext; 40 | .anyViewerContext ~> viewerContext 41 | } 42 | 43 | return try graphql(schema: schema, 44 | request: request, 45 | rootValue: viewerContext, 46 | context: context, 47 | eventLoopGroup: eventLoopGroup ?? defaultEventLoopGroup, 48 | variableValues: variableValues) 49 | } 50 | 51 | } 52 | 53 | extension GraphQLSchema where ViewerContext == Void { 54 | 55 | /** 56 | Performs a GraphQL Request 57 | 58 | - Parameters: 59 | - request: Query Text 60 | - variableValues; Values that should be given as variables to the Query. (defaults to empty) 61 | - eventLoop; Event Loop Group where the query should be resolved 62 | 63 | - Returns: A future with the result with data and errors for the query 64 | */ 65 | public static func perform(request: String, 66 | variableValues: [String : Map] = [:], 67 | eventLoopGroup: EventLoopGroup? = nil) throws -> Future { 68 | return try perform(request: request, viewerContext: ()) 69 | } 70 | 71 | } 72 | 73 | extension GraphQLSchema { 74 | 75 | public static var viewerContext: Context.Key { 76 | return .viewerContext 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/Root/GraphQLSchema+resolve.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import GraphQL 4 | import NIO 5 | import ContextKit 6 | 7 | extension GraphQLSchema { 8 | 9 | static func resolve(viewerContext: ViewerContext?) throws -> GraphQL.GraphQLSchema { 10 | var context = Resolution.Context.empty(viewerContextType: ViewerContext.self, viewerContext: viewerContext) 11 | 12 | var query = try context.resolve(object: Query.self) 13 | 14 | let mutation: GraphQLObjectType? 15 | if Mutation.self != None.self { 16 | mutation = try context.resolve(object: Mutation.self) 17 | } else { 18 | mutation = nil 19 | } 20 | 21 | let types = try context.types().filter { $0.name != query.name } 22 | 23 | if !context.nodeTypes.isEmpty { 24 | let newFields = [ 25 | "node" : nodeField(types: context.nodeTypes) 26 | ] 27 | 28 | let fields = query.fields.mapValues { definition -> GraphQLField in 29 | let args = Dictionary(uniqueKeysWithValues: definition.args.map { argDefinition in 30 | return ( 31 | argDefinition.name, 32 | GraphQLArgument(type: argDefinition.type, 33 | description: argDefinition.description, 34 | defaultValue: argDefinition.defaultValue) 35 | ) 36 | }) 37 | return GraphQLField(type: definition.type, 38 | description: definition.description, 39 | deprecationReason: definition.deprecationReason, 40 | args: args, 41 | resolve: definition.resolve) 42 | } 43 | .merging(newFields) { $1 } 44 | 45 | query = try GraphQLObjectType(name: query.name, 46 | description: query.description, 47 | fields: fields, 48 | interfaces: query.interfaces, 49 | isTypeOf: query.isTypeOf) 50 | } 51 | 52 | return try GraphQL.GraphQLSchema(query: query, 53 | mutation: mutation, 54 | subscription: nil, 55 | types: types + [query], 56 | directives: []) 57 | } 58 | 59 | } 60 | 61 | private func nodeField(types: [Node.Type]) -> GraphQLField { 62 | return GraphQLField( 63 | type: GraphQLNode, 64 | description: "Fetches an object given its ID", 65 | deprecationReason: nil, 66 | args: ["id" : GraphQLArgument(type: GraphQLNonNull(GraphQLID), description: "The ID of an object")] 67 | ) { (_, args, context, eventLoop, _) -> Future in 68 | let id = try args.get("id").stringValue(converting: true) 69 | return findNode(id: id, types: types, context: context as! MutableContext, eventLoop: eventLoop).map { $0 } 70 | } 71 | } 72 | 73 | private func findNode( 74 | id: String, 75 | types: C, 76 | context: MutableContext, 77 | eventLoop: EventLoopGroup 78 | ) -> EventLoopFuture where C.Element == Node.Type { 79 | guard let type = types.first else { 80 | return eventLoop.next().makeSucceededFuture(nil) 81 | } 82 | 83 | return type 84 | .find(id: id, context: context, eventLoop: eventLoop) 85 | .flatMap { node in 86 | if let node = node { 87 | return eventLoop.next().makeSucceededFuture(node) 88 | } 89 | 90 | return findNode(id: id, types: types.dropFirst(), context: context, eventLoop: eventLoop) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/Root/GraphQLSchema.swift: -------------------------------------------------------------------------------- 1 | 2 | import GraphQL 3 | import NIO 4 | 5 | /** 6 | # GraphQL Schema 7 | 8 | A GraphQL Schema describes the entire API that is available. 9 | It's basically only a name space for your Query and Mutation Types. 10 | 11 | Your Query and Mutation Types have to be Root Types with the same Viewer Context. 12 | - A Query Type is mandatory. 13 | - A Mutation Type is optional. 14 | 15 | ## Examples 16 | 17 | If you only want to offer a Query Type, you only need to implement that class: 18 | 19 | ```swift 20 | enum MySchema { 21 | 22 | class Query: QueryType { 23 | func greeting(name: String = "World") -> String { 24 | return "Hello, \(name)!" 25 | } 26 | 27 | init(viewerContext: ()) { ... } 28 | } 29 | 30 | } 31 | ``` 32 | 33 | that gets translated to the following schema: 34 | 35 | ```graphql 36 | type Query { 37 | greeting(name: String! = "World"): String! 38 | } 39 | ``` 40 | 41 | Alternatively you can also specify a Mutation Type: 42 | 43 | ```swift 44 | enum MySchema { 45 | class Query: QueryType { 46 | func greeting(name: String = "World") -> String { 47 | return "Hello, \(name)!" 48 | } 49 | 50 | init(viewerContext: ()) { ... } 51 | } 52 | 53 | class Mutation: MutationType { 54 | func store(status: String) -> String { ... } 55 | 56 | init(viewerContext: ()) { ... } 57 | } 58 | } 59 | ``` 60 | 61 | Which would generate this Schema: 62 | 63 | 64 | ```graphql 65 | type Query { 66 | greeting(name: String! = "World"): String! 67 | } 68 | 69 | type Mutation { 70 | store(status: String!): String! 71 | } 72 | ``` 73 | 74 | If your types need to have a context for the current user, they need to use the exact same `ViewerContext`: 75 | 76 | ```swift 77 | enum MySchema { 78 | typealias ViewerContext = User 79 | 80 | class Query: QueryType { 81 | let user: User 82 | 83 | var greeting: String { 84 | return "Hello, \(user.name)!" 85 | } 86 | 87 | init(viewerContext: User) { ... } 88 | } 89 | 90 | class Mutation: MutationType { 91 | let user: User 92 | 93 | func store(status: String) -> String { ... } 94 | 95 | init(viewerContext: User) { ... } 96 | } 97 | } 98 | ``` 99 | */ 100 | public protocol GraphQLSchema { 101 | /** 102 | Type of the Response of the Schema 103 | */ 104 | typealias Result = GraphQLResult 105 | 106 | /** 107 | Type of an Empty Mutation Type. Signals GraphZahl that no mutations are allowed 108 | */ 109 | typealias None = EmptyRootType 110 | 111 | /** 112 | Type that tells the Query/Mutation type everything about the User. 113 | This defaults to Void, to signal that all requests are treated the same, and this API does not compute anything different on a by user basis. 114 | */ 115 | associatedtype ViewerContext = Void 116 | 117 | /** 118 | Type of the Query for this Schema. Should conform to `QueryType`. And should have the same Viewer Context as the Schema 119 | */ 120 | associatedtype Query: QueryType where Query.ViewerContext == ViewerContext 121 | 122 | 123 | /** 124 | Type of the Mutation for this Schema. Should conform to `MutationType`. And should have the same Viewer Context as the Schema. 125 | Defaults to `None` signalling GraphZahl that no mutations are allowed. 126 | */ 127 | associatedtype Mutation: MutationType = None where Mutation.ViewerContext == ViewerContext 128 | } 129 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/Root/MandatoryRootType.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | 4 | /** 5 | # Mandatory Root Type 6 | 7 | An GraphQL Root Type that has the additional requirement of being mandatory. For example for Query Types. 8 | */ 9 | public protocol MandatoryRootType : GraphQLRootType { } 10 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/Root/MutationType.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | 4 | /** 5 | # Mutation Type 6 | 7 | A mutation type can be any Root Type 8 | */ 9 | public typealias MutationType = GraphQLRootType 10 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/Root/QueryType.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | 4 | /** 5 | # Query Type 6 | 7 | A mutation type can be any Mandatory Root Type 8 | */ 9 | public typealias QueryType = MandatoryRootType 10 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/Scalar/Enum/GraphQLEnum.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import GraphQL 4 | import NIO 5 | import ContextKit 6 | 7 | public protocol GraphQLEnum: OutputResolvable, InputResolvable, ConcreteResolvable, ValueResolvable { 8 | static func cases(using context: inout Resolution.Context) throws -> [String : Self] 9 | } 10 | 11 | extension GraphQLEnum where Self: CaseIterable & RawRepresentable, RawValue == String { 12 | 13 | public static func cases(using context: inout Resolution.Context) throws -> [String : Self] { 14 | return Dictionary(uniqueKeysWithValues: allCases.map { ($0.rawValue, $0) }) 15 | } 16 | 17 | } 18 | 19 | extension GraphQLEnum { 20 | 21 | public static var additionalArguments: [String : InputResolvable.Type] { 22 | return [:] 23 | } 24 | 25 | static func resolveEnum(using context: inout Resolution.Context) throws -> GraphQLEnumType { 26 | let keysAndValues = try cases(using: &context).map { ($0.key.upperCamelized, GraphQLEnumValue(value: try $0.value.map())) } 27 | let values = Dictionary(uniqueKeysWithValues: keysAndValues) 28 | return try GraphQL.GraphQLEnumType(name: concreteTypeName, values: values) 29 | } 30 | 31 | public static func reference(using context: inout Resolution.Context) throws -> GraphQLOutputType { 32 | return GraphQLNonNull(GraphQLTypeReference(concreteTypeName)) 33 | } 34 | 35 | public static func resolve(using context: inout Resolution.Context) throws -> GraphQLInputType { 36 | return GraphQLNonNull(try resolveEnum(using: &context)) 37 | } 38 | 39 | public static func resolve(using context: inout Resolution.Context) throws -> GraphQLOutputType { 40 | return GraphQLNonNull(try resolveEnum(using: &context)) 41 | } 42 | 43 | public func resolve(source: Any, arguments: [String : Map], context: MutableContext, eventLoop: EventLoopGroup) throws -> Output { 44 | return .map(try map()) 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/Scalar/Enum/KeyPath+GraphQLEnum.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import Runtime 4 | import GraphQL 5 | import ContextKit 6 | import NIO 7 | 8 | extension KeyPath: Resolvable where Root: ConcreteResolvable & KeyPathListable { } 9 | 10 | extension KeyPath: ConcreteResolvable where Root: ConcreteResolvable & KeyPathListable { 11 | 12 | public static var concreteTypeName: String { 13 | return Root.concreteTypeName + "Field" 14 | } 15 | 16 | } 17 | 18 | extension KeyPath: InputResolvable where Root: ConcreteResolvable & KeyPathListable { 19 | 20 | public static func resolve(using context: inout Resolution.Context) throws -> GraphQLInputType { 21 | return GraphQLNonNull(try GraphQLEnumType(name: concreteTypeName, values: try cases(using: &context))) 22 | } 23 | 24 | public static func create(from map: Map) throws -> Self { 25 | let name = try map.stringValue() 26 | return (\Root.[checkedMirrorDescendant: name] as WritableKeyPath) as! Self 27 | } 28 | 29 | private static func cases(using context: inout Resolution.Context) throws -> [String : GraphQLEnumValue] { 30 | let keyPaths: [String : KeyPath] = try Root.resolve() 31 | 32 | let cases = try keyPaths 33 | .map { ($0.key.upperCamelized, try $0.key.map()) } 34 | 35 | return Dictionary(cases) { $1 }.mapValues { GraphQLEnumValue(value: $0) } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/Scalar/Scalar/GraphQLScalar+InputResolvable.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import GraphQL 4 | 5 | extension GraphQLScalar { 6 | 7 | public static func resolve(using context: inout Resolution.Context) throws -> GraphQLInputType { 8 | return try GraphQLNonNull(resolve()) 9 | } 10 | 11 | public static func create(from map: Map) throws -> Self { 12 | return try Self.init(scalar: try ScalarValue(map: map)) 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/Scalar/Scalar/GraphQLScalar+OutputResolvable.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import GraphQL 4 | import NIO 5 | import ContextKit 6 | 7 | extension GraphQLScalar { 8 | 9 | public static var additionalArguments: [String : InputResolvable.Type] { 10 | return [:] 11 | } 12 | 13 | public static func resolve(using context: inout Resolution.Context) throws -> GraphQLOutputType { 14 | return try GraphQLNonNull(resolve()) 15 | } 16 | 17 | public func resolve(source: Any, 18 | arguments: [String : Map], 19 | context: MutableContext, 20 | eventLoop: EventLoopGroup) throws -> Output { 21 | 22 | return .map(try self.encodeScalar().graphql()) 23 | } 24 | 25 | public func resolve(source: Any, arguments: [String : Map], context: MutableContext, eventLoop: EventLoopGroup) -> EventLoopFuture { 26 | return eventLoop.next().submit { 27 | try self.encodeScalar().graphql() 28 | } 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/Scalar/Scalar/GraphQLScalar.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import GraphQL 4 | 5 | public protocol GraphQLScalar: OutputResolvable, InputResolvable, ConcreteResolvable, ValueResolvable, KeyPathListable { 6 | static func resolve() throws -> GraphQLScalarType 7 | 8 | init(scalar: ScalarValue) throws 9 | func encodeScalar() throws -> ScalarValue 10 | } 11 | 12 | extension GraphQLScalar { 13 | 14 | public static func resolve() throws -> GraphQLScalarType { 15 | return try GraphQLScalarType(name: concreteTypeName, 16 | serialize: { $0 as! Map }, 17 | parseValue: { $0 }, 18 | parseLiteral: { try Self.parseLiteral(literal: $0) }) 19 | } 20 | 21 | public func map() throws -> Map { 22 | return try encodeScalar().graphql() 23 | } 24 | 25 | static func parseLiteral(literal: Value) throws -> Map { 26 | switch literal { 27 | case is StringValue: 28 | return try GraphQLString.parseLiteral(valueAST: literal) 29 | case is IntValue: 30 | return try GraphQLInt.parseLiteral(valueAST: literal) 31 | case is FloatValue: 32 | return try GraphQLFloat.parseLiteral(valueAST: literal) 33 | case is BooleanValue: 34 | return try GraphQLBoolean.parseLiteral(valueAST: literal) 35 | default: 36 | return .null 37 | } 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/Scalar/Scalar/Implementations/Bool+Scalar.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import GraphQL 4 | 5 | extension Bool: GraphQLScalar { 6 | 7 | public static let concreteTypeName = "Boolean" 8 | 9 | public init(scalar: ScalarValue) throws { 10 | self = try scalar.bool() 11 | } 12 | 13 | public func encodeScalar() throws -> ScalarValue { 14 | return .bool(self) 15 | } 16 | 17 | public static func resolve() throws -> GraphQLScalarType { 18 | return GraphQLBoolean 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/Scalar/Scalar/Implementations/Double+Scalar.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import GraphQL 4 | 5 | extension Double: GraphQLScalar { 6 | 7 | public static let concreteTypeName = "Float" 8 | 9 | public init(scalar: ScalarValue) throws { 10 | self = try scalar.float() 11 | } 12 | 13 | public func encodeScalar() throws -> ScalarValue { 14 | return .number(Number(self)) 15 | } 16 | 17 | public static func resolve() throws -> GraphQLScalarType { 18 | return GraphQLFloat 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/Scalar/Scalar/Implementations/Float+Scalar.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import GraphQL 4 | 5 | extension Float: GraphQLScalar { 6 | 7 | public init(scalar: ScalarValue) throws { 8 | self = Float(try scalar.float()) 9 | } 10 | 11 | public func encodeScalar() throws -> ScalarValue { 12 | return .number(Number(self)) 13 | } 14 | 15 | public static func resolve() throws -> GraphQLScalarType { 16 | return GraphQLFloat 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/Scalar/Scalar/Implementations/Int+Scalar.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import GraphQL 4 | 5 | extension Int: GraphQLScalar { 6 | 7 | public init(scalar: ScalarValue) throws { 8 | self = try scalar.int() 9 | } 10 | 11 | public func encodeScalar() throws -> ScalarValue { 12 | return .number(Number(self)) 13 | } 14 | 15 | public static func resolve() throws -> GraphQLScalarType { 16 | return GraphQLInt 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/Scalar/Scalar/Implementations/String+Scalar.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import GraphQL 4 | 5 | extension String: GraphQLScalar { 6 | 7 | public init(scalar: ScalarValue) throws { 8 | self = try scalar.string() 9 | } 10 | 11 | public func encodeScalar() throws -> ScalarValue { 12 | return .string(self) 13 | } 14 | 15 | public static func resolve() throws -> GraphQLScalarType { 16 | return GraphQLString 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/Scalar/Scalar/Implementations/UUID+Scalar.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import GraphQL 4 | 5 | extension UUID: GraphQLScalar { 6 | 7 | public static var concreteTypeName: String { 8 | return "ID" 9 | } 10 | 11 | public init(scalar: ScalarValue) throws { 12 | guard let uuid = UUID(uuidString: try scalar.string()) else { throw ScalarTypeError.valueFailedInnerTypeConstraints(scalar, forType: UUID.self) } 13 | self = uuid 14 | } 15 | 16 | public func encodeScalar() throws -> ScalarValue { 17 | return .string(uuidString) 18 | } 19 | 20 | public static func resolve() throws -> GraphQLScalarType { 21 | return GraphQLID 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/Scalar/Scalar/RawRepresentable+GraphQLScalar.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | 4 | extension RawRepresentable where RawValue: GraphQLScalar, Self: GraphQLScalar { 5 | 6 | public init(scalar: ScalarValue) throws { 7 | let rawValue = try RawValue(scalar: scalar) 8 | guard let value = Self(rawValue: rawValue) else { 9 | throw ScalarTypeError.valueFailedInnerTypeConstraints(scalar, forType: Self.self) 10 | } 11 | 12 | self = value 13 | } 14 | 15 | public func encodeScalar() throws -> ScalarValue { 16 | return try rawValue.encodeScalar() 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/Scalar/ScalarTypeError.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import GraphQL 4 | 5 | public enum ScalarTypeError: Error { 6 | case unexpectedValue(ScalarValue, expected: ScalarType) 7 | case valueFailedInnerTypeConstraints(ScalarValue, forType: GraphQLScalar.Type) 8 | case valueNotScalar(Map) 9 | } 10 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/Scalar/Type/ScalarType.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | 4 | public enum ScalarType { 5 | case string 6 | case float 7 | case int 8 | case bool 9 | case id 10 | } 11 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/Scalar/Value/IDValue.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | 4 | public enum IDValue { 5 | case string(String) 6 | case int(Int) 7 | } 8 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/Scalar/Value/ScalarValue+decode.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | 4 | extension ScalarValue { 5 | 6 | public func string() throws -> String { 7 | guard case .string(let string) = self else { throw ScalarTypeError.unexpectedValue(self, expected: .string) } 8 | return string 9 | } 10 | 11 | public func int() throws -> Int { 12 | guard case .number(let int) = self else { throw ScalarTypeError.unexpectedValue(self, expected: .int) } 13 | return int.intValue 14 | } 15 | 16 | public func float() throws -> Double { 17 | guard case .number(let float) = self else { throw ScalarTypeError.unexpectedValue(self, expected: .float) } 18 | return float.doubleValue 19 | } 20 | 21 | public func bool() throws -> Bool { 22 | guard case .bool(let bool) = self else { throw ScalarTypeError.unexpectedValue(self, expected: .bool) } 23 | return bool 24 | } 25 | 26 | public func id() throws -> IDValue { 27 | switch self { 28 | case .string(let string): 29 | return .string(string) 30 | case .number(let int): 31 | return .int(int.intValue) 32 | case .bool: 33 | throw ScalarTypeError.unexpectedValue(self, expected: .id) 34 | } 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/Scalar/Value/ScalarValue+init.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import GraphQL 4 | 5 | extension ScalarValue { 6 | 7 | init(map: Map) throws { 8 | switch map { 9 | case .null, .array, .dictionary: 10 | throw ScalarTypeError.valueNotScalar(map) 11 | case .bool(let bool): 12 | self = .bool(bool) 13 | case .number(let number): 14 | self = .number(number) 15 | case .string(let string): 16 | self = .string(string) 17 | } 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/Scalar/Value/ScalarValue.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import GraphQL 4 | 5 | public enum ScalarValue { 6 | case string(String) 7 | case number(Number) 8 | case bool(Bool) 9 | 10 | func graphql() throws -> Map { 11 | switch self { 12 | case .string(let string): 13 | return .string(string) 14 | case .number(let number): 15 | return .number(number) 16 | case .bool(let bool): 17 | return .bool(bool) 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Resolution/Union/GraphQLUnionType.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import GraphQL 4 | import NIO 5 | import ContextKit 6 | 7 | 8 | private var caseTypes = [Int : [GraphQLObject.Type]]() 9 | 10 | public protocol GraphQLUnion: ConcreteResolvable, OutputResolvable { } 11 | 12 | extension GraphQLUnion { 13 | 14 | public static var additionalArguments: [String : InputResolvable.Type] { 15 | return [:] 16 | } 17 | 18 | public static func reference(using context: inout Resolution.Context) throws -> GraphQLOutputType { 19 | return GraphQLNonNull(GraphQLTypeReference(concreteTypeName)) 20 | } 21 | 22 | public static func resolve(using context: inout Resolution.Context) throws -> GraphQLOutputType { 23 | let types = try caseObjectTypes().map { try context.resolve(object: $0) } 24 | return GraphQLNonNull(try GraphQLUnionType(name: concreteTypeName, resolveType: nil, types: types)) 25 | } 26 | 27 | public func resolve(source: Any, arguments: [String : Map], context: MutableContext, eventLoop: EventLoopGroup) throws -> Output { 28 | let cases = try Self.caseObjectTypes() 29 | 30 | let bits = Int(ceil(log2(Double(cases.count)))) 31 | let caseIndex = withUnsafeBytes(of: self) { Int($0.last!.mostSignificant(bits)) } 32 | let object = withUnsafeBytes(of: self) { bytes -> OutputResolvable in 33 | let mutable = bytes.mutableCopy() 34 | mutable.storeBytes(of: bytes.last!.zeroMostSignificant(bits), toByteOffset: bytes.count - 1, as: UInt8.self) 35 | return UnsafeRawPointer(mutable.baseAddress!).unsafeLoad(as: cases[caseIndex]) as! OutputResolvable 36 | } 37 | 38 | return try object.resolve(source: source, arguments: arguments, context: context, eventLoop: eventLoop) 39 | } 40 | 41 | private static func caseObjectTypes() throws -> [GraphQLObject.Type] { 42 | return try caseTypes.getOrPut(unsafeBitCast(Self.self, to: Int.self)) { 43 | let (kind, cases) = try typeInfo(of: Self.self, .kind, .cases) 44 | guard case .enum = kind else { 45 | throw Resolution.Error.unionTypeIsNotAnEnum(type: Self.self) 46 | } 47 | 48 | let objectMetatypesTypes = cases.compactMap { $0.payload as? GraphQLObject.Type } 49 | 50 | guard objectMetatypesTypes.count == cases.count else { 51 | let invalidCases = cases.map { $0.payload }.filter { $0 as? GraphQLObject.Type == nil } 52 | throw Resolution.Error.notAllCasesOfUnionAreGraphQLObjects(type: Self.self, 53 | valid: objectMetatypesTypes, 54 | invalid: invalidCases) 55 | } 56 | 57 | return objectMetatypesTypes 58 | } 59 | } 60 | 61 | } 62 | 63 | extension UnsafeRawBufferPointer { 64 | 65 | fileprivate func mutableCopy() -> UnsafeMutableRawBufferPointer { 66 | let pointer = UnsafeMutableRawBufferPointer.allocate(byteCount: count, alignment: MemoryLayout.alignment) 67 | pointer.copyMemory(from: self) 68 | return pointer 69 | } 70 | 71 | } 72 | 73 | extension UInt8 { 74 | 75 | fileprivate func zeroMostSignificant(_ bits: Int) -> UInt8 { 76 | let shape = UInt8.max >> bits 77 | return self & shape 78 | } 79 | 80 | fileprivate func mostSignificant(_ bits: Int) -> UInt8 { 81 | let shift = Self(MemoryLayout.size * 8 - bits) 82 | let shape = UInt8.max >> (MemoryLayout.size * 8 - bits) 83 | return (self >> shift) & shape 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Utils/ContextKey+ViewerContext.swift: -------------------------------------------------------------------------------- 1 | 2 | import ContextKit 3 | 4 | private var keys = [Int : Context.AnyKey]() 5 | 6 | extension Context.Key { 7 | 8 | public static var viewerContext: Context.Key { 9 | let keyForKey = unsafeBitCast(T.self as Any.Type, to: Int.self) 10 | return keys.getOrPut(keyForKey, default: Context.Key()) as! Context.Key 11 | } 12 | 13 | } 14 | 15 | extension Context.Key where T == Any { 16 | public static let anyViewerContext = Context.Key() 17 | } 18 | 19 | extension ContextKeyPaths { 20 | 21 | public var anyViewerContext: Context.Key { 22 | return .anyViewerContext 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Utils/Dictionary+getOrPut.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | 4 | extension Dictionary { 5 | 6 | mutating func getOrPut(_ key: Key, default defaultValue: @autoclosure () throws -> Value) rethrows -> Value { 7 | return try getOrPut(key) { try defaultValue() } 8 | } 9 | 10 | mutating func getOrPut(_ key: Key, default defaultValue: () throws -> Value) rethrows -> Value { 11 | guard let value = self[key] else { 12 | let value = try defaultValue() 13 | self[key] = value 14 | return value 15 | } 16 | return value 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Utils/Lock.swift: -------------------------------------------------------------------------------- 1 | #if os(Linux) 2 | import Glibc 3 | #else 4 | import Darwin.C 5 | #endif 6 | 7 | final class Lock { 8 | private var mutex = pthread_mutex_t() 9 | 10 | init() { 11 | let result = pthread_mutex_init(&mutex, nil) 12 | 13 | guard result == 0 else { 14 | fatalError("Failed to initialize Lock: \(result)") 15 | } 16 | } 17 | 18 | deinit { 19 | pthread_mutex_destroy(&mutex) 20 | } 21 | 22 | func withLock(_ closure: () throws -> (T)) rethrows -> T { 23 | acquire() 24 | defer { release() } 25 | return try closure() 26 | } 27 | 28 | func acquire() { 29 | let result = pthread_mutex_lock(&mutex) 30 | 31 | guard result == 0 else { 32 | fatalError("Failed to aquire Lock: \(result)") 33 | } 34 | } 35 | 36 | func release() { 37 | pthread_mutex_unlock(&mutex) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Utils/Method Dispatching/Default Value/Argument+defaultValue.swift.gyb: -------------------------------------------------------------------------------- 1 | // This file was automatically generated and should not be edited. 2 | 3 | import Runtime 4 | import CContext 5 | 6 | %{ 7 | def _combos(elements, start_idx, length): 8 | # ignore elements before start_idx 9 | for i in range(start_idx, len(elements)): 10 | elem, count = elements[i] 11 | if count == 0: 12 | continue 13 | # base case: only one element needed 14 | if length == 1: 15 | yield (elem,) 16 | else: 17 | # need more than one elem: mutate the list and recurse 18 | elements[i] = (elem, count - 1) 19 | # when we recurse, we ignore elements before this one 20 | # this ensures we find combinations, not permutations 21 | for combo in _combos(elements, i, length - 1): 22 | yield (elem,) + combo 23 | # fix the list 24 | elements[i] = (elem, count) 25 | 26 | 27 | def combos(elements, length): 28 | if length == 0: 29 | return [[]] 30 | elements = list(elements.items()) 31 | return _combos(elements, 0, length) 32 | 33 | allowedResults = 8 34 | 35 | allowedTypes = { "int" : "Int", "float" : "Double" } 36 | allowedResultMap = { "int" : 4, "float" : 4 } 37 | }% 38 | 39 | extension MethodInfo.Argument { 40 | 41 | func defaultValue() throws -> Any? { 42 | guard let address = defaultAddress else { return nil } 43 | 44 | let resultDecoder = try resolveDecoder(for: type) 45 | 46 | switch resultDecoder.results.count { 47 | % for numberOfResults in range(1, allowedResults + 1): 48 | case ${numberOfResults}: 49 | return try defaultWith${numberOfResults}(address: address, decoder: resultDecoder) 50 | % end 51 | default: 52 | fatalError("Calls with more than \(MethodInfo.maximumNumberOfArgumentsWithReflection) arguments are not allowed") 53 | } 54 | } 55 | 56 | } 57 | 58 | extension MethodInfo.Argument { 59 | 60 | % for numberOfResults in range(1, allowedResults + 1): 61 | private func defaultWith${numberOfResults}(address: UnsafeRawPointer, decoder: FunctionResultDecoder) throws -> Any { 62 | switch ( 63 | address 64 | % for index in range(numberOfResults): 65 | , decoder.results[${index}] 66 | % end 67 | ) { 68 | 69 | %{ resultCombinations = list(combos(allowedResultMap, numberOfResults)) }% 70 | % for results in resultCombinations: 71 | 72 | case ( 73 | _ 74 | % for index, type in enumerate(results): 75 | , .${type}(let res${index}) 76 | % end 77 | ): 78 | %{ result_prefix = "_".join(results) }% 79 | let function = unsafeBitCast(address, to: (@convention(thin) () -> (${result_prefix}_function_result)).self) 80 | let result = function() 81 | 82 | % for index in range(numberOfResults): 83 | res${index}.decode(result.res${index}) 84 | % end 85 | return try decoder.decode() 86 | % end 87 | 88 | default: 89 | fatalError() 90 | } 91 | } 92 | % end 93 | 94 | } 95 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Utils/Method Dispatching/MethodInfo+call.swift.gyb: -------------------------------------------------------------------------------- 1 | // This file was automatically generated and should not be edited. 2 | 3 | import Runtime 4 | import CContext 5 | 6 | %{ 7 | def _combos(elements, start_idx, length): 8 | # ignore elements before start_idx 9 | for i in range(start_idx, len(elements)): 10 | elem, count = elements[i] 11 | if count == 0: 12 | continue 13 | # base case: only one element needed 14 | if length == 1: 15 | yield (elem,) 16 | else: 17 | # need more than one elem: mutate the list and recurse 18 | elements[i] = (elem, count - 1) 19 | # when we recurse, we ignore elements before this one 20 | # this ensures we find combinations, not permutations 21 | for combo in _combos(elements, i, length - 1): 22 | yield (elem,) + combo 23 | # fix the list 24 | elements[i] = (elem, count) 25 | 26 | 27 | def combos(elements, length): 28 | if length == 0: 29 | return [[]] 30 | elements = list(elements.items()) 31 | return _combos(elements, 0, length) 32 | 33 | allowedArguments = 14 34 | allowedResults = 8 35 | 36 | allowedTypes = { "int" : "Int", "float" : "Double" } 37 | allowedArgumentMap = { "int" : 6, "float" : 8 } 38 | allowedResultMap = { "int" : 4, "float" : 4 } 39 | }% 40 | 41 | extension MethodInfo { 42 | 43 | func call(receiver: AnyObject, arguments: [Any]) throws -> Any { 44 | assert(arguments.count == self.arguments.count, "Argument count must correspond to original argument count") 45 | 46 | let arguments = try zip(arguments, self.arguments).flatMap { try resolveArguments(for: $0.0, using: $0.1.type) }.ordered() 47 | let resultDecoder = try resolveDecoder(for: returnType) 48 | 49 | switch (arguments.count, resultDecoder.results.count) { 50 | % for numberOfArguments in range(allowedArguments + 1): 51 | % for numberOfResults in range(allowedResults + 1): 52 | case (${numberOfArguments}, ${numberOfResults}): 53 | return try callWith${numberOfArguments}ArgumentsTo${numberOfResults}(receiver: receiver, arguments: arguments, decoder: resultDecoder) 54 | % end 55 | % end 56 | default: 57 | fatalError("Calls with more than \(MethodInfo.maximumNumberOfArgumentsWithReflection) arguments are not allowed") 58 | } 59 | } 60 | 61 | } 62 | 63 | extension MethodInfo { 64 | 65 | % for numberOfArguments in range(allowedArguments + 1): 66 | % for numberOfResults in range(allowedResults + 1): 67 | private func callWith${numberOfArguments}ArgumentsTo${numberOfResults}(receiver: AnyObject, arguments: [FunctionArgument], decoder: FunctionResultDecoder) throws -> Any { 68 | switch ( 69 | receiver 70 | % for index in range(numberOfArguments): 71 | , arguments[${index}] 72 | % end 73 | % for index in range(numberOfResults): 74 | , decoder.results[${index}] 75 | % end 76 | ) { 77 | 78 | %{ argumentCombinations = list(combos(allowedArgumentMap, numberOfArguments)) }% 79 | %{ resultCombinations = list(combos(allowedResultMap, numberOfResults)) }% 80 | % for arguments in argumentCombinations: 81 | % for results in resultCombinations: 82 | 83 | case ( 84 | _ 85 | % for index, type in enumerate(arguments): 86 | , .${type}(let arg${index}) 87 | % end 88 | % for index, type in enumerate(results): 89 | , .${type}(let res${index}) 90 | % end 91 | ): 92 | %{ types = ", ".join([allowedTypes[x] for x in arguments]) }% 93 | % if numberOfResults != 0: 94 | %{ result_prefix = "_".join(results) }% 95 | let function = unsafeBitCast(address, to: (@convention(c) (${types}) -> (${result_prefix}_function_result)).self) 96 | % else: 97 | let function = unsafeBitCast(address, to: (@convention(c) (${types}) -> (Void)).self) 98 | % end 99 | 100 | % for index, type in enumerate(arguments): 101 | let arg${index}Casted = arg${index}.value 102 | % end 103 | 104 | let selfPointer = Unmanaged.passUnretained(self).toOpaque() 105 | let receiverPointer = Unmanaged.passUnretained(receiver).toOpaque() 106 | set_self_pointer(receiverPointer) 107 | 108 | %{ argumentString = ", ".join(["arg" + str(i) + "Casted" for i in range(numberOfArguments)]) }% 109 | % if numberOfResults != 0: 110 | let result = function(${argumentString}) 111 | % else: 112 | function(${argumentString}) 113 | % end 114 | 115 | set_self_pointer(selfPointer) 116 | % for index in range(numberOfResults): 117 | res${index}.decode(result.res${index}) 118 | % end 119 | return try decoder.decode() 120 | 121 | % end 122 | % end 123 | 124 | default: 125 | fatalError() 126 | } 127 | } 128 | % end 129 | % end 130 | 131 | } 132 | 133 | extension MethodInfo { 134 | 135 | static let maximumNumberOfArgumentsWithReflection = ${allowedArguments} 136 | 137 | } 138 | 139 | private func estimatedType(of type: Any.Type) throws -> Any.Type { 140 | let kind = try typeInfo(of: type, .kind) 141 | switch kind { 142 | case .class: 143 | return AnyObject.self 144 | default: 145 | return type 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Utils/Method Dispatching/Register Usage/RegisterUsage.swift: -------------------------------------------------------------------------------- 1 | // This file was automatically generated and should not be edited. 2 | 3 | import Foundation 4 | import CContext 5 | import Runtime 6 | 7 | enum IntArgument { 8 | case int8(Int8) 9 | case int16(Int16) 10 | case int32(Int32) 11 | case int(Int) 12 | case pointer(UnsafeMutableRawPointer) 13 | 14 | var value: Int { 15 | switch self { 16 | case .int8(let value): 17 | return Int(from: value) 18 | case .int16(let value): 19 | return Int(from: value) 20 | case .int32(let value): 21 | return Int(from: value) 22 | case .int(let value): 23 | return Int(from: value) 24 | case .pointer(let value): 25 | return Int(from: value) 26 | } 27 | } 28 | } 29 | 30 | enum IntResult { 31 | case int8(UnsafeMutablePointer) 32 | case int16(UnsafeMutablePointer) 33 | case int32(UnsafeMutablePointer) 34 | case int(UnsafeMutablePointer) 35 | 36 | func decode(_ int: Int) { 37 | switch self { 38 | case .int8(let pointer): 39 | pointer.pointee = withUnsafeBytes(of: int) { $0.baseAddress!.load(as: Int8.self) } 40 | case .int16(let pointer): 41 | pointer.pointee = withUnsafeBytes(of: int) { $0.baseAddress!.load(as: Int16.self) } 42 | case .int32(let pointer): 43 | pointer.pointee = withUnsafeBytes(of: int) { $0.baseAddress!.load(as: Int32.self) } 44 | case .int(let pointer): 45 | pointer.pointee = withUnsafeBytes(of: int) { $0.baseAddress!.load(as: Int.self) } 46 | } 47 | } 48 | } 49 | 50 | enum FloatArgument { 51 | case float(Float) 52 | case double(Double) 53 | 54 | var value: Double { 55 | switch self { 56 | case .float(let value): 57 | return Double(from: value) 58 | case .double(let value): 59 | return Double(from: value) 60 | } 61 | } 62 | } 63 | 64 | extension FloatArgument { 65 | 66 | func intArgument() -> IntArgument { 67 | switch self { 68 | case .double(let double): 69 | return .int(Int(bitPattern: UInt(double.bitPattern))) 70 | case .float(let float): 71 | return .int32(Int32(bitPattern: float.bitPattern)) 72 | } 73 | } 74 | 75 | } 76 | 77 | enum FloatResult { 78 | case float(UnsafeMutablePointer) 79 | case double(UnsafeMutablePointer) 80 | 81 | func decode(_ double: Double) { 82 | switch self { 83 | case .float(let pointer): 84 | pointer.pointee = Float(double) 85 | case .double(let pointer): 86 | pointer.pointee = Double(double) 87 | } 88 | } 89 | } 90 | 91 | extension FloatResult { 92 | 93 | func intResult() -> IntResult { 94 | switch self { 95 | case .double(let pointer): 96 | return pointer.withMemoryRebound(to: Int.self, capacity: 1) { .int($0) } 97 | case .float(let pointer): 98 | return pointer.withMemoryRebound(to: Int32.self, capacity: 1) { .int32($0) } 99 | } 100 | } 101 | 102 | } 103 | 104 | enum FunctionArgument { 105 | case int(IntArgument) 106 | case float(FloatArgument) 107 | } 108 | 109 | extension FunctionArgument { 110 | 111 | func intArgument() -> FunctionArgument { 112 | switch self { 113 | case .int: 114 | return self 115 | case .float(let float): 116 | return .int(float.intArgument()) 117 | } 118 | } 119 | 120 | } 121 | 122 | enum FunctionResult { 123 | case int(IntResult) 124 | case float(FloatResult) 125 | } 126 | 127 | extension FunctionResult { 128 | 129 | func intResult() -> FunctionResult { 130 | switch self { 131 | case .int: 132 | return self 133 | case .float(let float): 134 | return .int(float.intResult()) 135 | } 136 | } 137 | 138 | } 139 | 140 | extension Sequence where Element == FunctionArgument { 141 | 142 | func ordered() -> [FunctionArgument] { 143 | return sorted { lhs, rhs in 144 | switch (lhs, rhs) { 145 | case (.int, .float): 146 | return true 147 | case (.int, .int), (.float, .float), (.float, .int): 148 | return false 149 | } 150 | } 151 | } 152 | 153 | } 154 | 155 | extension Sequence where Element == FunctionResult { 156 | 157 | func ordered() -> [FunctionResult] { 158 | return sorted { lhs, rhs in 159 | switch (lhs, rhs) { 160 | case (.int, .float): 161 | return true 162 | case (.int, .int), (.float, .float), (.float, .int): 163 | return false 164 | } 165 | } 166 | } 167 | 168 | } 169 | 170 | extension Int { 171 | 172 | fileprivate init(from transformable: T) { 173 | let pointer = UnsafeMutableRawPointer.allocate(byteCount: MemoryLayout.size, alignment: MemoryLayout.alignment) 174 | pointer.storeBytes(of: transformable, as: T.self) 175 | self = pointer.load(as: Int.self) 176 | } 177 | 178 | } 179 | 180 | extension Double { 181 | 182 | fileprivate init(from transformable: T) { 183 | let pointer = UnsafeMutableRawPointer.allocate(byteCount: MemoryLayout.size, alignment: MemoryLayout.alignment) 184 | pointer.storeBytes(of: transformable, as: T.self) 185 | self = pointer.load(as: Double.self) 186 | } 187 | 188 | } 189 | 190 | func resolveIntResults(for size: Int, pointer: UnsafeMutableRawPointer) -> [FunctionResult] { 191 | if size == 0 { 192 | return [] 193 | } 194 | let remainder = size % MemoryLayout.size 195 | if remainder == MemoryLayout.size { 196 | let newSize = size - MemoryLayout.size 197 | return resolveIntResults(for: newSize, pointer: pointer) + [.int(.int8(pointer.advanced(by: newSize).assumingMemoryBound(to: Int8.self)))] 198 | } 199 | if remainder == MemoryLayout.size { 200 | let newSize = size - MemoryLayout.size 201 | return resolveIntResults(for: newSize, pointer: pointer) + [.int(.int16(pointer.advanced(by: newSize).assumingMemoryBound(to: Int16.self)))] 202 | } 203 | if remainder == MemoryLayout.size { 204 | let newSize = size - MemoryLayout.size 205 | return resolveIntResults(for: newSize, pointer: pointer) + [.int(.int32(pointer.advanced(by: newSize).assumingMemoryBound(to: Int32.self)))] 206 | } 207 | 208 | let newSize = size - MemoryLayout.size 209 | return resolveIntResults(for: newSize, pointer: pointer) + [.int(.int(pointer.advanced(by: newSize).assumingMemoryBound(to: Int.self)))] 210 | } 211 | 212 | func isPrimitive(type: Any.Type) -> Bool { 213 | if type == Bool.self { 214 | return true 215 | } 216 | if type == Int8.self { 217 | return true 218 | } 219 | if type == Int16.self { 220 | return true 221 | } 222 | if type == Int32.self { 223 | return true 224 | } 225 | if type == Int.self { 226 | return true 227 | } 228 | if type == Float.self { 229 | return true 230 | } 231 | if type == Double.self { 232 | return true 233 | } 234 | 235 | return false 236 | } 237 | 238 | func isValueNil(value: Any, type: Any.Type) throws -> Bool { 239 | if value is NSNull && type != NSNull.self { 240 | return true 241 | } 242 | 243 | let size = try typeInfo(of: type, .size) 244 | 245 | let bytes = withUnsafeBytes(of: value) { $0.baseAddress!.assumingMemoryBound(to: Int8.self) } 246 | 247 | // Check that every value is zero 248 | guard UnsafeBufferPointer(start: bytes, count: size).allSatisfy({ $0 == 0 }) else { return false } 249 | 250 | // Check that the byte at the end is not zero 251 | guard bytes.advanced(by: size).pointee != 0 else { return false } 252 | 253 | return true 254 | } 255 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Utils/Method Dispatching/Register Usage/RegisterUsage.swift.gyb: -------------------------------------------------------------------------------- 1 | // This file was automatically generated and should not be edited. 2 | 3 | import Foundation 4 | import CContext 5 | import Runtime 6 | %{ int_types = ["Int8", "Int16", "Int32", "Int"] }% 7 | %{ float_types = ["Float", "Double"] }% 8 | 9 | enum IntArgument { 10 | % for swiftType in int_types: 11 | case ${swiftType.lower()}(${swiftType}) 12 | % end 13 | case pointer(UnsafeMutableRawPointer) 14 | 15 | var value: Int { 16 | switch self { 17 | % for swiftType in int_types: 18 | case .${swiftType.lower()}(let value): 19 | return Int(from: value) 20 | % end 21 | case .pointer(let value): 22 | return Int(from: value) 23 | } 24 | } 25 | } 26 | 27 | enum IntResult { 28 | % for swiftType in int_types: 29 | case ${swiftType.lower()}(UnsafeMutablePointer<${swiftType}>) 30 | % end 31 | 32 | func decode(_ int: Int) { 33 | switch self { 34 | % for swiftType in int_types: 35 | case .${swiftType.lower()}(let pointer): 36 | pointer.pointee = withUnsafeBytes(of: int) { $0.baseAddress!.load(as: ${swiftType}.self) } 37 | % end 38 | } 39 | } 40 | } 41 | 42 | enum FloatArgument { 43 | % for swiftType in float_types: 44 | case ${swiftType.lower()}(${swiftType}) 45 | % end 46 | 47 | var value: Double { 48 | switch self { 49 | % for swiftType in float_types: 50 | case .${swiftType.lower()}(let value): 51 | return Double(from: value) 52 | % end 53 | } 54 | } 55 | } 56 | 57 | extension FloatArgument { 58 | 59 | func intArgument() -> IntArgument { 60 | switch self { 61 | case .double(let double): 62 | return .int(Int(bitPattern: UInt(double.bitPattern))) 63 | case .float(let float): 64 | return .int32(Int32(bitPattern: float.bitPattern)) 65 | } 66 | } 67 | 68 | } 69 | 70 | enum FloatResult { 71 | % for swiftType in float_types: 72 | case ${swiftType.lower()}(UnsafeMutablePointer<${swiftType}>) 73 | % end 74 | 75 | func decode(_ double: Double) { 76 | switch self { 77 | % for swiftType in float_types: 78 | case .${swiftType.lower()}(let pointer): 79 | pointer.pointee = ${swiftType}(double) 80 | % end 81 | } 82 | } 83 | } 84 | 85 | extension FloatResult { 86 | 87 | func intResult() -> IntResult { 88 | switch self { 89 | case .double(let pointer): 90 | return pointer.withMemoryRebound(to: Int.self, capacity: 1) { .int($0) } 91 | case .float(let pointer): 92 | return pointer.withMemoryRebound(to: Int32.self, capacity: 1) { .int32($0) } 93 | } 94 | } 95 | 96 | } 97 | 98 | enum FunctionArgument { 99 | case int(IntArgument) 100 | case float(FloatArgument) 101 | } 102 | 103 | extension FunctionArgument { 104 | 105 | func intArgument() -> FunctionArgument { 106 | switch self { 107 | case .int: 108 | return self 109 | case .float(let float): 110 | return .int(float.intArgument()) 111 | } 112 | } 113 | 114 | } 115 | 116 | enum FunctionResult { 117 | case int(IntResult) 118 | case float(FloatResult) 119 | } 120 | 121 | extension FunctionResult { 122 | 123 | func intResult() -> FunctionResult { 124 | switch self { 125 | case .int: 126 | return self 127 | case .float(let float): 128 | return .int(float.intResult()) 129 | } 130 | } 131 | 132 | } 133 | 134 | extension Sequence where Element == FunctionArgument { 135 | 136 | func ordered() -> [FunctionArgument] { 137 | return sorted { lhs, rhs in 138 | switch (lhs, rhs) { 139 | case (.int, .float): 140 | return true 141 | case (.int, .int), (.float, .float), (.float, .int): 142 | return false 143 | } 144 | } 145 | } 146 | 147 | } 148 | 149 | extension Sequence where Element == FunctionResult { 150 | 151 | func ordered() -> [FunctionResult] { 152 | return sorted { lhs, rhs in 153 | switch (lhs, rhs) { 154 | case (.int, .float): 155 | return true 156 | case (.int, .int), (.float, .float), (.float, .int): 157 | return false 158 | } 159 | } 160 | } 161 | 162 | } 163 | 164 | extension Int { 165 | 166 | fileprivate init(from transformable: T) { 167 | let pointer = UnsafeMutableRawPointer.allocate(byteCount: MemoryLayout.size, alignment: MemoryLayout.alignment) 168 | pointer.storeBytes(of: transformable, as: T.self) 169 | self = pointer.load(as: Int.self) 170 | } 171 | 172 | } 173 | 174 | extension Double { 175 | 176 | fileprivate init(from transformable: T) { 177 | let pointer = UnsafeMutableRawPointer.allocate(byteCount: MemoryLayout.size, alignment: MemoryLayout.alignment) 178 | pointer.storeBytes(of: transformable, as: T.self) 179 | self = pointer.load(as: Double.self) 180 | } 181 | 182 | } 183 | 184 | func resolveIntResults(for size: Int, pointer: UnsafeMutableRawPointer) -> [FunctionResult] { 185 | if size == 0 { 186 | return [] 187 | } 188 | let remainder = size % MemoryLayout.size 189 | %{ relevant_types = [type for type in int_types if type != "Int"] }% 190 | % for swiftType in relevant_types: 191 | if remainder == MemoryLayout<${swiftType}>.size { 192 | let newSize = size - MemoryLayout<${swiftType}>.size 193 | return resolveIntResults(for: newSize, pointer: pointer) + [.int(.${swiftType.lower()}(pointer.advanced(by: newSize).assumingMemoryBound(to: ${swiftType}.self)))] 194 | } 195 | % end 196 | 197 | let newSize = size - MemoryLayout.size 198 | return resolveIntResults(for: newSize, pointer: pointer) + [.int(.int(pointer.advanced(by: newSize).assumingMemoryBound(to: Int.self)))] 199 | } 200 | 201 | func isPrimitive(type: Any.Type) -> Bool { 202 | if type == Bool.self { 203 | return true 204 | } 205 | % for swiftType in int_types: 206 | if type == ${swiftType}.self { 207 | return true 208 | } 209 | % end 210 | % for swiftType in float_types: 211 | if type == ${swiftType}.self { 212 | return true 213 | } 214 | % end 215 | 216 | return false 217 | } 218 | 219 | func isValueNil(value: Any, type: Any.Type) throws -> Bool { 220 | if value is NSNull && type != NSNull.self { 221 | return true 222 | } 223 | 224 | let size = try typeInfo(of: type, .size) 225 | 226 | let bytes = withUnsafeBytes(of: value) { $0.baseAddress!.assumingMemoryBound(to: Int8.self) } 227 | 228 | // Check that every value is zero 229 | guard UnsafeBufferPointer(start: bytes, count: size).allSatisfy({ $0 == 0 }) else { return false } 230 | 231 | // Check that the byte at the end is not zero 232 | guard bytes.advanced(by: size).pointee != 0 else { return false } 233 | 234 | return true 235 | } 236 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Utils/Method Dispatching/Register Usage/Resolvable/MethodCallResolvable.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | 4 | 5 | protocol MethodCallResolvable { 6 | static func arguments(value: Any, isNil: Bool) throws -> [FunctionArgument] 7 | static func decoder(pointer: UnsafeMutableRawPointer) throws -> ([FunctionResult], Int) 8 | } 9 | 10 | extension MethodCallResolvable { 11 | 12 | static var hasExtraInhabitants: Bool { 13 | return MemoryLayout.size == MemoryLayout.size 14 | } 15 | 16 | } 17 | 18 | protocol SelfMethodCallResolvable: MethodCallResolvable { 19 | static func createArguments(value: Self) throws -> [FunctionArgument] 20 | static func createNil() throws -> [FunctionArgument] 21 | } 22 | 23 | extension SelfMethodCallResolvable { 24 | static func arguments(value: Any, isNil: Bool) throws -> [FunctionArgument] { 25 | if isNil { 26 | return try createNil() 27 | } 28 | return try createArguments(value: value as! Self) 29 | } 30 | } 31 | 32 | extension Int8: SelfMethodCallResolvable { 33 | static func createArguments(value: Self) throws -> [FunctionArgument] { 34 | return [.int(.int8(value))] 35 | } 36 | 37 | static func createNil() throws -> [FunctionArgument] { 38 | return [.int(.int8(0))] 39 | } 40 | 41 | static func decoder(pointer: UnsafeMutableRawPointer) throws -> ([FunctionResult], Int) { 42 | return ([.int(.int8(pointer.assumingMemoryBound(to: Int8.self)))], MemoryLayout.stride) 43 | } 44 | } 45 | 46 | extension Int16: SelfMethodCallResolvable { 47 | static func createArguments(value: Self) throws -> [FunctionArgument] { 48 | return [.int(.int16(value))] 49 | } 50 | 51 | static func createNil() throws -> [FunctionArgument] { 52 | return [.int(.int16(0))] 53 | } 54 | 55 | static func decoder(pointer: UnsafeMutableRawPointer) throws -> ([FunctionResult], Int) { 56 | return ([.int(.int16(pointer.assumingMemoryBound(to: Int16.self)))], MemoryLayout.stride) 57 | } 58 | } 59 | 60 | extension Int32: SelfMethodCallResolvable { 61 | static func createArguments(value: Self) throws -> [FunctionArgument] { 62 | return [.int(.int32(value))] 63 | } 64 | 65 | static func createNil() throws -> [FunctionArgument] { 66 | return [.int(.int32(0))] 67 | } 68 | 69 | static func decoder(pointer: UnsafeMutableRawPointer) throws -> ([FunctionResult], Int) { 70 | return ([.int(.int32(pointer.assumingMemoryBound(to: Int32.self)))], MemoryLayout.stride) 71 | } 72 | } 73 | 74 | extension Int: SelfMethodCallResolvable { 75 | static func createArguments(value: Self) throws -> [FunctionArgument] { 76 | return [.int(.int(value))] 77 | } 78 | 79 | static func createNil() throws -> [FunctionArgument] { 80 | return [.int(.int(0))] 81 | } 82 | 83 | static func decoder(pointer: UnsafeMutableRawPointer) throws -> ([FunctionResult], Int) { 84 | return ([.int(.int(pointer.assumingMemoryBound(to: Int.self)))], MemoryLayout.stride) 85 | } 86 | } 87 | 88 | 89 | extension Float: SelfMethodCallResolvable { 90 | static func createArguments(value: Self) throws -> [FunctionArgument] { 91 | return [.float(.float(value))] 92 | } 93 | 94 | static func createNil() throws -> [FunctionArgument] { 95 | return [.float(.float(0))] 96 | } 97 | 98 | static func decoder(pointer: UnsafeMutableRawPointer) throws -> ([FunctionResult], Int) { 99 | return ([.float(.float(pointer.assumingMemoryBound(to: Float.self)))], MemoryLayout.stride) 100 | } 101 | } 102 | 103 | extension Double: SelfMethodCallResolvable { 104 | static func createArguments(value: Self) throws -> [FunctionArgument] { 105 | return [.float(.double(value))] 106 | } 107 | 108 | static func createNil() throws -> [FunctionArgument] { 109 | return [.float(.double(0))] 110 | } 111 | 112 | static func decoder(pointer: UnsafeMutableRawPointer) throws -> ([FunctionResult], Int) { 113 | return ([.float(.double(pointer.assumingMemoryBound(to: Double.self)))], MemoryLayout.stride) 114 | } 115 | } 116 | 117 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Utils/Method Dispatching/Register Usage/Resolvable/MethodCallResolvable.swift.gyb: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | 4 | %{ int_types = ["Int8", "Int16", "Int32", "Int"] }% 5 | %{ float_types = ["Float", "Double"] }% 6 | 7 | protocol MethodCallResolvable { 8 | static func arguments(value: Any, isNil: Bool) throws -> [FunctionArgument] 9 | static func decoder(pointer: UnsafeMutableRawPointer) throws -> ([FunctionResult], Int) 10 | } 11 | 12 | extension MethodCallResolvable { 13 | 14 | static var hasExtraInhabitants: Bool { 15 | return MemoryLayout.size == MemoryLayout.size 16 | } 17 | 18 | } 19 | 20 | protocol SelfMethodCallResolvable: MethodCallResolvable { 21 | static func createArguments(value: Self) throws -> [FunctionArgument] 22 | static func createNil() throws -> [FunctionArgument] 23 | } 24 | 25 | extension SelfMethodCallResolvable { 26 | static func arguments(value: Any, isNil: Bool) throws -> [FunctionArgument] { 27 | if isNil { 28 | return try createNil() 29 | } 30 | return try createArguments(value: value as! Self) 31 | } 32 | } 33 | 34 | % for swiftType in int_types: 35 | extension ${swiftType}: SelfMethodCallResolvable { 36 | static func createArguments(value: Self) throws -> [FunctionArgument] { 37 | return [.int(.${swiftType.lower()}(value))] 38 | } 39 | 40 | static func createNil() throws -> [FunctionArgument] { 41 | return [.int(.${swiftType.lower()}(0))] 42 | } 43 | 44 | static func decoder(pointer: UnsafeMutableRawPointer) throws -> ([FunctionResult], Int) { 45 | return ([.int(.${swiftType.lower()}(pointer.assumingMemoryBound(to: ${swiftType}.self)))], MemoryLayout<${swiftType}>.stride) 46 | } 47 | } 48 | 49 | % end 50 | 51 | % for swiftType in float_types: 52 | extension ${swiftType}: SelfMethodCallResolvable { 53 | static func createArguments(value: Self) throws -> [FunctionArgument] { 54 | return [.float(.${swiftType.lower()}(value))] 55 | } 56 | 57 | static func createNil() throws -> [FunctionArgument] { 58 | return [.float(.${swiftType.lower()}(0))] 59 | } 60 | 61 | static func decoder(pointer: UnsafeMutableRawPointer) throws -> ([FunctionResult], Int) { 62 | return ([.float(.${swiftType.lower()}(pointer.assumingMemoryBound(to: ${swiftType}.self)))], MemoryLayout<${swiftType}>.stride) 63 | } 64 | } 65 | 66 | % end 67 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Utils/Method Dispatching/Register Usage/Resolvable/SpecialCases.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | 4 | protocol SpecialCase: SelfMethodCallResolvable { } 5 | 6 | extension SpecialCase { 7 | static func createArguments(value: Self) throws -> [FunctionArgument] { 8 | let bytes = withUnsafeBytes(of: value) { $0.bindMemory(to: Int.self) } 9 | return bytes.map { .int(.int($0)) } 10 | } 11 | 12 | static func createNil() throws -> [FunctionArgument] { 13 | let numberOfBytes = MemoryLayout.size / 8 14 | return Array(repeating: .int(.int(0)), count: numberOfBytes) 15 | } 16 | 17 | static func decoder(pointer: UnsafeMutableRawPointer) throws -> ([FunctionResult], Int) { 18 | let numberOfBytes = MemoryLayout.size / 8 19 | let array = (0...stride) 21 | } 22 | } 23 | 24 | protocol ReferenceConvertibleSpecialScase: SpecialCase, ReferenceConvertible { } 25 | 26 | extension ReferenceConvertibleSpecialScase { 27 | 28 | static func createArguments(value: Self) throws -> [FunctionArgument] { 29 | let pointer = UnsafeMutableRawPointer.allocate(byteCount: MemoryLayout.size, alignment: MemoryLayout.alignment) 30 | pointer.storeBytes(of: value, as: Self.self) 31 | return [.int(.pointer(pointer))] 32 | } 33 | 34 | static func decoder(pointer: UnsafeMutableRawPointer) throws -> ([FunctionResult], Int) { 35 | return ([.int(.int(pointer.assumingMemoryBound(to: Int.self)))], MemoryLayout.stride) 36 | } 37 | 38 | } 39 | 40 | extension Bool: SelfMethodCallResolvable { 41 | 42 | static func createArguments(value: Bool) throws -> [FunctionArgument] { 43 | return [.int(.int8(value ? 1 : 0))] 44 | } 45 | 46 | static func createNil() throws -> [FunctionArgument] { 47 | return [.int(.int8(2))] 48 | } 49 | 50 | static func decoder(pointer: UnsafeMutableRawPointer) throws -> ([FunctionResult], Int) { 51 | return ([.int(.int8(pointer.assumingMemoryBound(to: Int8.self)))], MemoryLayout.stride) 52 | } 53 | 54 | } 55 | 56 | extension String: SpecialCase { } 57 | 58 | extension Array: SpecialCase { } 59 | 60 | extension Date: ReferenceConvertibleSpecialScase { } 61 | 62 | extension UUID: ReferenceConvertibleSpecialScase { } 63 | 64 | extension URL: ReferenceConvertibleSpecialScase { } 65 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Utils/Method Dispatching/Register Usage/Resolve/FunctionResultDecoder.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import Runtime 4 | 5 | struct FunctionResultDecoder { 6 | let type: Any.Type 7 | let pointer: UnsafeRawBufferPointer 8 | let results: [FunctionResult] 9 | 10 | func decode() throws -> Any { 11 | if type == Void.self { 12 | return () 13 | } 14 | 15 | return pointer.baseAddress!.unsafeLoad(as: type) 16 | } 17 | } 18 | 19 | func resolveResults(for type: Any.Type, pointer: UnsafeMutableRawPointer) throws -> ([FunctionResult], Int) { 20 | if let type = type as? MethodCallResolvable.Type { 21 | return try type.decoder(pointer: pointer) 22 | } 23 | 24 | let (kind, genericTypes, properties, size, stride) = try typeInfo(of: type, .kind, .genericTypes, .properties, .size, .stride) 25 | 26 | switch kind { 27 | case .class: 28 | return ([.int(.int(pointer.assumingMemoryBound(to: Int.self)))], MemoryLayout.size) 29 | case .optional: 30 | let actualType = genericTypes.first! 31 | if let actualType = actualType as? MethodCallResolvable.Type, actualType.hasExtraInhabitants { 32 | return try actualType.decoder(pointer: pointer) 33 | } 34 | 35 | if isPrimitive(type: actualType) { 36 | let (result, offset) = try resolveResults(for: actualType, pointer: pointer) 37 | return (result.map { $0.intResult() } + [.int(.int8(pointer.advanced(by: offset).assumingMemoryBound(to: Int8.self)))], stride) 38 | } 39 | 40 | let kind = try typeInfo(of: actualType, .kind) 41 | if kind == .class { 42 | return try resolveResults(for: actualType, pointer: pointer) 43 | } 44 | 45 | // TODO: Refactor to use allow enum layouts: https://github.com/apple/swift/blob/master/docs/ABI/TypeLayout.rst#fragile-enum-layout 46 | let (result, _) = try resolveResults(for: actualType, pointer: pointer.advanced(by: 1)) 47 | return ([.int(.int8(pointer.assumingMemoryBound(to: Int8.self)))] + result.map { $0.intResult() }, stride) 48 | case .enum: 49 | return (resolveIntResults(for: size, pointer: pointer), stride) 50 | default: 51 | return try properties.reduce(([], 0)) { accumulator, property in 52 | let (results, offset) = try resolveResults(for: property.type, pointer: pointer.advanced(by: accumulator.1)) 53 | return (accumulator.0 + results, accumulator.1 + offset) 54 | } 55 | } 56 | } 57 | 58 | func resolveDecoder(for type: Any.Type) throws -> FunctionResultDecoder { 59 | let (size, stride, alignment) = try typeInfo(of: type, .size, .stride, .alignment) 60 | 61 | let pointer = UnsafeMutableRawBufferPointer.allocate(byteCount: size + 1, alignment: alignment) 62 | let (results, offset) = try resolveResults(for: type, pointer: pointer.baseAddress!) 63 | assert(size <= offset && offset <= stride) 64 | 65 | return FunctionResultDecoder(type: type, 66 | pointer: UnsafeRawBufferPointer(pointer), 67 | results: results.ordered()) 68 | } 69 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Utils/Method Dispatching/Register Usage/Resolve/resolveArguments.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import Runtime 4 | 5 | func resolveArguments(for value: Any, using type: Any.Type, isNil: Bool = false) throws -> [FunctionArgument] { 6 | if let type = type as? MethodCallResolvable.Type { 7 | return try type.arguments(value: value, isNil: isNil) 8 | } 9 | 10 | let (kind, genericTypes, properties) = try typeInfo(of: type, .kind, .genericTypes, .properties) 11 | 12 | switch kind { 13 | case .class: 14 | if isNil { 15 | return [.int(.int(0))] 16 | } 17 | return [.int(.pointer(Unmanaged.passUnretained(value as AnyObject).toOpaque()))] 18 | case .optional: 19 | let actualType = genericTypes.first! 20 | let isNil = try isNil || isValueNil(value: value, type: actualType) 21 | 22 | if let actualType = actualType as? MethodCallResolvable.Type, actualType.hasExtraInhabitants { 23 | return try actualType.arguments(value: value, isNil: isNil) 24 | } 25 | 26 | let kind = try typeInfo(of: actualType, .kind) 27 | if kind == .class { 28 | return try resolveArguments(for: value, using: actualType, isNil: isNil) 29 | } 30 | 31 | return try resolveArguments(for: value, using: actualType, isNil: isNil).map { $0.intArgument() } + [.int(.int8(isNil ? 1 : 0))] 32 | case .enum: 33 | if isNil { 34 | return [.int(.int8(0))] 35 | } 36 | return [.int(.int8(withUnsafeBytes(of: value) { $0.bindMemory(to: Int8.self).baseAddress!.pointee }))] 37 | default: 38 | return try properties.flatMap { try resolveArguments(for: isNil ? 0 : $0.get(from: value), using: $0.type, isNil: isNil) } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Utils/Method Dispatching/typeInfo.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import Runtime 4 | 5 | 6 | struct TypeInfoValue { 7 | let keyPath: KeyPath 8 | let includedFlag: TypeInfo.IncludeOptions? 9 | } 10 | 11 | 12 | extension TypeInfoValue where T == [MethodInfo] { 13 | static let methods = TypeInfoValue(keyPath: \.methods, includedFlag: .methods) 14 | } 15 | 16 | 17 | extension TypeInfoValue where T == TypeInfo.IncludeOptions { 18 | static let includedInfo = TypeInfoValue(keyPath: \.includedInfo, includedFlag: nil) 19 | } 20 | 21 | 22 | extension TypeInfoValue where T == [Case] { 23 | static let cases = TypeInfoValue(keyPath: \.cases, includedFlag: .cases) 24 | } 25 | 26 | 27 | extension TypeInfoValue where T == [PropertyInfo] { 28 | static let properties = TypeInfoValue(keyPath: \.properties, includedFlag: .properties) 29 | } 30 | 31 | 32 | extension TypeInfoValue where T == Int { 33 | static let alignment = TypeInfoValue(keyPath: \.alignment, includedFlag: nil) 34 | } 35 | 36 | 37 | extension TypeInfoValue where T == Int { 38 | static let size = TypeInfoValue(keyPath: \.size, includedFlag: nil) 39 | } 40 | 41 | 42 | extension TypeInfoValue where T == Kind { 43 | static let kind = TypeInfoValue(keyPath: \.kind, includedFlag: nil) 44 | } 45 | 46 | 47 | extension TypeInfoValue where T == String { 48 | static let name = TypeInfoValue(keyPath: \.name, includedFlag: nil) 49 | } 50 | 51 | 52 | extension TypeInfoValue where T == [Any.Type] { 53 | static let inheritance = TypeInfoValue(keyPath: \.inheritance, includedFlag: .inheritance) 54 | } 55 | 56 | 57 | extension TypeInfoValue where T == Int { 58 | static let stride = TypeInfoValue(keyPath: \.stride, includedFlag: nil) 59 | } 60 | 61 | 62 | extension TypeInfoValue where T == String { 63 | static let mangledName = TypeInfoValue(keyPath: \.mangledName, includedFlag: .mangledName) 64 | } 65 | 66 | 67 | extension TypeInfoValue where T == [Any.Type] { 68 | static let genericTypes = TypeInfoValue(keyPath: \.genericTypes, includedFlag: .genericTypes) 69 | } 70 | 71 | 72 | extension TypeInfoValue where T == Any.Type { 73 | static let type = TypeInfoValue(keyPath: \.type, includedFlag: nil) 74 | } 75 | 76 | 77 | func typeInfo(of type: Any.Type, _ arg0: TypeInfoValue) throws -> (Arg0) { 78 | let flags = TypeInfo.IncludeOptions([arg0.includedFlag].compactMap { $0 }) 79 | let info = try typeInfo(of: type, include: flags) 80 | return (info[keyPath: arg0.keyPath]) 81 | } 82 | 83 | func typeInfo(of type: Any.Type, _ arg0: TypeInfoValue, _ arg1: TypeInfoValue) throws -> (Arg0, Arg1) { 84 | let flags = TypeInfo.IncludeOptions([arg0.includedFlag, arg1.includedFlag].compactMap { $0 }) 85 | let info = try typeInfo(of: type, include: flags) 86 | return (info[keyPath: arg0.keyPath], info[keyPath: arg1.keyPath]) 87 | } 88 | 89 | func typeInfo(of type: Any.Type, _ arg0: TypeInfoValue, _ arg1: TypeInfoValue, _ arg2: TypeInfoValue) throws -> (Arg0, Arg1, Arg2) { 90 | let flags = TypeInfo.IncludeOptions([arg0.includedFlag, arg1.includedFlag, arg2.includedFlag].compactMap { $0 }) 91 | let info = try typeInfo(of: type, include: flags) 92 | return (info[keyPath: arg0.keyPath], info[keyPath: arg1.keyPath], info[keyPath: arg2.keyPath]) 93 | } 94 | 95 | func typeInfo(of type: Any.Type, _ arg0: TypeInfoValue, _ arg1: TypeInfoValue, _ arg2: TypeInfoValue, _ arg3: TypeInfoValue) throws -> (Arg0, Arg1, Arg2, Arg3) { 96 | let flags = TypeInfo.IncludeOptions([arg0.includedFlag, arg1.includedFlag, arg2.includedFlag, arg3.includedFlag].compactMap { $0 }) 97 | let info = try typeInfo(of: type, include: flags) 98 | return (info[keyPath: arg0.keyPath], info[keyPath: arg1.keyPath], info[keyPath: arg2.keyPath], info[keyPath: arg3.keyPath]) 99 | } 100 | 101 | func typeInfo(of type: Any.Type, _ arg0: TypeInfoValue, _ arg1: TypeInfoValue, _ arg2: TypeInfoValue, _ arg3: TypeInfoValue, _ arg4: TypeInfoValue) throws -> (Arg0, Arg1, Arg2, Arg3, Arg4) { 102 | let flags = TypeInfo.IncludeOptions([arg0.includedFlag, arg1.includedFlag, arg2.includedFlag, arg3.includedFlag, arg4.includedFlag].compactMap { $0 }) 103 | let info = try typeInfo(of: type, include: flags) 104 | return (info[keyPath: arg0.keyPath], info[keyPath: arg1.keyPath], info[keyPath: arg2.keyPath], info[keyPath: arg3.keyPath], info[keyPath: arg4.keyPath]) 105 | } 106 | 107 | func typeInfo(of type: Any.Type, _ arg0: TypeInfoValue, _ arg1: TypeInfoValue, _ arg2: TypeInfoValue, _ arg3: TypeInfoValue, _ arg4: TypeInfoValue, _ arg5: TypeInfoValue) throws -> (Arg0, Arg1, Arg2, Arg3, Arg4, Arg5) { 108 | let flags = TypeInfo.IncludeOptions([arg0.includedFlag, arg1.includedFlag, arg2.includedFlag, arg3.includedFlag, arg4.includedFlag, arg5.includedFlag].compactMap { $0 }) 109 | let info = try typeInfo(of: type, include: flags) 110 | return (info[keyPath: arg0.keyPath], info[keyPath: arg1.keyPath], info[keyPath: arg2.keyPath], info[keyPath: arg3.keyPath], info[keyPath: arg4.keyPath], info[keyPath: arg5.keyPath]) 111 | } 112 | 113 | func typeInfo(of type: Any.Type, _ arg0: TypeInfoValue, _ arg1: TypeInfoValue, _ arg2: TypeInfoValue, _ arg3: TypeInfoValue, _ arg4: TypeInfoValue, _ arg5: TypeInfoValue, _ arg6: TypeInfoValue) throws -> (Arg0, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6) { 114 | let flags = TypeInfo.IncludeOptions([arg0.includedFlag, arg1.includedFlag, arg2.includedFlag, arg3.includedFlag, arg4.includedFlag, arg5.includedFlag, arg6.includedFlag].compactMap { $0 }) 115 | let info = try typeInfo(of: type, include: flags) 116 | return (info[keyPath: arg0.keyPath], info[keyPath: arg1.keyPath], info[keyPath: arg2.keyPath], info[keyPath: arg3.keyPath], info[keyPath: arg4.keyPath], info[keyPath: arg5.keyPath], info[keyPath: arg6.keyPath]) 117 | } 118 | 119 | func typeInfo(of type: Any.Type, _ arg0: TypeInfoValue, _ arg1: TypeInfoValue, _ arg2: TypeInfoValue, _ arg3: TypeInfoValue, _ arg4: TypeInfoValue, _ arg5: TypeInfoValue, _ arg6: TypeInfoValue, _ arg7: TypeInfoValue) throws -> (Arg0, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7) { 120 | let flags = TypeInfo.IncludeOptions([arg0.includedFlag, arg1.includedFlag, arg2.includedFlag, arg3.includedFlag, arg4.includedFlag, arg5.includedFlag, arg6.includedFlag, arg7.includedFlag].compactMap { $0 }) 121 | let info = try typeInfo(of: type, include: flags) 122 | return (info[keyPath: arg0.keyPath], info[keyPath: arg1.keyPath], info[keyPath: arg2.keyPath], info[keyPath: arg3.keyPath], info[keyPath: arg4.keyPath], info[keyPath: arg5.keyPath], info[keyPath: arg6.keyPath], info[keyPath: arg7.keyPath]) 123 | } 124 | 125 | func typeInfo(of type: Any.Type, _ arg0: TypeInfoValue, _ arg1: TypeInfoValue, _ arg2: TypeInfoValue, _ arg3: TypeInfoValue, _ arg4: TypeInfoValue, _ arg5: TypeInfoValue, _ arg6: TypeInfoValue, _ arg7: TypeInfoValue, _ arg8: TypeInfoValue) throws -> (Arg0, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Arg8) { 126 | let flags = TypeInfo.IncludeOptions([arg0.includedFlag, arg1.includedFlag, arg2.includedFlag, arg3.includedFlag, arg4.includedFlag, arg5.includedFlag, arg6.includedFlag, arg7.includedFlag, arg8.includedFlag].compactMap { $0 }) 127 | let info = try typeInfo(of: type, include: flags) 128 | return (info[keyPath: arg0.keyPath], info[keyPath: arg1.keyPath], info[keyPath: arg2.keyPath], info[keyPath: arg3.keyPath], info[keyPath: arg4.keyPath], info[keyPath: arg5.keyPath], info[keyPath: arg6.keyPath], info[keyPath: arg7.keyPath], info[keyPath: arg8.keyPath]) 129 | } 130 | 131 | func typeInfo(of type: Any.Type, _ arg0: TypeInfoValue, _ arg1: TypeInfoValue, _ arg2: TypeInfoValue, _ arg3: TypeInfoValue, _ arg4: TypeInfoValue, _ arg5: TypeInfoValue, _ arg6: TypeInfoValue, _ arg7: TypeInfoValue, _ arg8: TypeInfoValue, _ arg9: TypeInfoValue) throws -> (Arg0, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Arg8, Arg9) { 132 | let flags = TypeInfo.IncludeOptions([arg0.includedFlag, arg1.includedFlag, arg2.includedFlag, arg3.includedFlag, arg4.includedFlag, arg5.includedFlag, arg6.includedFlag, arg7.includedFlag, arg8.includedFlag, arg9.includedFlag].compactMap { $0 }) 133 | let info = try typeInfo(of: type, include: flags) 134 | return (info[keyPath: arg0.keyPath], info[keyPath: arg1.keyPath], info[keyPath: arg2.keyPath], info[keyPath: arg3.keyPath], info[keyPath: arg4.keyPath], info[keyPath: arg5.keyPath], info[keyPath: arg6.keyPath], info[keyPath: arg7.keyPath], info[keyPath: arg8.keyPath], info[keyPath: arg9.keyPath]) 135 | } 136 | 137 | func typeInfo(of type: Any.Type, _ arg0: TypeInfoValue, _ arg1: TypeInfoValue, _ arg2: TypeInfoValue, _ arg3: TypeInfoValue, _ arg4: TypeInfoValue, _ arg5: TypeInfoValue, _ arg6: TypeInfoValue, _ arg7: TypeInfoValue, _ arg8: TypeInfoValue, _ arg9: TypeInfoValue, _ arg10: TypeInfoValue) throws -> (Arg0, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Arg8, Arg9, Arg10) { 138 | let flags = TypeInfo.IncludeOptions([arg0.includedFlag, arg1.includedFlag, arg2.includedFlag, arg3.includedFlag, arg4.includedFlag, arg5.includedFlag, arg6.includedFlag, arg7.includedFlag, arg8.includedFlag, arg9.includedFlag, arg10.includedFlag].compactMap { $0 }) 139 | let info = try typeInfo(of: type, include: flags) 140 | return (info[keyPath: arg0.keyPath], info[keyPath: arg1.keyPath], info[keyPath: arg2.keyPath], info[keyPath: arg3.keyPath], info[keyPath: arg4.keyPath], info[keyPath: arg5.keyPath], info[keyPath: arg6.keyPath], info[keyPath: arg7.keyPath], info[keyPath: arg8.keyPath], info[keyPath: arg9.keyPath], info[keyPath: arg10.keyPath]) 141 | } 142 | 143 | func typeInfo(of type: Any.Type, _ arg0: TypeInfoValue, _ arg1: TypeInfoValue, _ arg2: TypeInfoValue, _ arg3: TypeInfoValue, _ arg4: TypeInfoValue, _ arg5: TypeInfoValue, _ arg6: TypeInfoValue, _ arg7: TypeInfoValue, _ arg8: TypeInfoValue, _ arg9: TypeInfoValue, _ arg10: TypeInfoValue, _ arg11: TypeInfoValue) throws -> (Arg0, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Arg8, Arg9, Arg10, Arg11) { 144 | let flags = TypeInfo.IncludeOptions([arg0.includedFlag, arg1.includedFlag, arg2.includedFlag, arg3.includedFlag, arg4.includedFlag, arg5.includedFlag, arg6.includedFlag, arg7.includedFlag, arg8.includedFlag, arg9.includedFlag, arg10.includedFlag, arg11.includedFlag].compactMap { $0 }) 145 | let info = try typeInfo(of: type, include: flags) 146 | return (info[keyPath: arg0.keyPath], info[keyPath: arg1.keyPath], info[keyPath: arg2.keyPath], info[keyPath: arg3.keyPath], info[keyPath: arg4.keyPath], info[keyPath: arg5.keyPath], info[keyPath: arg6.keyPath], info[keyPath: arg7.keyPath], info[keyPath: arg8.keyPath], info[keyPath: arg9.keyPath], info[keyPath: arg10.keyPath], info[keyPath: arg11.keyPath]) 147 | } 148 | 149 | func typeInfo(of type: Any.Type, _ arg0: TypeInfoValue, _ arg1: TypeInfoValue, _ arg2: TypeInfoValue, _ arg3: TypeInfoValue, _ arg4: TypeInfoValue, _ arg5: TypeInfoValue, _ arg6: TypeInfoValue, _ arg7: TypeInfoValue, _ arg8: TypeInfoValue, _ arg9: TypeInfoValue, _ arg10: TypeInfoValue, _ arg11: TypeInfoValue, _ arg12: TypeInfoValue) throws -> (Arg0, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Arg8, Arg9, Arg10, Arg11, Arg12) { 150 | let flags = TypeInfo.IncludeOptions([arg0.includedFlag, arg1.includedFlag, arg2.includedFlag, arg3.includedFlag, arg4.includedFlag, arg5.includedFlag, arg6.includedFlag, arg7.includedFlag, arg8.includedFlag, arg9.includedFlag, arg10.includedFlag, arg11.includedFlag, arg12.includedFlag].compactMap { $0 }) 151 | let info = try typeInfo(of: type, include: flags) 152 | return (info[keyPath: arg0.keyPath], info[keyPath: arg1.keyPath], info[keyPath: arg2.keyPath], info[keyPath: arg3.keyPath], info[keyPath: arg4.keyPath], info[keyPath: arg5.keyPath], info[keyPath: arg6.keyPath], info[keyPath: arg7.keyPath], info[keyPath: arg8.keyPath], info[keyPath: arg9.keyPath], info[keyPath: arg10.keyPath], info[keyPath: arg11.keyPath], info[keyPath: arg12.keyPath]) 153 | } 154 | 155 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Utils/Method Dispatching/typeInfo.swift.gyb: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import Runtime 4 | 5 | %{ 6 | infos = { 7 | "kind" : "Kind", 8 | "name" : "String", 9 | "type" : "Any.Type", 10 | "mangledName" : "String", 11 | "properties" : "[PropertyInfo]", 12 | "methods" : "[MethodInfo]", 13 | "inheritance" : "[Any.Type]", 14 | "size" : "Int", 15 | "alignment" : "Int", 16 | "stride" : "Int", 17 | "cases" : "[Case]", 18 | "genericTypes" : "[Any.Type]", 19 | "includedInfo" : "TypeInfo.IncludeOptions" 20 | } 21 | 22 | includedOptions = ["mangledName", "properties", "methods", "inheritance", "cases", "genericTypes"] 23 | }% 24 | 25 | struct TypeInfoValue { 26 | let keyPath: KeyPath 27 | let includedFlag: TypeInfo.IncludeOptions? 28 | } 29 | 30 | % for property, type in infos.items(): 31 | 32 | extension TypeInfoValue where T == ${type} { 33 | %{ includedFlag = "." + property if property in includedOptions else "nil" }% 34 | static let ${property} = TypeInfoValue(keyPath: \.${property}, includedFlag: ${includedFlag}) 35 | } 36 | 37 | % end 38 | 39 | % for numberOfValues in range(1, len(infos) + 1): 40 | %{ argTypes = ["Arg" + str(i) for i in range(numberOfValues)] }% 41 | %{ argParameters = ["_ arg" + str(i) + ": TypeInfoValue" for i in range(numberOfValues)] }% 42 | %{ argTypeString = ", ".join(argTypes) }% 43 | %{ argParameterString = ", ".join(argParameters) }% 44 | func typeInfo<${argTypeString}>(of type: Any.Type, ${argParameterString}) throws -> (${argTypeString}) { 45 | %{ argParameterFlagList = ["arg" + str(i) + ".includedFlag" for i in range(numberOfValues)] }% 46 | %{ argParameterString = ", ".join(argParameterFlagList) }% 47 | let flags = TypeInfo.IncludeOptions([${argParameterString}].compactMap { $0 }) 48 | let info = try typeInfo(of: type, include: flags) 49 | %{ typeAccess = ["info[keyPath: arg" + str(i) + ".keyPath]" for i in range(numberOfValues)] }% 50 | %{ typeAccessString = ", ".join(typeAccess) }% 51 | return (${typeAccessString}) 52 | } 53 | 54 | % end 55 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Utils/MethodInfo+resolve.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import Runtime 4 | import GraphQL 5 | import NIO 6 | import ContextKit 7 | 8 | extension MethodInfo { 9 | 10 | func resolve(for receiverType: GraphQLObject.Type, using context: inout Resolution.Context) throws -> GraphQLField? { 11 | do { 12 | guard let returnType = returnType as? OutputResolvable.Type else { return nil } 13 | 14 | let viewerContext = context.viewerContextType 15 | let relevantArguments = arguments.filter { $0.type != MutableContext.self && $0.type != viewerContext } 16 | let mappedArguments = relevantArguments.compactMap { argument in argument.name.map { ($0, argument) } } 17 | let arguments = try Dictionary(uniqueKeysWithValues: mappedArguments) 18 | .compactMapValues { argument -> GraphQLArgument? in 19 | guard let argumentType = argument.type as? InputResolvable.Type else { return nil } 20 | 21 | let type = try context.resolve(type: argumentType) 22 | 23 | guard let defaultValue = try argument.defaultValue() else { 24 | return GraphQLArgument(type: type, defaultValue: nil) 25 | } 26 | 27 | guard let valueResolvable = defaultValue as? ValueResolvable else { 28 | switch type { 29 | case let type as GraphQLNonNull: 30 | return GraphQLArgument(type: type.ofType as! GraphQLInputType, defaultValue: nil) 31 | default: 32 | return GraphQLArgument(type: type, defaultValue: nil) 33 | } 34 | } 35 | 36 | return GraphQLArgument(type: type, defaultValue: try valueResolvable.map()) 37 | } 38 | 39 | guard arguments.count == relevantArguments.count else { return nil } 40 | 41 | guard arguments.count <= MethodInfo.maximumNumberOfArgumentsWithReflection else { 42 | // Print a warning in such cases to make sure the developers catch it 43 | print("Warning: Method \(receiverType).\(methodName) is technically abstractable to GraphQL but it has too many arguments.") 44 | print(" Currently we don't support more than \(MethodInfo.maximumNumberOfArgumentsWithReflection) arguments when using reflection.") 45 | print(" This is due to the limitations on reflection in swift. We had to draw a line regarding the number of arguments somewhere.") 46 | print(" If this is really necessary please contact the mantainers to increase this limit.") 47 | 48 | return nil 49 | } 50 | 51 | let completeArguments = try returnType 52 | .additionalGraphqlArguments(using: &context) 53 | .merging(arguments) { $1 } 54 | 55 | return GraphQLField(type: try context.reference(for: returnType), 56 | args: completeArguments) { (source, args, context, eventLoop, _) -> Future in 57 | 58 | let args = try args.dictionaryValue() 59 | let object = receiverType.object(from: source) 60 | return try self.call(receiver: object, 61 | argumentMap: args, 62 | context: context as! MutableContext, 63 | eventLoop: eventLoop, 64 | viewerContext: viewerContext) 65 | } 66 | } catch Resolution.Error.viewerContextDidNotMatchExpectedType { 67 | return nil 68 | } catch { 69 | throw error 70 | } 71 | } 72 | 73 | } 74 | 75 | extension MethodInfo { 76 | 77 | fileprivate func call(receiver: AnyObject, 78 | argumentMap: [String : Map], 79 | context: MutableContext, 80 | eventLoop: EventLoopGroup, 81 | viewerContext: Any.Type) throws -> EventLoopFuture { 82 | 83 | let arguments = try self.arguments.map { argument -> Any in 84 | if argument.type == MutableContext.self { 85 | return context 86 | } 87 | 88 | if argument.type == viewerContext { 89 | return context.anyViewerContext 90 | } 91 | 92 | guard let name = argument.name, 93 | let argumentType = argument.type as? InputResolvable.Type else { fatalError() } 94 | 95 | if let value = argumentMap[name] { 96 | return try argumentType.create(from: value) 97 | } 98 | 99 | if argument.defaultAddress != nil { 100 | return try argument.defaultValue()! 101 | } 102 | 103 | return try argumentType.createFromMissingKey() 104 | } as [Any] 105 | 106 | let result = try self.call(receiver: receiver, arguments: arguments) 107 | 108 | // TODO: for some reason this breaks with arrays... 109 | // this will break the server if we ever return [Future] 110 | if let result = result as? OutputResolvable { 111 | return try result.resolve(source: receiver, arguments: argumentMap, context: context, eventLoop: eventLoop).convert(eventLoopGroup: eventLoop) 112 | } 113 | 114 | return eventLoop.next().makeSucceededFuture(result) 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Utils/PropertyInfo+resolve.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import Runtime 4 | import GraphQL 5 | import NIO 6 | import ContextKit 7 | 8 | extension PropertyInfo { 9 | 10 | func resolve(for receiverType: GraphQLObject.Type, using context: inout Resolution.Context) throws -> PropertyResult? { 11 | do { 12 | if let type = type as? CustomGraphQLProperty.Type { 13 | return try type.resolve(with: self, for: receiverType, using: &context) 14 | } 15 | 16 | guard let type = type as? OutputResolvable.Type else { return nil } 17 | 18 | let arguments = try type.additionalGraphqlArguments(using: &context) 19 | 20 | let field = GraphQLField(type: try context.reference(for: type), 21 | args: arguments) { source, arguments, context, eventLoop, _ in 22 | 23 | let object = receiverType.object(from: source) 24 | 25 | let result = try self.get(from: object) 26 | if let result = result as? OutputResolvable { 27 | let arguments = try arguments.dictionaryValue() 28 | return try result.resolve(source: object, arguments: arguments, context: context as! MutableContext, eventLoop: eventLoop).convert(eventLoopGroup: eventLoop) 29 | } 30 | 31 | return eventLoop.next().makeSucceededFuture(result) 32 | } 33 | 34 | return .field(name.deleting(prefix: "_"), field) 35 | } catch Resolution.Error.viewerContextDidNotMatchExpectedType { 36 | return nil 37 | } catch { 38 | throw error 39 | } 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Utils/String+camelCasing.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | 4 | // Copied and adapted from https://gist.github.com/reitzig/67b41e75176ddfd432cb09392a270218 5 | // Modifications made public at: https://gist.github.com/nerdsupremacist/8e620beb2dcf6404f9edcac756ed28dc 6 | fileprivate let badChars = CharacterSet.alphanumerics.inverted 7 | 8 | extension String { 9 | var uppercasingFirst: String { 10 | return prefix(1).uppercased() + dropFirst().lowercased() 11 | } 12 | 13 | var lowercasingFirst: String { 14 | return prefix(1).lowercased() + dropFirst().lowercased() 15 | } 16 | 17 | var camelized: String { 18 | guard !isEmpty else { 19 | return "" 20 | } 21 | 22 | guard uppercased() != self else { return lowercased() } 23 | 24 | let parts = self.parts 25 | let first = String(describing: parts.first!).lowercasingFirst 26 | let rest = parts.dropFirst().map({String($0).uppercasingFirst}) 27 | 28 | return ([first] + rest).joined(separator: "") 29 | } 30 | 31 | var upperCamelized: String { 32 | guard !isEmpty else { 33 | return "" 34 | } 35 | 36 | return parts.map { String($0).uppercasingFirst }.joined(separator: "") 37 | } 38 | 39 | private var parts: [String] { 40 | let basics = replacingOccurrences(of: "([a-z])([A-Z])", 41 | with: "$1 $2", 42 | options: .regularExpression) 43 | 44 | let complete = basics.replacingOccurrences(of: "([A-Z]+)([A-Z][a-z]|$)", 45 | with: "$1 $2", 46 | options: .regularExpression) 47 | 48 | return complete.components(separatedBy: badChars) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Sources/GraphZahl/Utils/String+deletingPrefix.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | 4 | extension String { 5 | func deleting(prefix: String) -> String { 6 | guard self.hasPrefix(prefix) else { return self } 7 | return String(self.dropFirst(prefix.count)) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Sources/GraphZahlTests/CallingTests.swift: -------------------------------------------------------------------------------- 1 | 2 | import XCTest 3 | import Runtime 4 | import NIO 5 | import ContextKit 6 | @testable import GraphQL 7 | @testable import GraphZahl 8 | 9 | private let google = URL(string: "https://google.com")! 10 | 11 | class CallingTests: XCTestCase { 12 | 13 | static var allTests: [(String, (CallingTests) -> () throws -> Void)] { 14 | return [ 15 | ("testArray", testArray) 16 | ] 17 | } 18 | 19 | // TODO: Fix with https://github.com/nerdsupremacist/GraphZahl/issues/22 20 | func ignore_testURL() throws { 21 | let info = try typeInfo(of: MyClass.self) 22 | let instance = MyClass() 23 | let method = info.methods.first { $0.methodName == "url" }! 24 | let result = try method.call(receiver: instance, arguments: []) as! URL 25 | XCTAssertEqual(result, google) 26 | } 27 | 28 | func testEnumCase() throws { 29 | let info = try typeInfo(of: MyClass.self) 30 | let instance = MyClass() 31 | let method = info.methods.first { $0.methodName == "enumCase" }! 32 | let result = try method.call(receiver: instance, arguments: []) as! MyEnum 33 | XCTAssertEqual(result, .first) 34 | } 35 | 36 | func testOptionalBool() throws { 37 | let info = try typeInfo(of: MyClass.self) 38 | let instance = MyClass() 39 | let method = info.methods.first { $0.methodName == "optionalBool" }! 40 | let result = try method.call(receiver: instance, arguments: []) 41 | XCTAssertEqual(result as! Bool, true) 42 | } 43 | 44 | func testOptionalDouble() throws { 45 | let info = try typeInfo(of: MyClass.self) 46 | let instance = MyClass() 47 | let method = info.methods.first { $0.methodName == "optionalDouble" }! 48 | let result = try method.call(receiver: instance, arguments: []) 49 | XCTAssertEqual(result as! Double, 42) 50 | } 51 | 52 | func testOptionalEnumCase() throws { 53 | let info = try typeInfo(of: MyClass.self) 54 | let instance = MyClass() 55 | let method = info.methods.first { $0.methodName == "optioanlEnumCase" }! 56 | let result = try method.call(receiver: instance, arguments: []) as! MyEnum 57 | XCTAssertEqual(result, .first) 58 | } 59 | 60 | func testOptionalArray() throws { 61 | let info = try typeInfo(of: MyClass.self) 62 | let instance = MyClass() 63 | let method = info.methods.first { $0.methodName == "optionalArray" }! 64 | let result = try method.call(receiver: instance, arguments: []) as! [String] 65 | XCTAssertEqual(result, ["Hello World!"]) 66 | } 67 | 68 | func testOptionalClass() throws { 69 | let info = try typeInfo(of: MyClass.self) 70 | let instance = MyClass() 71 | let method = info.methods.first { $0.methodName == "optionalClass" }! 72 | let result = try method.call(receiver: instance, arguments: []) 73 | XCTAssert(result as! MyClass === instance) 74 | } 75 | 76 | func testOptionalString() throws { 77 | let info = try typeInfo(of: MyClass.self) 78 | let instance = MyClass() 79 | let method = info.methods.first { $0.methodName == "optionalString" }! 80 | let result = try method.call(receiver: instance, arguments: ["World" as Any]) 81 | XCTAssertEqual(result as! String, "Hello World!") 82 | } 83 | 84 | func testArray() throws { 85 | let info = try typeInfo(of: MyClass.self) 86 | let instance = MyClass() 87 | let method = info.methods.first { $0.methodName == "doit" }! 88 | let arguments = [try method.arguments[0].defaultValue()!] as [Any] 89 | let string = try method.call(receiver: instance, arguments: arguments) as! String 90 | XCTAssertEqual(string, "firstsecond") 91 | } 92 | 93 | func testOtherArray() throws { 94 | let info = try typeInfo(of: MyClass.self) 95 | let method = info.methods.first { $0.methodName == "doit" }! 96 | 97 | var context = Resolution.Context.empty(viewerContextType: Void.self, viewerContext: ()) 98 | let field = try method.resolve(for: MyClass.self, using: &context)! 99 | 100 | let instance = MyClass() 101 | let eventLoop = MultiThreadedEventLoopGroup(numberOfThreads: 1) 102 | 103 | let type = try GraphQLObjectType(name: "thing", fields: ["field":field]) 104 | let schema = try GraphQL.GraphQLSchema(query: type) 105 | let mutableContext = MutableContext() 106 | let resolveInfo = GraphQLResolveInfo(fieldName: "", 107 | fieldASTs: [], 108 | returnType: GraphQLString, 109 | parentType: type, 110 | path: [], 111 | schema: schema, 112 | fragments: [:], 113 | rootValue: instance, 114 | operation: OperationDefinition(operation: .query, selectionSet: SelectionSet(selections: [])), 115 | variableValues: [:]) 116 | 117 | let string = try field.resolve!(instance, [:] as Map, mutableContext, eventLoop, resolveInfo).wait() as! Map 118 | XCTAssertEqual(try string.stringValue(), "firstsecond") 119 | } 120 | } 121 | 122 | enum MyEnum: String, CaseIterable, GraphQLEnum { 123 | static let mostCases: [MyEnum] = [.first, .second] 124 | 125 | case first 126 | case second 127 | case third 128 | } 129 | 130 | class MyClass: GraphQLObject { 131 | var url: URL { 132 | return google 133 | } 134 | 135 | func optionalBool() -> Bool? { 136 | return true 137 | } 138 | 139 | func optionalDouble() -> Double? { 140 | return 42 141 | } 142 | 143 | func optionalInt() -> Int? { 144 | return 42 145 | } 146 | 147 | func optionalClass() -> MyClass? { 148 | return self 149 | } 150 | 151 | func optionalString(name: String?) -> String? { 152 | return name.map { "Hello \($0)!" } 153 | } 154 | 155 | func optionalArray() -> [String]? { 156 | return ["Hello World!"] 157 | } 158 | 159 | func optioanlEnumCase() -> MyEnum? { 160 | return .first 161 | } 162 | 163 | func enumCase() -> MyEnum { 164 | return .first 165 | } 166 | 167 | func doit(cases: [MyEnum] = MyEnum.mostCases) -> String { 168 | return cases.map(\.rawValue).joined() 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /Sources/GraphZahlTests/SchemaResolutionTests.swift: -------------------------------------------------------------------------------- 1 | 2 | import XCTest 3 | import Runtime 4 | import NIO 5 | import ContextKit 6 | @testable import GraphZahl 7 | import GraphQL 8 | 9 | class SchemaResolutionTests: XCTestCase { 10 | 11 | func testUnion() throws { 12 | let query = """ 13 | { 14 | union { 15 | __typename 16 | ... on Foo { 17 | foo 18 | } 19 | ... on Bar { 20 | bar 21 | } 22 | ... on Baz { 23 | baz 24 | } 25 | } 26 | } 27 | """ 28 | 29 | let result = try Schema.perform(request: query).wait() 30 | 31 | let expectedData: Map = [ 32 | "union" : [ 33 | "__typename": "Bar", 34 | "bar": 42, 35 | ] 36 | ] 37 | 38 | XCTAssertEqual(expectedData, result.data) 39 | } 40 | 41 | func testDelegatedOutputs() throws { 42 | struct SomeInt: DelegatedOutputResolvable { 43 | let int: Int 44 | 45 | func resolve(source: Any, arguments: [String : Map], context: MutableContext, eventLoop: EventLoopGroup) throws -> some OutputResolvable { 46 | return int 47 | } 48 | } 49 | 50 | var context = Resolution.Context.empty(viewerContextType: Int.self, viewerContext: 42) 51 | let output = try SomeInt.resolve(using: &context) 52 | guard let nonNull = output as? GraphQLNonNull else { 53 | XCTFail("outptut of some int is nullable") 54 | return 55 | } 56 | guard let scalar = nonNull.ofType as? GraphQLScalarType else { 57 | XCTFail("outptut of some int is not a scalar") 58 | return 59 | } 60 | XCTAssertEqual(scalar, GraphQLInt) 61 | } 62 | 63 | func testKeypaths() throws { 64 | let query = """ 65 | { 66 | foo(filter: Foo, equals: "Foo1") { 67 | foo 68 | } 69 | } 70 | """ 71 | 72 | let result = try Schema.perform(request: query).wait() 73 | 74 | let expectedData: Map = [ 75 | "foo" : [ 76 | ["foo": "Foo1"] 77 | ] 78 | ] 79 | 80 | XCTAssertEqual(expectedData, result.data) 81 | } 82 | 83 | func testIdAndDateCombination() throws { 84 | let query = """ 85 | { 86 | idAndDateCombination(id: "1E9FF7FB-7424-4319-9BFA-470F4C6549FA", date: "2020-06-18T12:54:13Z") 87 | } 88 | """ 89 | 90 | let result = try Schema.perform(request: query).wait() 91 | 92 | let expectedData: Map = [ 93 | "idAndDateCombination" : 42, 94 | ] 95 | 96 | XCTAssertEqual(expectedData, result.data) 97 | } 98 | 99 | func testCombiningDoublesAndFloats() throws { 100 | let query = """ 101 | { 102 | combiningDoublesAndFloats(bool: true, float: 1.0) { 103 | double 104 | } 105 | } 106 | """ 107 | 108 | let result = try Schema.perform(request: query).wait() 109 | 110 | let expectedData: Map = [ 111 | "combiningDoublesAndFloats" : [ 112 | "double" : 42 113 | ], 114 | ] 115 | 116 | XCTAssertEqual(expectedData, result.data) 117 | } 118 | 119 | } 120 | 121 | class Schema: GraphZahl.GraphQLSchema { 122 | class Query: QueryType { 123 | func combiningDoublesAndFloats(bool: Bool, float: Float) -> ObjectWithDouble { 124 | return ObjectWithDouble() 125 | } 126 | 127 | func union() -> Union { 128 | return .bar(Bar(bar: 42)) 129 | } 130 | 131 | func idAndDateCombination(id: UUID, date: Date) -> Int { 132 | return 42 133 | } 134 | 135 | func foo(filter: KeyPath, equals: String) -> [Foo] { 136 | return [ 137 | Foo(foo: "Foo"), 138 | Foo(foo: "Foo1"), 139 | Foo(foo: "Foo2"), 140 | ].filter { $0[keyPath: filter] == equals } 141 | } 142 | 143 | required init(viewerContext: ()) { } 144 | } 145 | 146 | } 147 | 148 | extension Schema { 149 | 150 | class ObjectWithDouble: GraphQLObject { 151 | let double: Double = 42 152 | } 153 | 154 | class Foo: GraphQLObject { 155 | let foo: String 156 | 157 | init(foo: String) { 158 | self.foo = foo 159 | } 160 | } 161 | 162 | class Bar: GraphQLObject { 163 | let bar: Int 164 | 165 | init(bar: Int) { 166 | self.bar = bar 167 | } 168 | } 169 | 170 | class Baz: GraphQLObject { 171 | let baz: Bool 172 | 173 | init(baz: Bool) { 174 | self.baz = baz 175 | } 176 | } 177 | 178 | enum Union: GraphQLUnion { 179 | case foo(Foo) 180 | case bar(Bar) 181 | case baz(Baz) 182 | } 183 | 184 | } 185 | 186 | private let dateFormatter = ISO8601DateFormatter() 187 | 188 | extension Date: GraphQLScalar { 189 | public init(scalar: ScalarValue) throws { 190 | guard let date = dateFormatter.date(from: try scalar.string()) else { 191 | throw ScalarTypeError.valueFailedInnerTypeConstraints(scalar, forType: Date.self) 192 | } 193 | self = date 194 | } 195 | public func encodeScalar() throws -> ScalarValue { 196 | return try dateFormatter.string(from: self).encodeScalar() 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /demo/helloworld.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdsupremacist/GraphZahl/c306e04e7ef80ec8d27fd351d263e430de3d934a/demo/helloworld.png -------------------------------------------------------------------------------- /demo/nestedobject.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdsupremacist/GraphZahl/c306e04e7ef80ec8d27fd351d263e430de3d934a/demo/nestedobject.png -------------------------------------------------------------------------------- /demo/object.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdsupremacist/GraphZahl/c306e04e7ef80ec8d27fd351d263e430de3d934a/demo/object.png -------------------------------------------------------------------------------- /demo/urlscalar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdsupremacist/GraphZahl/c306e04e7ef80ec8d27fd351d263e430de3d934a/demo/urlscalar.png -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdsupremacist/GraphZahl/c306e04e7ef80ec8d27fd351d263e430de3d934a/logo.png -------------------------------------------------------------------------------- /useGYB: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | find . -name '*.gyb' | \ 4 | while read file; do \ 5 | gyb --line-directive '' -o "${file%.gyb}" "$file"; \ 6 | done 7 | --------------------------------------------------------------------------------