├── .gitignore ├── .swiftpm └── xcode │ └── package.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── LICENSE ├── Package.swift ├── README.md ├── SociableWeaver.xcodeproj ├── SociableWeaverTests_Info.plist ├── SociableWeaver_Info.plist ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings └── xcshareddata │ └── xcschemes │ ├── SociableWeaver-Package.xcscheme │ └── SociableWeaverTests.xcscheme ├── Sources └── SociableWeaver │ ├── Enumerations │ ├── CaseStyleOption.swift │ └── OperationType.swift │ ├── Extensions │ ├── Array+Argument.swift │ └── String+Utils.swift │ ├── FunctionBuilders │ ├── ObjectBuilder.swift │ └── OperationBuilder.swift │ ├── Helpers │ ├── Field.swift │ ├── ForEachWeavable.swift │ ├── Fragment │ │ ├── Fragment.swift │ │ ├── FragmentBuilder.swift │ │ ├── FragmentReference.swift │ │ └── InlineFragment.swift │ ├── MetaField.swift │ ├── Object │ │ ├── Object.swift │ │ ├── PageInfoModel.swift │ │ └── Slice.swift │ └── Weave.swift │ ├── Protocols │ ├── Argument.swift │ ├── Directive.swift │ ├── Removable.swift │ └── Weavable.swift │ └── Utils │ └── FieldFormatter.swift └── Tests ├── LinuxMain.swift └── SociableWeaverTests ├── Models ├── Author.swift ├── Comment.swift ├── PageInfo.swift └── Post.swift ├── SociableWeaverBuilderTests.swift ├── SociableWeaverGeneralTests.swift ├── SociableWeaverMutationTests.swift ├── SociableWeaverQueryTests.swift ├── SociableWeaverSingleComponentTests.swift └── XCTestManifests.swift /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | xcuserdata/ 5 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Bluecheese LLC 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.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.4 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: "SociableWeaver", 8 | products: [ 9 | .library( 10 | name: "SociableWeaver", 11 | targets: ["SociableWeaver"]), 12 | ], 13 | targets: [ 14 | .target( 15 | name: "SociableWeaver", 16 | dependencies: []), 17 | .testTarget( 18 | name: "SociableWeaverTests", 19 | dependencies: ["SociableWeaver"]), 20 | ] 21 | ) 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SociableWeaver 2 | 3 | Swift meets GraphQL in this lightweight, easy to use framework. SociableWeaver uses a declarative style of programming and makes GraphQL queries look natural in Swift code. Through the use of Swift 5.1 function builders and `CodingKeys`, SociableWeaver removes all of the need for unsafe strings and Dictionaries when creating objects and fields. 4 | 5 | ## Requirements 6 | Xcode 11.x or a Swift 5.1x toolchain with Swift Package Manager. 7 | 8 | ## Installation 9 | 10 | ### Swift Package Manager 11 | For projects using a `.xcodeproj` the best method is to navigate to `File > Swift Packages > Add Package Dependency...`. From there just simply enter `https://github.com/NicholasBellucci/SociableWeaver` as the package repository url and use the master branch or the most recent version. Master will always be inline with the newest release. The other method is to simply add `.package(url: "https://github.com/NicholasBellucci/SociableWeaver.git", from: "0.1.0")` to your `Package.swift` file's `dependencies`. 12 | 13 | ### Carthage 14 | 15 | Add the following entry to your `Cartfile` and run `$ carthage update SociableWeaver` 16 | 17 | ``` 18 | github "NicholasBellucci/SociableWeaver" 19 | ``` 20 | 21 | ## Table of Contents 22 | * [Objects and Fields](#objects-and-fields) 23 | * [Arguments](#arguments) 24 | * [Optionals](#optionals) 25 | * [Alias](#alias) 26 | * [Fragments](#fragments) 27 | * [Operation Name](#operation-name) 28 | * [Variables](#variables) 29 | * [Directives](#directives) 30 | * [Mutations](#mutations) 31 | * [Inline Fragments](#inline-fragments) 32 | * [Meta Fields](#meta-fields) 33 | * [Pagination](#pagination) 34 | * [Custom Types](#custom-types) 35 | * [ForEachWeavable](#foreachweavable) 36 | * [BuilderType](#buildertype) 37 | * [CaseStyleOption](#casestyleoption) 38 | * [EnumValueRepresentable](#enumvaluerepresentable) 39 | 40 | ## Usage 41 | 42 | SociableWeaver supports all that GraphQL has to offer. In order to get everything out of this framework, just make sure that any `Codable` models used contain `CodingKeys`. For example: 43 | 44 | ```swift 45 | public struct Post: Codable { 46 | public enum CodingKeys: String, CodingKey, CaseIterable { 47 | case id, title, content 48 | } 49 | 50 | public let id: String 51 | public let title: String 52 | public let content: String 53 | 54 | public init(id: String, title: String, content: String) { 55 | self.id = id 56 | self.title = title 57 | self.content = content 58 | } 59 | } 60 | ``` 61 | 62 | If `CodingKeys` aren't possible, SociableWeaver does support strings. It is highly recommended this be used as a last resort as it will make queries more difficult to manage. 63 | 64 | ### Objects and Fields 65 | [GraphQL Fields](https://graphql.org/learn/queries/#fields) 66 | 67 | GraphQL is all about querying specific fields on objects and returning only what is needed. With SociableWeaver constructing objects with fields is a breeze. 68 | 69 | ##### Swift 70 | ```swift 71 | Weave(.query) { 72 | Object(Post.self) { 73 | Field(Post.CodingKeys.id) 74 | Field(Post.CodingKeys.title) 75 | Field(Post.CodingKeys.content) 76 | } 77 | } 78 | ``` 79 | 80 | ##### GraphQL Query 81 | ```graphql 82 | query { 83 | post { 84 | id 85 | title 86 | content 87 | } 88 | } 89 | ``` 90 | 91 | ### Arguments 92 | 93 | [GraphQL Arguments](https://graphql.org/learn/queries/#arguments) 94 | 95 | Arguments are a key part of GraphQL and allow for much more refined queries. SociableWeaver supports arguments on both objects and fields. 96 | 97 | The only requirement is that the value for the argument conforms to `ArgumentValueRepresentable`. Core types such as `String`, `Int`, `Bool` etc. will already conform. Enumerations will need to conform to the [EnumValueRepresentable](https://github.com/NicholasBellucci/SociableWeaver#enumvaluerepresentable) protocol. 98 | 99 | ##### Swift 100 | ```swift 101 | Weave(.query) { 102 | Object(Post.self) { 103 | Field(Post.CodingKeys.title) 104 | 105 | Object(Post.CodingKeys.author) { 106 | Field(Author.CodingKeys.id) 107 | Field(Author.CodingKeys.name) 108 | .argument(key: "lastName", value: "Doe") 109 | } 110 | 111 | Object(Post.CodingKeys.comments) { 112 | Field(Comment.CodingKeys.id) 113 | Field(Comment.CodingKeys.content) 114 | } 115 | .argument(key: "filter", value: CommentFilter.recent) 116 | } 117 | } 118 | ``` 119 | 120 | ##### GraphQL Query 121 | ```graphql 122 | query { 123 | post { 124 | title 125 | author { 126 | id 127 | name(lastName: "Doe") 128 | } 129 | comments(filter: RECENT) { 130 | id 131 | content 132 | } 133 | } 134 | } 135 | ``` 136 | 137 | #### Optionals 138 | 139 | Optionals are supported and can be included in the query. In the instance where an optional should be included and the value is nil, the resulting GraphQL value will be `null`. 140 | 141 | In order to include an optional make sure to get the argument value of the property without including a `?`. This will result in a query param of `age: null`. 142 | ```swift 143 | public struct Author: Codable { 144 | public enum CodingKeys: String, CodingKey, CaseIterable { 145 | case id, name, age, birthplace 146 | } 147 | 148 | ... 149 | public let age: Int? 150 | ... 151 | } 152 | 153 | extension Author: ArgumentValueRepresentable { 154 | public var argumentValue: String { 155 | var params: [String: String?] = [:] 156 | 157 | ... 158 | params["age"] = age.argumentValue 159 | ... 160 | 161 | let paramStrings: [String] = params.compactMap { argument in 162 | guard let value = argument.value else { 163 | return nil 164 | } 165 | 166 | return "\(argument.key): \(value)" 167 | } 168 | 169 | return "{ \(paramStrings.joined(separator: ",")) }" 170 | } 171 | }' 172 | ``` 173 | 174 | ### Alias 175 | 176 | [GraphQL Alias](https://graphql.org/learn/queries/#aliases) 177 | 178 | Aliases are key when querying a single object multiple times in the same request. 179 | 180 | ##### Swift 181 | ```swift 182 | Weave(.query) { 183 | Object(Post.self) { 184 | Object(Post.CodingKeys.comments) { 185 | Field(Comment.CodingKeys.id) 186 | Field(Comment.CodingKeys.content) 187 | } 188 | .argument(key: "filter", value: CommentFilter.recent) 189 | .alias("newComments") 190 | 191 | Object(Post.CodingKeys.comments) { 192 | Field(Comment.CodingKeys.id) 193 | Field(Comment.CodingKeys.content) 194 | } 195 | .argument(key: "filter", value: CommentFilter.old) 196 | .alias("oldComments") 197 | } 198 | } 199 | ``` 200 | 201 | ##### GraphQL Query 202 | 203 | ```graphql 204 | query { 205 | post { 206 | newComments: comments(filter: RECENT) { 207 | id 208 | content 209 | } 210 | oldComments: comments(filter: OLD) { 211 | id 212 | content 213 | } 214 | } 215 | } 216 | ``` 217 | 218 | ### Fragments 219 | 220 | [GraphQL Fragments](https://graphql.org/learn/queries/#fragments) 221 | 222 | GraphQL fragments can help when building complicated queries. SociableWeaver makes them extremely simple and allows the proper references to be placed exactly where they would be in the query. With the help of a `FragmentBuilder` the `FragmentReference` can be added to the objects that require the fields and the `Fragment` can be added to the operation itself. 223 | 224 | ##### Swift 225 | ```swift 226 | let authorFragment = FragmentBuilder(name: "authorFields", type: Author.self) 227 | let query = Weave(.query) { 228 | Object(Post.self) { 229 | Object(Post.CodingKeys.author) { 230 | FragmentReference(for: authorFragment) 231 | } 232 | 233 | Object(Post.CodingKeys.comments) { 234 | Field(Comment.CodingKeys.content) 235 | 236 | Object(Comment.CodingKeys.author) { 237 | FragmentReference(for: authorFragment) 238 | } 239 | } 240 | } 241 | 242 | Fragment(authorFragment) { 243 | Field(Author.CodingKeys.id) 244 | Field(Author.CodingKeys.name) 245 | } 246 | } 247 | ``` 248 | 249 | ##### GraphQL Query 250 | ```graphql 251 | query { 252 | post { 253 | author { 254 | ...authorFields 255 | } 256 | comments { 257 | content 258 | author { 259 | ...authorFields 260 | } 261 | } 262 | } 263 | } 264 | 265 | fragment authorFields on Author { 266 | id 267 | name 268 | } 269 | ``` 270 | 271 | ### Operation Name 272 | 273 | [GraphQL Operation Name](https://graphql.org/learn/queries/#operation-name) 274 | 275 | Operation names aren't required but can make the queries more unique. 276 | 277 | ```swift 278 | Weave(.query) { 279 | Object(Post.self) { 280 | Field(Post.CodingKeys.id) 281 | Field(Post.CodingKeys.title) 282 | Field(Post.CodingKeys.content) 283 | } 284 | } 285 | .name("GetPostAndContent") 286 | ``` 287 | 288 | ##### GraphQL Query 289 | ```graphql 290 | query GetPost { 291 | post { 292 | id 293 | title 294 | content 295 | } 296 | } 297 | ``` 298 | 299 | ### Variables 300 | 301 | [GraphQL Variables](https://graphql.org/learn/queries/#variables) 302 | 303 | Since direct JSON is not needed when making queries in SociableWeaver, variables can and should be define in a method and passed into the query as arguments. 304 | 305 | ##### Swift 306 | ```swift 307 | queryPost(id: 1) 308 | 309 | func queryPost(id: Int) { 310 | Weave(.query) { 311 | Object(Post.self) { 312 | Field(Post.CodingKeys.title) 313 | Field(Post.CodingKeys.content) 314 | 315 | Object(Post.CodingKeys.author) { 316 | Field(Author.CodingKeys.id) 317 | Field(Author.CodingKeys.name) 318 | } 319 | } 320 | .argument(key: "id", value: id) 321 | } 322 | } 323 | ``` 324 | 325 | ##### GraphQL Query 326 | ```graphql 327 | query { 328 | post(id: 1) { 329 | title 330 | content 331 | author { 332 | id 333 | name 334 | } 335 | } 336 | } 337 | ``` 338 | 339 | ### Directives 340 | 341 | [GraphQL Directives](https://graphql.org/learn/queries/#directives) 342 | 343 | Directives in GraphQL allows the server to affect execution of the query. The two directives are `@include` and `@skip` both of which can be added to fields or included fragments. The example defines true or false but in an actual query these values would be boolean variables. 344 | 345 | Just to note, Skip will always take precedent over include. Also any objects/fragments that end up not having fields will be removed from the query. 346 | 347 | ```swift 348 | let query = Weave(.query) { 349 | Object(Post.self) { 350 | Field(Post.CodingKeys.title) 351 | Field(Post.CodingKeys.content) 352 | .include(if: true) 353 | 354 | Object(Post.CodingKeys.author) { 355 | Field(Author.CodingKeys.name) 356 | } 357 | .include(if: false) 358 | 359 | Object(Post.CodingKeys.comments) { 360 | Field(Comment.CodingKeys.content) 361 | .include(if: true) 362 | .skip(if: true) 363 | 364 | Object(Comment.CodingKeys.author) { 365 | Field(Author.CodingKeys.name) 366 | .skip(if: true) 367 | } 368 | } 369 | } 370 | } 371 | ``` 372 | 373 | ##### GraphQL Queries 374 | ```graphql 375 | query { 376 | post { 377 | title 378 | content 379 | } 380 | } 381 | ``` 382 | 383 | ### Mutations 384 | 385 | [GraphQL Mutations](https://graphql.org/learn/queries/#mutations) 386 | 387 | Mutations work the same as simple queries and should be used when data is supposed to be written. An `Object.schemaName` will replace the name of the Object or Key included in the initializer. 388 | 389 | ##### Swift 390 | ```swift 391 | Weave(.mutation) { 392 | Object(Post.self) { 393 | Field(Post.CodingKeys.id) 394 | Field(Post.CodingKeys.title) 395 | Field(Post.CodingKeys.content) 396 | } 397 | .schemaName("createPost") 398 | .argument(key: "title", value: "TestPost") 399 | .argument(key: "content", value: "This is a test post.") 400 | .argument(key: "author", value: "John Doe") 401 | } 402 | ``` 403 | 404 | ##### GraphQL Mutation 405 | ```graphql 406 | mutation { 407 | createPost(title: "TestPost", content: "This is a test post.", author: "John Doe") { 408 | id 409 | title 410 | content 411 | } 412 | } 413 | ``` 414 | 415 | ### Inline Fragments 416 | 417 | [GraphQL Inline Fragments](https://graphql.org/learn/queries/#inline-fragments) 418 | 419 | Inline fragments are useful when querying on an interface or union type as they allow the return of underlying types. 420 | 421 | ##### Swift 422 | ```swift 423 | Weave(.query) { 424 | Object(Post.self) { 425 | Field(Post.CodingKeys.title) 426 | Field(Post.CodingKeys.content) 427 | 428 | Object(Post.CodingKeys.comments) { 429 | Field(Comment.CodingKeys.content) 430 | 431 | Object(Comment.CodingKeys.author) { 432 | InlineFragment("AnonymousUser") { 433 | Field(Author.CodingKeys.id) 434 | } 435 | 436 | InlineFragment("RegisteredUser") { 437 | Field(Author.CodingKeys.id) 438 | Field(Author.CodingKeys.name) 439 | } 440 | } 441 | } 442 | } 443 | } 444 | ``` 445 | 446 | ##### GraphQL Query 447 | ```graphql 448 | query { 449 | post { 450 | title 451 | content 452 | comments { 453 | content 454 | author { 455 | ... on AnonymousUser { 456 | id 457 | } 458 | ... on RegisteredUser { 459 | id 460 | name 461 | } 462 | } 463 | } 464 | } 465 | } 466 | ``` 467 | 468 | ### Meta Fields 469 | 470 | [GraphQL Meta Fields](https://graphql.org/learn/queries/#meta-fields) 471 | 472 | GraphQL meta fields can be customized and are recognized to have two proceeding underscores. The `__typename` meta field is a GraphQL default and can be used to return the object type in the results of a query. 473 | 474 | Custom meta fields can be defined by using `MetaFieldType.custom`. This enum takes an associated String which does not need to include the double underscores before the name. For example: `.custom("schema")` results in `__schema`. 475 | 476 | ##### Swift 477 | ```swift 478 | Weave(.query) { 479 | Object(Post.self){ 480 | Field(Post.CodingKeys.title) 481 | Field(Post.CodingKeys.content) 482 | 483 | Object(Post.CodingKeys.author) { 484 | MetaField(.typename) 485 | Field(Author.CodingKeys.name) 486 | } 487 | } 488 | } 489 | ``` 490 | 491 | ##### GraphQL Query 492 | ```graphql 493 | query { 494 | post { 495 | title 496 | content 497 | author { 498 | __typename 499 | name 500 | } 501 | } 502 | } 503 | ``` 504 | 505 | ### Pagination 506 | 507 | [GraphQL Pagination](https://graphql.org/learn/pagination/) 508 | 509 | SociableWeaver support pagination out of the box and can be easily customized. Features supported include slicing, edges, and page info inclusion. 510 | 511 | #### Slicing 512 | 513 | Slicing in GraphQL is great for fetching a specified amount of objects in a response. With SociableWeaver this can be specified with the `Object.slice` method. 514 | 515 | ##### Swift 516 | ```swift 517 | Weave(.query) { 518 | Object(Post.CodingKeys.comments) { 519 | Field(Comment.CodingKeys.id) 520 | Field(Comment.CodingKeys.author) 521 | Field(Comment.CodingKeys.content) 522 | } 523 | .slice(amount: 2) 524 | } 525 | ``` 526 | 527 | ##### GraphQL Query 528 | ```graphql 529 | { 530 | comments(first: 2) { 531 | id 532 | author 533 | content 534 | } 535 | } 536 | ``` 537 | 538 | #### Cursor-Based Pagination 539 | 540 | Cursor-based pagination is described as being the most powerful pagination type GraphQL provides. Setup this pagination by declaring the pagination type for an object. 541 | 542 | ##### Swift 543 | ```swift 544 | Weave(.query) { 545 | Object(Post.CodingKeys.comments) { 546 | Field(Comment.CodingKeys.id) 547 | Field(Comment.CodingKeys.author) 548 | Field(Comment.CodingKeys.content) 549 | } 550 | .slice(amount: 2) 551 | .paginationType(.cursor) 552 | } 553 | ``` 554 | 555 | ##### GraphQL Query 556 | ```graphql 557 | { 558 | comments(first: 2) { 559 | edges { 560 | cursor 561 | node { 562 | id 563 | author 564 | content 565 | } 566 | } 567 | } 568 | } 569 | ``` 570 | 571 | #### Pagination Page Info 572 | 573 | Including page info such as whether or not there is a next page or the end cursor is very flexible and supports a custom model. 574 | 575 | ##### Swift 576 | ```swift 577 | Weave(.query) { 578 | Object(Post.CodingKeys.comments) { 579 | Field(Comment.CodingKeys.id) 580 | Field(Comment.CodingKeys.author) 581 | Field(Comment.CodingKeys.content) 582 | } 583 | .slice(amount: 2) 584 | .paginationType(.cursor) 585 | .pageInfo(type: PageInfo.self, 586 | keys: PageInfo.CodingKeys.startCursor, 587 | PageInfo.CodingKeys.endCursor, 588 | PageInfo.CodingKeys.hasNextPage) 589 | } 590 | ``` 591 | 592 | ##### GraphQL Query 593 | ```graphql 594 | { 595 | comments(first: 2) { 596 | edges { 597 | cursor 598 | node { 599 | id 600 | author 601 | content 602 | } 603 | } 604 | pageInfo { 605 | startCursor 606 | endCursor 607 | hasNextPage 608 | } 609 | } 610 | } 611 | ``` 612 | 613 | ### Custom Types 614 | 615 | SociableWeaver provides a couple of custom types that help to build more natural looking queries. These types may or may not have been included in examples but will also be defined in this section to provide more clarity. 616 | 617 | #### ForEachWeavable 618 | 619 | The ForEachWeavable struct was added for times where you may want to add objects or fields in a loop. More discussion around this use case can be found [here](https://github.com/NicholasBellucci/SociableWeaver/issues/42). 620 | 621 | ##### Swift 622 | ```swift 623 | let authors = [ 624 | Author(id: "1", name: "John", age: 17, birthplace: [:]), 625 | Author(id: "2", name: "Jane", age: 29, birthplace: [:]), 626 | Author(id: "3", name: "Adam", age: 41, birthplace: [:]) 627 | ] 628 | 629 | let query = Weave(.query) { 630 | ForEachWeavable(authors) { author in 631 | Object("postsForAuthor") { 632 | Field(Author.CodingKeys.id) 633 | Field(Author.CodingKeys.name) 634 | Field(Author.CodingKeys.age) 635 | Field(Author.CodingKeys.birthplace) 636 | } 637 | .argument(key: "id", value: author.id) 638 | } 639 | } 640 | ``` 641 | 642 | ##### GraphQL Query 643 | ```graphql 644 | { 645 | postsForAuthor(id: "1") { 646 | id 647 | name 648 | age 649 | birthplace 650 | } 651 | postsForAuthor(id: "2") { 652 | id 653 | name 654 | age 655 | birthplace 656 | } 657 | postsForAuthor(id: "3") { 658 | id 659 | name 660 | age 661 | birthplace 662 | } 663 | } 664 | ``` 665 | 666 | #### BuilderType 667 | 668 | Due to current limitations with function builders, individual elements are not currently accepted. For that reason each function builder initializer has a corresponding initializer for a single element. `BuilderType.individual` has been set up to specify when an object or fragment will consist of only one element. The default value for the `builderType` parameter on all initializations is `.individual`. This means that passing it is not required and will result in the same outcome. 669 | 670 | ```swift 671 | Object(Post.CodingKeys.author) { 672 | Field(Author.CodingKeys.name) 673 | } 674 | 675 | Fragment(authorFragment, .individual) { 676 | Field(Author.CodingKeys.name) 677 | } 678 | ``` 679 | 680 | #### CaseStyleOption 681 | 682 | This enumeration has been provided to allow for customization when it comes to object and fields that are initialized with a model or coding key. Defaulted to camel case. 683 | 684 | ```swift 685 | Field(Comment.CodingKeys.createdAt) 686 | .caseStyle(.lowercase) 687 | 688 | public enum CaseStyleOption { 689 | case lowercase 690 | case uppercase 691 | case capitalized 692 | case camelCase 693 | case pascalCase 694 | case snakeCase 695 | case kebabCase 696 | } 697 | ``` 698 | 699 | #### EnumValueRepresentable 700 | 701 | GraphQL enumeration values are represented as uppercase representations of the case names. For this reason, custom enumerations in swift that should be passed as argument values can conform to `EnumValueRepresentable`. This protocol conforms to `ArgumentValueRepresentable` and is extended to provide the `argumentValue` as an uppercase version of the case value. 702 | 703 | ```swift 704 | enum PostCategories: EnumValueRepresentable { 705 | case art 706 | case music 707 | case technology 708 | } 709 | 710 | 711 | Object(Post.self) { 712 | ... 713 | } 714 | .argument(key: "category", value: PostCategories.technology) 715 | 716 | /// Result: post(category: TECHNOLOGY) { ... } 717 | ``` 718 | 719 | ## License 720 | 721 | SociableWeaver is, and always will be, MIT licensed. See [LICENSE](LICENSE) for details. 722 | -------------------------------------------------------------------------------- /SociableWeaver.xcodeproj/SociableWeaverTests_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | BNDL 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /SociableWeaver.xcodeproj/SociableWeaver_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | FMWK 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /SociableWeaver.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXAggregateTarget section */ 10 | "SociableWeaver::SociableWeaverPackageTests::ProductTarget" /* SociableWeaverPackageTests */ = { 11 | isa = PBXAggregateTarget; 12 | buildConfigurationList = OBJ_82 /* Build configuration list for PBXAggregateTarget "SociableWeaverPackageTests" */; 13 | buildPhases = ( 14 | ); 15 | dependencies = ( 16 | OBJ_85 /* PBXTargetDependency */, 17 | ); 18 | name = SociableWeaverPackageTests; 19 | productName = SociableWeaverPackageTests; 20 | }; 21 | /* End PBXAggregateTarget section */ 22 | 23 | /* Begin PBXBuildFile section */ 24 | 4C88F4FF24DF5D20007CD2E0 /* Slice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C88F4FE24DF5D20007CD2E0 /* Slice.swift */; }; 25 | 4C88F50224DF5F75007CD2E0 /* SociableWeaverBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C88F50024DF5F70007CD2E0 /* SociableWeaverBuilderTests.swift */; }; 26 | 4C88F50524DF988F007CD2E0 /* PageInfoModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C88F50424DF988F007CD2E0 /* PageInfoModel.swift */; }; 27 | 4C88F50724DF9B00007CD2E0 /* PageInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C88F50624DF9B00007CD2E0 /* PageInfo.swift */; }; 28 | OBJ_55 /* CaseStyleOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_10 /* CaseStyleOption.swift */; }; 29 | OBJ_56 /* OperationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_11 /* OperationType.swift */; }; 30 | OBJ_57 /* Array+Argument.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_13 /* Array+Argument.swift */; }; 31 | OBJ_58 /* String+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_14 /* String+Utils.swift */; }; 32 | OBJ_59 /* ObjectBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_16 /* ObjectBuilder.swift */; }; 33 | OBJ_60 /* OperationBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_17 /* OperationBuilder.swift */; }; 34 | OBJ_61 /* Field.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_19 /* Field.swift */; }; 35 | OBJ_62 /* Fragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_21 /* Fragment.swift */; }; 36 | OBJ_63 /* FragmentBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_22 /* FragmentBuilder.swift */; }; 37 | OBJ_64 /* FragmentReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_23 /* FragmentReference.swift */; }; 38 | OBJ_65 /* InlineFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_24 /* InlineFragment.swift */; }; 39 | OBJ_66 /* MetaField.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_25 /* MetaField.swift */; }; 40 | OBJ_67 /* Object.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_26 /* Object.swift */; }; 41 | OBJ_68 /* Weave.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_27 /* Weave.swift */; }; 42 | OBJ_69 /* Argument.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_29 /* Argument.swift */; }; 43 | OBJ_70 /* Directive.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_30 /* Directive.swift */; }; 44 | OBJ_71 /* Removable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_31 /* Removable.swift */; }; 45 | OBJ_72 /* Weavable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_32 /* Weavable.swift */; }; 46 | OBJ_73 /* FieldFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_34 /* FieldFormatter.swift */; }; 47 | OBJ_80 /* Package.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_6 /* Package.swift */; }; 48 | OBJ_91 /* Author.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_38 /* Author.swift */; }; 49 | OBJ_92 /* Comment.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_39 /* Comment.swift */; }; 50 | OBJ_93 /* Post.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_40 /* Post.swift */; }; 51 | OBJ_94 /* SociableWeaverGeneralTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_41 /* SociableWeaverGeneralTests.swift */; }; 52 | OBJ_95 /* SociableWeaverMutationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_42 /* SociableWeaverMutationTests.swift */; }; 53 | OBJ_96 /* SociableWeaverQueryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_43 /* SociableWeaverQueryTests.swift */; }; 54 | OBJ_97 /* XCTestManifests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_44 /* XCTestManifests.swift */; }; 55 | OBJ_99 /* SociableWeaver.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = "SociableWeaver::SociableWeaver::Product" /* SociableWeaver.framework */; }; 56 | /* End PBXBuildFile section */ 57 | 58 | /* Begin PBXContainerItemProxy section */ 59 | 68BFFC3A2412A95C00BCB953 /* PBXContainerItemProxy */ = { 60 | isa = PBXContainerItemProxy; 61 | containerPortal = OBJ_1 /* Project object */; 62 | proxyType = 1; 63 | remoteGlobalIDString = "SociableWeaver::SociableWeaver"; 64 | remoteInfo = SociableWeaver; 65 | }; 66 | 68BFFC3B2412A95D00BCB953 /* PBXContainerItemProxy */ = { 67 | isa = PBXContainerItemProxy; 68 | containerPortal = OBJ_1 /* Project object */; 69 | proxyType = 1; 70 | remoteGlobalIDString = "SociableWeaver::SociableWeaverTests"; 71 | remoteInfo = SociableWeaverTests; 72 | }; 73 | /* End PBXContainerItemProxy section */ 74 | 75 | /* Begin PBXFileReference section */ 76 | 4C88F4FE24DF5D20007CD2E0 /* Slice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Slice.swift; sourceTree = ""; }; 77 | 4C88F50024DF5F70007CD2E0 /* SociableWeaverBuilderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SociableWeaverBuilderTests.swift; sourceTree = ""; }; 78 | 4C88F50424DF988F007CD2E0 /* PageInfoModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageInfoModel.swift; sourceTree = ""; }; 79 | 4C88F50624DF9B00007CD2E0 /* PageInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageInfo.swift; sourceTree = ""; }; 80 | OBJ_10 /* CaseStyleOption.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CaseStyleOption.swift; sourceTree = ""; }; 81 | OBJ_11 /* OperationType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperationType.swift; sourceTree = ""; }; 82 | OBJ_13 /* Array+Argument.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+Argument.swift"; sourceTree = ""; }; 83 | OBJ_14 /* String+Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Utils.swift"; sourceTree = ""; }; 84 | OBJ_16 /* ObjectBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectBuilder.swift; sourceTree = ""; }; 85 | OBJ_17 /* OperationBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperationBuilder.swift; sourceTree = ""; }; 86 | OBJ_19 /* Field.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Field.swift; sourceTree = ""; }; 87 | OBJ_21 /* Fragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Fragment.swift; sourceTree = ""; }; 88 | OBJ_22 /* FragmentBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FragmentBuilder.swift; sourceTree = ""; }; 89 | OBJ_23 /* FragmentReference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FragmentReference.swift; sourceTree = ""; }; 90 | OBJ_24 /* InlineFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InlineFragment.swift; sourceTree = ""; }; 91 | OBJ_25 /* MetaField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetaField.swift; sourceTree = ""; }; 92 | OBJ_26 /* Object.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Object.swift; sourceTree = ""; }; 93 | OBJ_27 /* Weave.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Weave.swift; sourceTree = ""; }; 94 | OBJ_29 /* Argument.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Argument.swift; sourceTree = ""; }; 95 | OBJ_30 /* Directive.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Directive.swift; sourceTree = ""; }; 96 | OBJ_31 /* Removable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Removable.swift; sourceTree = ""; }; 97 | OBJ_32 /* Weavable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Weavable.swift; sourceTree = ""; }; 98 | OBJ_34 /* FieldFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FieldFormatter.swift; sourceTree = ""; }; 99 | OBJ_38 /* Author.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Author.swift; sourceTree = ""; }; 100 | OBJ_39 /* Comment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Comment.swift; sourceTree = ""; }; 101 | OBJ_40 /* Post.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Post.swift; sourceTree = ""; }; 102 | OBJ_41 /* SociableWeaverGeneralTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SociableWeaverGeneralTests.swift; sourceTree = ""; }; 103 | OBJ_42 /* SociableWeaverMutationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SociableWeaverMutationTests.swift; sourceTree = ""; }; 104 | OBJ_43 /* SociableWeaverQueryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SociableWeaverQueryTests.swift; sourceTree = ""; }; 105 | OBJ_44 /* XCTestManifests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCTestManifests.swift; sourceTree = ""; }; 106 | OBJ_48 /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; 107 | OBJ_49 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 108 | OBJ_6 /* Package.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; path = Package.swift; sourceTree = ""; }; 109 | "SociableWeaver::SociableWeaver::Product" /* SociableWeaver.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = SociableWeaver.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 110 | "SociableWeaver::SociableWeaverTests::Product" /* SociableWeaverTests.xctest */ = {isa = PBXFileReference; lastKnownFileType = file; path = SociableWeaverTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 111 | /* End PBXFileReference section */ 112 | 113 | /* Begin PBXFrameworksBuildPhase section */ 114 | OBJ_74 /* Frameworks */ = { 115 | isa = PBXFrameworksBuildPhase; 116 | buildActionMask = 0; 117 | files = ( 118 | ); 119 | runOnlyForDeploymentPostprocessing = 0; 120 | }; 121 | OBJ_98 /* Frameworks */ = { 122 | isa = PBXFrameworksBuildPhase; 123 | buildActionMask = 0; 124 | files = ( 125 | OBJ_99 /* SociableWeaver.framework in Frameworks */, 126 | ); 127 | runOnlyForDeploymentPostprocessing = 0; 128 | }; 129 | /* End PBXFrameworksBuildPhase section */ 130 | 131 | /* Begin PBXGroup section */ 132 | 4C88F4FD24DF5D17007CD2E0 /* Object */ = { 133 | isa = PBXGroup; 134 | children = ( 135 | OBJ_26 /* Object.swift */, 136 | 4C88F4FE24DF5D20007CD2E0 /* Slice.swift */, 137 | 4C88F50424DF988F007CD2E0 /* PageInfoModel.swift */, 138 | ); 139 | path = Object; 140 | sourceTree = ""; 141 | }; 142 | OBJ_12 /* Extensions */ = { 143 | isa = PBXGroup; 144 | children = ( 145 | OBJ_13 /* Array+Argument.swift */, 146 | OBJ_14 /* String+Utils.swift */, 147 | ); 148 | path = Extensions; 149 | sourceTree = ""; 150 | }; 151 | OBJ_15 /* FunctionBuilders */ = { 152 | isa = PBXGroup; 153 | children = ( 154 | OBJ_16 /* ObjectBuilder.swift */, 155 | OBJ_17 /* OperationBuilder.swift */, 156 | ); 157 | path = FunctionBuilders; 158 | sourceTree = ""; 159 | }; 160 | OBJ_18 /* Helpers */ = { 161 | isa = PBXGroup; 162 | children = ( 163 | OBJ_20 /* Fragment */, 164 | 4C88F4FD24DF5D17007CD2E0 /* Object */, 165 | OBJ_19 /* Field.swift */, 166 | OBJ_25 /* MetaField.swift */, 167 | OBJ_27 /* Weave.swift */, 168 | ); 169 | path = Helpers; 170 | sourceTree = ""; 171 | }; 172 | OBJ_20 /* Fragment */ = { 173 | isa = PBXGroup; 174 | children = ( 175 | OBJ_21 /* Fragment.swift */, 176 | OBJ_22 /* FragmentBuilder.swift */, 177 | OBJ_23 /* FragmentReference.swift */, 178 | OBJ_24 /* InlineFragment.swift */, 179 | ); 180 | path = Fragment; 181 | sourceTree = ""; 182 | }; 183 | OBJ_28 /* Protocols */ = { 184 | isa = PBXGroup; 185 | children = ( 186 | OBJ_29 /* Argument.swift */, 187 | OBJ_30 /* Directive.swift */, 188 | OBJ_31 /* Removable.swift */, 189 | OBJ_32 /* Weavable.swift */, 190 | ); 191 | path = Protocols; 192 | sourceTree = ""; 193 | }; 194 | OBJ_33 /* Utils */ = { 195 | isa = PBXGroup; 196 | children = ( 197 | OBJ_34 /* FieldFormatter.swift */, 198 | ); 199 | path = Utils; 200 | sourceTree = ""; 201 | }; 202 | OBJ_35 /* Tests */ = { 203 | isa = PBXGroup; 204 | children = ( 205 | OBJ_36 /* SociableWeaverTests */, 206 | ); 207 | name = Tests; 208 | sourceTree = SOURCE_ROOT; 209 | }; 210 | OBJ_36 /* SociableWeaverTests */ = { 211 | isa = PBXGroup; 212 | children = ( 213 | OBJ_37 /* Models */, 214 | 4C88F50024DF5F70007CD2E0 /* SociableWeaverBuilderTests.swift */, 215 | OBJ_41 /* SociableWeaverGeneralTests.swift */, 216 | OBJ_42 /* SociableWeaverMutationTests.swift */, 217 | OBJ_43 /* SociableWeaverQueryTests.swift */, 218 | OBJ_44 /* XCTestManifests.swift */, 219 | ); 220 | name = SociableWeaverTests; 221 | path = Tests/SociableWeaverTests; 222 | sourceTree = SOURCE_ROOT; 223 | }; 224 | OBJ_37 /* Models */ = { 225 | isa = PBXGroup; 226 | children = ( 227 | OBJ_38 /* Author.swift */, 228 | OBJ_39 /* Comment.swift */, 229 | OBJ_40 /* Post.swift */, 230 | 4C88F50624DF9B00007CD2E0 /* PageInfo.swift */, 231 | ); 232 | path = Models; 233 | sourceTree = ""; 234 | }; 235 | OBJ_45 /* Products */ = { 236 | isa = PBXGroup; 237 | children = ( 238 | "SociableWeaver::SociableWeaverTests::Product" /* SociableWeaverTests.xctest */, 239 | "SociableWeaver::SociableWeaver::Product" /* SociableWeaver.framework */, 240 | ); 241 | name = Products; 242 | sourceTree = BUILT_PRODUCTS_DIR; 243 | }; 244 | OBJ_5 = { 245 | isa = PBXGroup; 246 | children = ( 247 | OBJ_6 /* Package.swift */, 248 | OBJ_7 /* Sources */, 249 | OBJ_35 /* Tests */, 250 | OBJ_45 /* Products */, 251 | OBJ_48 /* LICENSE */, 252 | OBJ_49 /* README.md */, 253 | ); 254 | sourceTree = ""; 255 | }; 256 | OBJ_7 /* Sources */ = { 257 | isa = PBXGroup; 258 | children = ( 259 | OBJ_8 /* SociableWeaver */, 260 | ); 261 | name = Sources; 262 | sourceTree = SOURCE_ROOT; 263 | }; 264 | OBJ_8 /* SociableWeaver */ = { 265 | isa = PBXGroup; 266 | children = ( 267 | OBJ_9 /* Enumerations */, 268 | OBJ_12 /* Extensions */, 269 | OBJ_15 /* FunctionBuilders */, 270 | OBJ_18 /* Helpers */, 271 | OBJ_28 /* Protocols */, 272 | OBJ_33 /* Utils */, 273 | ); 274 | name = SociableWeaver; 275 | path = Sources/SociableWeaver; 276 | sourceTree = SOURCE_ROOT; 277 | }; 278 | OBJ_9 /* Enumerations */ = { 279 | isa = PBXGroup; 280 | children = ( 281 | OBJ_10 /* CaseStyleOption.swift */, 282 | OBJ_11 /* OperationType.swift */, 283 | ); 284 | path = Enumerations; 285 | sourceTree = ""; 286 | }; 287 | /* End PBXGroup section */ 288 | 289 | /* Begin PBXNativeTarget section */ 290 | "SociableWeaver::SociableWeaver" /* SociableWeaver */ = { 291 | isa = PBXNativeTarget; 292 | buildConfigurationList = OBJ_51 /* Build configuration list for PBXNativeTarget "SociableWeaver" */; 293 | buildPhases = ( 294 | OBJ_54 /* Sources */, 295 | OBJ_74 /* Frameworks */, 296 | ); 297 | buildRules = ( 298 | ); 299 | dependencies = ( 300 | ); 301 | name = SociableWeaver; 302 | productName = SociableWeaver; 303 | productReference = "SociableWeaver::SociableWeaver::Product" /* SociableWeaver.framework */; 304 | productType = "com.apple.product-type.framework"; 305 | }; 306 | "SociableWeaver::SociableWeaverTests" /* SociableWeaverTests */ = { 307 | isa = PBXNativeTarget; 308 | buildConfigurationList = OBJ_87 /* Build configuration list for PBXNativeTarget "SociableWeaverTests" */; 309 | buildPhases = ( 310 | OBJ_90 /* Sources */, 311 | OBJ_98 /* Frameworks */, 312 | ); 313 | buildRules = ( 314 | ); 315 | dependencies = ( 316 | OBJ_100 /* PBXTargetDependency */, 317 | ); 318 | name = SociableWeaverTests; 319 | productName = SociableWeaverTests; 320 | productReference = "SociableWeaver::SociableWeaverTests::Product" /* SociableWeaverTests.xctest */; 321 | productType = "com.apple.product-type.bundle.unit-test"; 322 | }; 323 | "SociableWeaver::SwiftPMPackageDescription" /* SociableWeaverPackageDescription */ = { 324 | isa = PBXNativeTarget; 325 | buildConfigurationList = OBJ_76 /* Build configuration list for PBXNativeTarget "SociableWeaverPackageDescription" */; 326 | buildPhases = ( 327 | OBJ_79 /* Sources */, 328 | ); 329 | buildRules = ( 330 | ); 331 | dependencies = ( 332 | ); 333 | name = SociableWeaverPackageDescription; 334 | productName = SociableWeaverPackageDescription; 335 | productType = "com.apple.product-type.framework"; 336 | }; 337 | /* End PBXNativeTarget section */ 338 | 339 | /* Begin PBXProject section */ 340 | OBJ_1 /* Project object */ = { 341 | isa = PBXProject; 342 | attributes = { 343 | LastSwiftMigration = 9999; 344 | LastUpgradeCheck = 9999; 345 | }; 346 | buildConfigurationList = OBJ_2 /* Build configuration list for PBXProject "SociableWeaver" */; 347 | compatibilityVersion = "Xcode 3.2"; 348 | developmentRegion = en; 349 | hasScannedForEncodings = 0; 350 | knownRegions = ( 351 | en, 352 | ); 353 | mainGroup = OBJ_5; 354 | productRefGroup = OBJ_45 /* Products */; 355 | projectDirPath = ""; 356 | projectRoot = ""; 357 | targets = ( 358 | "SociableWeaver::SociableWeaver" /* SociableWeaver */, 359 | "SociableWeaver::SwiftPMPackageDescription" /* SociableWeaverPackageDescription */, 360 | "SociableWeaver::SociableWeaverPackageTests::ProductTarget" /* SociableWeaverPackageTests */, 361 | "SociableWeaver::SociableWeaverTests" /* SociableWeaverTests */, 362 | ); 363 | }; 364 | /* End PBXProject section */ 365 | 366 | /* Begin PBXSourcesBuildPhase section */ 367 | OBJ_54 /* Sources */ = { 368 | isa = PBXSourcesBuildPhase; 369 | buildActionMask = 0; 370 | files = ( 371 | 4C88F50724DF9B00007CD2E0 /* PageInfo.swift in Sources */, 372 | OBJ_55 /* CaseStyleOption.swift in Sources */, 373 | OBJ_56 /* OperationType.swift in Sources */, 374 | OBJ_57 /* Array+Argument.swift in Sources */, 375 | OBJ_58 /* String+Utils.swift in Sources */, 376 | OBJ_59 /* ObjectBuilder.swift in Sources */, 377 | OBJ_60 /* OperationBuilder.swift in Sources */, 378 | OBJ_61 /* Field.swift in Sources */, 379 | OBJ_62 /* Fragment.swift in Sources */, 380 | OBJ_63 /* FragmentBuilder.swift in Sources */, 381 | OBJ_64 /* FragmentReference.swift in Sources */, 382 | 4C88F4FF24DF5D20007CD2E0 /* Slice.swift in Sources */, 383 | OBJ_65 /* InlineFragment.swift in Sources */, 384 | OBJ_66 /* MetaField.swift in Sources */, 385 | OBJ_67 /* Object.swift in Sources */, 386 | 4C88F50524DF988F007CD2E0 /* PageInfoModel.swift in Sources */, 387 | OBJ_68 /* Weave.swift in Sources */, 388 | OBJ_69 /* Argument.swift in Sources */, 389 | OBJ_70 /* Directive.swift in Sources */, 390 | OBJ_71 /* Removable.swift in Sources */, 391 | OBJ_72 /* Weavable.swift in Sources */, 392 | OBJ_73 /* FieldFormatter.swift in Sources */, 393 | ); 394 | runOnlyForDeploymentPostprocessing = 0; 395 | }; 396 | OBJ_79 /* Sources */ = { 397 | isa = PBXSourcesBuildPhase; 398 | buildActionMask = 0; 399 | files = ( 400 | OBJ_80 /* Package.swift in Sources */, 401 | ); 402 | runOnlyForDeploymentPostprocessing = 0; 403 | }; 404 | OBJ_90 /* Sources */ = { 405 | isa = PBXSourcesBuildPhase; 406 | buildActionMask = 0; 407 | files = ( 408 | OBJ_91 /* Author.swift in Sources */, 409 | OBJ_92 /* Comment.swift in Sources */, 410 | 4C88F50224DF5F75007CD2E0 /* SociableWeaverBuilderTests.swift in Sources */, 411 | OBJ_93 /* Post.swift in Sources */, 412 | OBJ_94 /* SociableWeaverGeneralTests.swift in Sources */, 413 | OBJ_95 /* SociableWeaverMutationTests.swift in Sources */, 414 | OBJ_96 /* SociableWeaverQueryTests.swift in Sources */, 415 | OBJ_97 /* XCTestManifests.swift in Sources */, 416 | ); 417 | runOnlyForDeploymentPostprocessing = 0; 418 | }; 419 | /* End PBXSourcesBuildPhase section */ 420 | 421 | /* Begin PBXTargetDependency section */ 422 | OBJ_100 /* PBXTargetDependency */ = { 423 | isa = PBXTargetDependency; 424 | target = "SociableWeaver::SociableWeaver" /* SociableWeaver */; 425 | targetProxy = 68BFFC3A2412A95C00BCB953 /* PBXContainerItemProxy */; 426 | }; 427 | OBJ_85 /* PBXTargetDependency */ = { 428 | isa = PBXTargetDependency; 429 | target = "SociableWeaver::SociableWeaverTests" /* SociableWeaverTests */; 430 | targetProxy = 68BFFC3B2412A95D00BCB953 /* PBXContainerItemProxy */; 431 | }; 432 | /* End PBXTargetDependency section */ 433 | 434 | /* Begin XCBuildConfiguration section */ 435 | OBJ_3 /* Debug */ = { 436 | isa = XCBuildConfiguration; 437 | buildSettings = { 438 | CLANG_ENABLE_OBJC_ARC = YES; 439 | COMBINE_HIDPI_IMAGES = YES; 440 | COPY_PHASE_STRIP = NO; 441 | CURRENT_PROJECT_VERSION = 1; 442 | DEBUG_INFORMATION_FORMAT = dwarf; 443 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 444 | ENABLE_NS_ASSERTIONS = YES; 445 | GCC_OPTIMIZATION_LEVEL = 0; 446 | GCC_PREPROCESSOR_DEFINITIONS = ( 447 | "$(inherited)", 448 | "SWIFT_PACKAGE=1", 449 | "DEBUG=1", 450 | ); 451 | MACOSX_DEPLOYMENT_TARGET = 10.10; 452 | ONLY_ACTIVE_ARCH = YES; 453 | OTHER_SWIFT_FLAGS = "$(inherited) -DXcode"; 454 | PRODUCT_NAME = "$(TARGET_NAME)"; 455 | SDKROOT = macosx; 456 | SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator"; 457 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) SWIFT_PACKAGE DEBUG"; 458 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 459 | USE_HEADERMAP = NO; 460 | }; 461 | name = Debug; 462 | }; 463 | OBJ_4 /* Release */ = { 464 | isa = XCBuildConfiguration; 465 | buildSettings = { 466 | CLANG_ENABLE_OBJC_ARC = YES; 467 | COMBINE_HIDPI_IMAGES = YES; 468 | COPY_PHASE_STRIP = YES; 469 | CURRENT_PROJECT_VERSION = 1; 470 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 471 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 472 | GCC_OPTIMIZATION_LEVEL = s; 473 | GCC_PREPROCESSOR_DEFINITIONS = ( 474 | "$(inherited)", 475 | "SWIFT_PACKAGE=1", 476 | ); 477 | MACOSX_DEPLOYMENT_TARGET = 10.10; 478 | OTHER_SWIFT_FLAGS = "$(inherited) -DXcode"; 479 | PRODUCT_NAME = "$(TARGET_NAME)"; 480 | SDKROOT = macosx; 481 | SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator"; 482 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) SWIFT_PACKAGE"; 483 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 484 | USE_HEADERMAP = NO; 485 | }; 486 | name = Release; 487 | }; 488 | OBJ_52 /* Debug */ = { 489 | isa = XCBuildConfiguration; 490 | buildSettings = { 491 | ENABLE_TESTABILITY = YES; 492 | FRAMEWORK_SEARCH_PATHS = ( 493 | "$(inherited)", 494 | "$(PLATFORM_DIR)/Developer/Library/Frameworks", 495 | ); 496 | HEADER_SEARCH_PATHS = "$(inherited)"; 497 | INFOPLIST_FILE = SociableWeaver.xcodeproj/SociableWeaver_Info.plist; 498 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 499 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) $(TOOLCHAIN_DIR)/usr/lib/swift/macosx"; 500 | MACOSX_DEPLOYMENT_TARGET = 10.10; 501 | OTHER_CFLAGS = "$(inherited)"; 502 | OTHER_LDFLAGS = "$(inherited)"; 503 | OTHER_SWIFT_FLAGS = "$(inherited)"; 504 | PRODUCT_BUNDLE_IDENTIFIER = SociableWeaver; 505 | PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; 506 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 507 | SKIP_INSTALL = YES; 508 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)"; 509 | SWIFT_VERSION = 5.0; 510 | TARGET_NAME = SociableWeaver; 511 | TVOS_DEPLOYMENT_TARGET = 9.0; 512 | WATCHOS_DEPLOYMENT_TARGET = 2.0; 513 | }; 514 | name = Debug; 515 | }; 516 | OBJ_53 /* Release */ = { 517 | isa = XCBuildConfiguration; 518 | buildSettings = { 519 | ENABLE_TESTABILITY = YES; 520 | FRAMEWORK_SEARCH_PATHS = ( 521 | "$(inherited)", 522 | "$(PLATFORM_DIR)/Developer/Library/Frameworks", 523 | ); 524 | HEADER_SEARCH_PATHS = "$(inherited)"; 525 | INFOPLIST_FILE = SociableWeaver.xcodeproj/SociableWeaver_Info.plist; 526 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 527 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) $(TOOLCHAIN_DIR)/usr/lib/swift/macosx"; 528 | MACOSX_DEPLOYMENT_TARGET = 10.10; 529 | OTHER_CFLAGS = "$(inherited)"; 530 | OTHER_LDFLAGS = "$(inherited)"; 531 | OTHER_SWIFT_FLAGS = "$(inherited)"; 532 | PRODUCT_BUNDLE_IDENTIFIER = SociableWeaver; 533 | PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; 534 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 535 | SKIP_INSTALL = YES; 536 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)"; 537 | SWIFT_VERSION = 5.0; 538 | TARGET_NAME = SociableWeaver; 539 | TVOS_DEPLOYMENT_TARGET = 9.0; 540 | WATCHOS_DEPLOYMENT_TARGET = 2.0; 541 | }; 542 | name = Release; 543 | }; 544 | OBJ_77 /* Debug */ = { 545 | isa = XCBuildConfiguration; 546 | buildSettings = { 547 | LD = /usr/bin/true; 548 | OTHER_SWIFT_FLAGS = "-swift-version 5 -I $(TOOLCHAIN_DIR)/usr/lib/swift/pm/4_2 -target x86_64-apple-macosx10.10 -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk -package-description-version 5.1"; 549 | SWIFT_VERSION = 5.0; 550 | }; 551 | name = Debug; 552 | }; 553 | OBJ_78 /* Release */ = { 554 | isa = XCBuildConfiguration; 555 | buildSettings = { 556 | LD = /usr/bin/true; 557 | OTHER_SWIFT_FLAGS = "-swift-version 5 -I $(TOOLCHAIN_DIR)/usr/lib/swift/pm/4_2 -target x86_64-apple-macosx10.10 -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk -package-description-version 5.1"; 558 | SWIFT_VERSION = 5.0; 559 | }; 560 | name = Release; 561 | }; 562 | OBJ_83 /* Debug */ = { 563 | isa = XCBuildConfiguration; 564 | buildSettings = { 565 | }; 566 | name = Debug; 567 | }; 568 | OBJ_84 /* Release */ = { 569 | isa = XCBuildConfiguration; 570 | buildSettings = { 571 | }; 572 | name = Release; 573 | }; 574 | OBJ_88 /* Debug */ = { 575 | isa = XCBuildConfiguration; 576 | buildSettings = { 577 | CLANG_ENABLE_MODULES = YES; 578 | EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; 579 | FRAMEWORK_SEARCH_PATHS = ( 580 | "$(inherited)", 581 | "$(PLATFORM_DIR)/Developer/Library/Frameworks", 582 | ); 583 | HEADER_SEARCH_PATHS = "$(inherited)"; 584 | INFOPLIST_FILE = SociableWeaver.xcodeproj/SociableWeaverTests_Info.plist; 585 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 586 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @loader_path/../Frameworks @loader_path/Frameworks"; 587 | MACOSX_DEPLOYMENT_TARGET = 10.10; 588 | OTHER_CFLAGS = "$(inherited)"; 589 | OTHER_LDFLAGS = "$(inherited)"; 590 | OTHER_SWIFT_FLAGS = "$(inherited)"; 591 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)"; 592 | SWIFT_VERSION = 5.0; 593 | TARGET_NAME = SociableWeaverTests; 594 | TVOS_DEPLOYMENT_TARGET = 9.0; 595 | WATCHOS_DEPLOYMENT_TARGET = 2.0; 596 | }; 597 | name = Debug; 598 | }; 599 | OBJ_89 /* Release */ = { 600 | isa = XCBuildConfiguration; 601 | buildSettings = { 602 | CLANG_ENABLE_MODULES = YES; 603 | EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; 604 | FRAMEWORK_SEARCH_PATHS = ( 605 | "$(inherited)", 606 | "$(PLATFORM_DIR)/Developer/Library/Frameworks", 607 | ); 608 | HEADER_SEARCH_PATHS = "$(inherited)"; 609 | INFOPLIST_FILE = SociableWeaver.xcodeproj/SociableWeaverTests_Info.plist; 610 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 611 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @loader_path/../Frameworks @loader_path/Frameworks"; 612 | MACOSX_DEPLOYMENT_TARGET = 10.10; 613 | OTHER_CFLAGS = "$(inherited)"; 614 | OTHER_LDFLAGS = "$(inherited)"; 615 | OTHER_SWIFT_FLAGS = "$(inherited)"; 616 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)"; 617 | SWIFT_VERSION = 5.0; 618 | TARGET_NAME = SociableWeaverTests; 619 | TVOS_DEPLOYMENT_TARGET = 9.0; 620 | WATCHOS_DEPLOYMENT_TARGET = 2.0; 621 | }; 622 | name = Release; 623 | }; 624 | /* End XCBuildConfiguration section */ 625 | 626 | /* Begin XCConfigurationList section */ 627 | OBJ_2 /* Build configuration list for PBXProject "SociableWeaver" */ = { 628 | isa = XCConfigurationList; 629 | buildConfigurations = ( 630 | OBJ_3 /* Debug */, 631 | OBJ_4 /* Release */, 632 | ); 633 | defaultConfigurationIsVisible = 0; 634 | defaultConfigurationName = Release; 635 | }; 636 | OBJ_51 /* Build configuration list for PBXNativeTarget "SociableWeaver" */ = { 637 | isa = XCConfigurationList; 638 | buildConfigurations = ( 639 | OBJ_52 /* Debug */, 640 | OBJ_53 /* Release */, 641 | ); 642 | defaultConfigurationIsVisible = 0; 643 | defaultConfigurationName = Release; 644 | }; 645 | OBJ_76 /* Build configuration list for PBXNativeTarget "SociableWeaverPackageDescription" */ = { 646 | isa = XCConfigurationList; 647 | buildConfigurations = ( 648 | OBJ_77 /* Debug */, 649 | OBJ_78 /* Release */, 650 | ); 651 | defaultConfigurationIsVisible = 0; 652 | defaultConfigurationName = Release; 653 | }; 654 | OBJ_82 /* Build configuration list for PBXAggregateTarget "SociableWeaverPackageTests" */ = { 655 | isa = XCConfigurationList; 656 | buildConfigurations = ( 657 | OBJ_83 /* Debug */, 658 | OBJ_84 /* Release */, 659 | ); 660 | defaultConfigurationIsVisible = 0; 661 | defaultConfigurationName = Release; 662 | }; 663 | OBJ_87 /* Build configuration list for PBXNativeTarget "SociableWeaverTests" */ = { 664 | isa = XCConfigurationList; 665 | buildConfigurations = ( 666 | OBJ_88 /* Debug */, 667 | OBJ_89 /* Release */, 668 | ); 669 | defaultConfigurationIsVisible = 0; 670 | defaultConfigurationName = Release; 671 | }; 672 | /* End XCConfigurationList section */ 673 | }; 674 | rootObject = OBJ_1 /* Project object */; 675 | } 676 | -------------------------------------------------------------------------------- /SociableWeaver.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /SociableWeaver.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SociableWeaver.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded 6 | 7 | 8 | -------------------------------------------------------------------------------- /SociableWeaver.xcodeproj/xcshareddata/xcschemes/SociableWeaver-Package.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 53 | 54 | 60 | 61 | 63 | 64 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /SociableWeaver.xcodeproj/xcshareddata/xcschemes/SociableWeaverTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 14 | 15 | 17 | 23 | 24 | 25 | 26 | 27 | 37 | 38 | 44 | 45 | 47 | 48 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /Sources/SociableWeaver/Enumerations/CaseStyleOption.swift: -------------------------------------------------------------------------------- 1 | /// The case styles available for class name conversions to strings. 2 | public enum CaseStyleOption { 3 | case lowercase 4 | case uppercase 5 | case capitalized 6 | case camelCase 7 | case pascalCase 8 | case snakeCase 9 | case kebabCase 10 | } 11 | -------------------------------------------------------------------------------- /Sources/SociableWeaver/Enumerations/OperationType.swift: -------------------------------------------------------------------------------- 1 | /// The possible operation types that can be made. 2 | public enum OperationType { 3 | case query 4 | case mutation 5 | case subscription 6 | } 7 | 8 | /// Used when a function builder only has one type. 9 | /// This will be removed when function builders fully implemented in Swift. 10 | public enum BuilderType { 11 | case individual 12 | } 13 | -------------------------------------------------------------------------------- /Sources/SociableWeaver/Extensions/Array+Argument.swift: -------------------------------------------------------------------------------- 1 | extension Array where Element == Argument { 2 | /// The GraphQL representation of an arguments array. 3 | var graphQLRepresentable: String { 4 | var components: [String] = [] 5 | 6 | forEach { 7 | components.append("\($0.key): \($0.value.argumentValue)") 8 | } 9 | 10 | return components.joined(separator: ", ") 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Sources/SociableWeaver/Extensions/String+Utils.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension String { 4 | /** 5 | Wraps fields in curly braces to create a key object pair in a string 6 | 7 | - parameter fields: The fields to be wrapped. 8 | */ 9 | func withSubfields(_ fields: String, paginationType: PaginationType? = nil, pageInfo: PageInfoModel? = nil) -> String { 10 | if let pageInfo = pageInfo, paginationType == .cursor { 11 | return "\(self) { edges { cursor node { \(fields) } } \(String(describing: pageInfo.type)) { \(pageInfo.keys.joined(separator: " ")) } }" 12 | } else if paginationType == .cursor { 13 | return "\(self) { edges { cursor node { \(fields) } } }" 14 | } else { 15 | return "\(self) { \(fields) }" 16 | } 17 | } 18 | 19 | /** 20 | Converts any String, or in this intented case, an Object Type string to a specific case style. 21 | This methods splits a string into a string array based on capital letters. 22 | 23 | - parameter option: The preferred case style. 24 | */ 25 | func convert(with option: CaseStyleOption) -> String { 26 | switch option { 27 | case .lowercase: 28 | return self.lowercased() 29 | case .uppercase: 30 | return self.uppercased() 31 | case .capitalized: 32 | return self.capitalized 33 | case .camelCase: 34 | return self.camelCased() 35 | case .pascalCase: 36 | return self.pascalCased() 37 | case .snakeCase: 38 | return self.snakeCased() 39 | case .kebabCase: 40 | return self.kebabCased() 41 | } 42 | } 43 | } 44 | 45 | fileprivate extension String { 46 | /// Determines if a character is uppercase. 47 | var isUppercase: Bool { 48 | String(self).uppercased() == String(self) 49 | } 50 | 51 | /// A string array of words that were capitalized in a string 52 | var capitalizedWords: [String]? { 53 | guard !self.isEmpty else { return nil } 54 | 55 | let indexes = Set(self 56 | .enumerated() 57 | .filter { $0.element.isUppercase } 58 | .map { $0.offset }) 59 | 60 | let words = self 61 | .map { String($0) } 62 | .enumerated() 63 | .reduce([String]()) { words, next -> [String] in 64 | guard !words.isEmpty else { return [next.element] } 65 | guard !indexes.contains(next.offset) else { return words + [String(next.element)] } 66 | 67 | var words = words 68 | words[words.count-1] += String(next.element) 69 | return words 70 | } 71 | 72 | return words 73 | } 74 | 75 | /** 76 | Camel case string style. 77 | Ex. camelCase 78 | */ 79 | func camelCased() -> String { 80 | guard var words = self.capitalizedWords else { return "" } 81 | words[0] = words[0].lowercased() 82 | return words.joined(separator: "") 83 | } 84 | 85 | /** 86 | Pascal case string style. 87 | Ex. PascalCase 88 | */ 89 | func pascalCased() -> String { 90 | guard let words = self.capitalizedWords else { return "" } 91 | return words.map { $0.capitalized }.joined(separator: "") 92 | } 93 | 94 | /** 95 | Snake case string style. 96 | Ex. snake_case 97 | */ 98 | func snakeCased() -> String { 99 | guard let words = self.capitalizedWords else { return "" } 100 | return words.map { $0.lowercased() }.joined(separator: "_") 101 | } 102 | 103 | /** 104 | Kebab case string style. 105 | Ex. kebab-case 106 | */ 107 | func kebabCased() -> String { 108 | guard let words = self.capitalizedWords else { return "" } 109 | return words.map { $0.lowercased() }.joined(separator: "-") 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /Sources/SociableWeaver/FunctionBuilders/ObjectBuilder.swift: -------------------------------------------------------------------------------- 1 | @resultBuilder 2 | public struct ObjectBuilder { 3 | public static func buildBlock(_ children: ObjectWeavable...) -> String { 4 | var descriptions: [String] = [] 5 | 6 | children.forEach { 7 | guard let directive = $0 as? Directive else { return } 8 | if directive.skip || !directive.include { return } 9 | if let removable = directive as? Removable, removable.remove { return } 10 | 11 | descriptions.append(String(describing: $0)) 12 | } 13 | 14 | return descriptions.joined(separator: " ") 15 | } 16 | 17 | public static func buildBlock(_ component: ObjectWeavable) -> String { 18 | String(describing: component) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Sources/SociableWeaver/FunctionBuilders/OperationBuilder.swift: -------------------------------------------------------------------------------- 1 | @resultBuilder 2 | public struct OperationBuilder { 3 | public static func buildBlock(_ children: Weavable...) -> String { 4 | var weavables: [Weavable] = [] 5 | var fragments: [Fragment] = [] 6 | 7 | children.forEach { 8 | if let object = $0 as? Object { 9 | if object.remove { return } 10 | if object.skip || !object.include { return } 11 | 12 | weavables.append(object) 13 | } else if let field = $0 as? Field { 14 | if field.skip || !field.include { return } 15 | 16 | weavables.append(field) 17 | } else if let fragment = $0 as? Fragment { 18 | fragments.append(fragment) 19 | } else if let forEach = $0 as? ForEachWeavable { 20 | if forEach.skip || !forEach.include { return } 21 | 22 | weavables.append(forEach) 23 | } 24 | } 25 | 26 | let weavablesRepresentation = weavables.map { String(describing: $0) }.joined(separator: " ") 27 | let fragmentsRepresentation = fragments.map { String(describing: $0) }.joined(separator: " ") 28 | 29 | return "{ \(weavablesRepresentation) } \(fragmentsRepresentation)".trimmingCharacters(in: .whitespacesAndNewlines) 30 | } 31 | 32 | public static func buildBlock(_ component: Object) -> String { 33 | "{ \(String(describing: component)) }" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Sources/SociableWeaver/Helpers/Field.swift: -------------------------------------------------------------------------------- 1 | /** 2 | GraphQL fields are used to ask for specific fields on objects. 3 | 4 | `Field.name` 5 | The raw name provided to the field. 6 | */ 7 | public struct Field: Directive { 8 | private var name: String 9 | private var nameRepresentable: String 10 | 11 | private var alias: String? = nil 12 | private var arguments: [Argument]? = nil 13 | 14 | var include: Bool = true 15 | var skip: Bool = false 16 | 17 | public init(_ type: Any.Type) { 18 | self.name = String(describing: type) 19 | self.nameRepresentable = String(describing: type).convert(with: .camelCase) 20 | } 21 | 22 | public init(_ key: CodingKey) { 23 | self.name = key.stringValue 24 | self.nameRepresentable = key.stringValue.convert(with: .camelCase) 25 | } 26 | 27 | public init(_ string: String) { 28 | self.name = string 29 | self.nameRepresentable = string.convert(with: .camelCase) 30 | } 31 | } 32 | 33 | public extension Field { 34 | /** 35 | Sets the alias of this field. 36 | 37 | - Parameter alias: The alias to use when constructing this field. 38 | - Returns: A `Field` with the alias in question. 39 | */ 40 | func alias(_ alias: String) -> Field { 41 | var copy = self 42 | copy.alias = alias 43 | return copy 44 | } 45 | 46 | /** 47 | Sets an argument for this field. 48 | 49 | - Parameter key: The key for the argument. 50 | - Parameter value: The value for the argument conforming to `ArgumentValueRepresentable`. 51 | - Returns: A `Field` including the argument passed. 52 | */ 53 | func argument(key: CodingKey, value: ArgumentValueRepresentable?) -> Field { 54 | var copy = self 55 | let argument = Argument(key: key.stringValue, value: value) 56 | 57 | if copy.arguments != nil { 58 | copy.arguments!.append(argument) 59 | } else { 60 | copy.arguments = [argument] 61 | } 62 | 63 | return copy 64 | } 65 | 66 | /** 67 | Sets an argument for this field. 68 | 69 | - Parameter key: The key for the argument. 70 | - Parameter value: The value for the argument conforming to `ArgumentValueRepresentable`. 71 | - Parameter includeIfNil: Boolean to determine if argument should be included when value is nil. Defaults to false. 72 | - Returns: A `Field` including the argument passed. 73 | */ 74 | func argument(key: String, value: ArgumentValueRepresentable, includeIfNil: Bool = false) -> Field { 75 | if value.argumentValue == "null", includeIfNil == false { 76 | return self 77 | } 78 | 79 | var copy = self 80 | let argument = Argument(key: key, value: value) 81 | 82 | if copy.arguments != nil { 83 | copy.arguments!.append(argument) 84 | } else { 85 | copy.arguments = [argument] 86 | } 87 | 88 | return copy 89 | } 90 | 91 | /** 92 | Sets the case style of this field. 93 | 94 | - Parameter caseStyle: The case style to use when constructing this field. 95 | - Returns: A `Field` with the case style applied to the name. 96 | */ 97 | func caseStyle(_ caseStyle: CaseStyleOption) -> Field { 98 | var copy = self 99 | copy.nameRepresentable = copy.name.convert(with: caseStyle) 100 | return copy 101 | } 102 | 103 | /** 104 | Only include this field in the operation if the argument is true. 105 | 106 | - Parameter argument: A boolean argument. 107 | - Returns: A `Field` with its include value set. 108 | */ 109 | func include(if argument: Bool) -> Field { 110 | var copy = self 111 | copy.include = argument 112 | return copy 113 | } 114 | 115 | /** 116 | Skip this field if the argument is true 117 | 118 | - Parameter argument: A boolean argument. 119 | - Returns: A `Field` with its skip value set. 120 | */ 121 | func skip(if argument: Bool) -> Field { 122 | var copy = self 123 | copy.skip = argument 124 | return copy 125 | } 126 | } 127 | 128 | /** 129 | `Field` conforms to `ObjectWeavable` in order to provide a description as well as a debug description of the field in question. 130 | 131 | Example `String(describing: field)`: `newPost: post(id: 1)` 132 | Example `String(reflecting: field)`: `newPost: post(id: 1)` 133 | */ 134 | extension Field: ObjectWeavable { 135 | public var description: String { 136 | buildDescription() 137 | } 138 | 139 | public var debugDescription: String { 140 | buildDescription() 141 | } 142 | } 143 | 144 | private extension Field { 145 | /// Determines which format is needed based on the parameters provided on initialization. 146 | func buildDescription() -> String { 147 | switch(alias, arguments) { 148 | case let(.some(alias), .some(arguments)): 149 | return FieldFormatter.formatField(nameRepresentable, alias: alias, arguments: arguments) 150 | case let(.some(alias), nil): 151 | return FieldFormatter.formatField(nameRepresentable, alias: alias) 152 | case let(nil, .some(arguments)): 153 | return FieldFormatter.formatField(nameRepresentable, arguments: arguments) 154 | default: 155 | return nameRepresentable 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /Sources/SociableWeaver/Helpers/ForEachWeavable.swift: -------------------------------------------------------------------------------- 1 | public struct ForEachWeavable: Directive { 2 | private var objects: [ObjectWeavable] = [] 3 | 4 | var include: Bool = true 5 | var skip: Bool = false 6 | 7 | public init(_ array: [T], content: @escaping (T) -> ObjectWeavable) { 8 | array.forEach { objects.append(content($0)) } 9 | } 10 | } 11 | 12 | public extension ForEachWeavable { 13 | /** 14 | Only include this object in the operation if the argument is true. 15 | 16 | - Parameter argument: A boolean argument. 17 | - Returns: An `Object` with its include value set. 18 | */ 19 | func include(if argument: Bool) -> ForEachWeavable { 20 | var copy = self 21 | copy.include = argument 22 | return copy 23 | } 24 | 25 | /** 26 | Skip this object if the argument is true 27 | 28 | - Parameter argument: A boolean argument. 29 | - Returns: An `Object` with its skip value set. 30 | */ 31 | func skip(if argument: Bool) -> ForEachWeavable { 32 | var copy = self 33 | copy.skip = argument 34 | return copy 35 | } 36 | } 37 | 38 | extension ForEachWeavable: ObjectWeavable { 39 | public var description: String { 40 | objects.map { $0.description }.joined(separator: " ") 41 | } 42 | 43 | public var debugDescription: String { 44 | objects.map { $0.debugDescription }.joined(separator: " ") 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Sources/SociableWeaver/Helpers/Fragment/Fragment.swift: -------------------------------------------------------------------------------- 1 | /** 2 | GraphQL `Fragments` let you construct sets of fields, and then include them in queries where you need to. 3 | 4 | `Fragment.builder` 5 | The `FragmentBuilder` which will be used to construct the fragment's field. 6 | 7 | `Fragment.fieldAggregates` 8 | The aggregated fields that make up the fragment. 9 | */ 10 | public struct Fragment { 11 | var builder: FragmentBuilder 12 | let fieldAggregates: String 13 | 14 | private init(_ builder: FragmentBuilder, fieldAggregates: String) { 15 | self.builder = builder 16 | self.fieldAggregates = fieldAggregates 17 | } 18 | } 19 | 20 | public extension Fragment { 21 | /** 22 | Fragment initializer using the object function builder. 23 | This initializer accepts a `FragmentBuilder` object which will be used as to determine the name. 24 | 25 | - parameter builder: The objects fragment builder. 26 | - parameter content: The object builder accepts structs/classes conforming to `ObjectWeavable`. 27 | */ 28 | init(_ builder: FragmentBuilder, @ObjectBuilder _ content: () -> String) { 29 | self.init(builder, fieldAggregates: content()) 30 | } 31 | 32 | /** 33 | Workaround for function builders not accepting one element yet due to it still being a prototype. 34 | TODO - Remove when functionBuilders are fully implemented. 35 | 36 | Fragment initializer using the object function builder. 37 | This initializer accepts a `FragmentBuilder` object which will be used as to determine the name. 38 | 39 | - parameter builder: The objects fragment builder. 40 | - parameter content: The individual object conforming to `ObjectWeavable` 41 | */ 42 | init(_ builder: FragmentBuilder, _ builderType: BuilderType = .individual, _ content: () -> ObjectWeavable) { 43 | self.init(builder, fieldAggregates: String(describing: content())) 44 | } 45 | } 46 | 47 | /** 48 | `Fragment` conforms to `ObjectWeavable` in order to provide a description as well as a debug description of the fragment in question. 49 | 50 | Example `String(describing: field)`: `fragment authorFields on Author { id name }` 51 | Example `String(reflecting: field)`: `fragment authorFields on Author { id name }` 52 | */ 53 | extension Fragment: Weavable { 54 | public var description: String { 55 | String(describing: builder).withSubfields(fieldAggregates) 56 | } 57 | 58 | public var debugDescription: String { 59 | String(describing: builder).withSubfields(fieldAggregates) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Sources/SociableWeaver/Helpers/Fragment/FragmentBuilder.swift: -------------------------------------------------------------------------------- 1 | /** 2 | A `FragmentBuilder` is used by fragments and fragment references to help construct the needed field. 3 | 4 | `FragmentBuilder.name` 5 | The name given to a `Fragment`. 6 | 7 | `FragmentBuilder.type` 8 | The desired object type of the `Fragment`. 9 | */ 10 | public struct FragmentBuilder { 11 | let name: String 12 | let type: String 13 | 14 | public init(name: String, type: Any.Type) { 15 | self.name = name 16 | self.type = String(describing: type) 17 | } 18 | 19 | public init(name: String, type: String) { 20 | self.name = name 21 | self.type = type 22 | } 23 | } 24 | 25 | /** 26 | `FragmentBuilder` conforms to `ObjectWeavable` in order to provide a description as well as a debug description of the builder in question. 27 | 28 | Example `String(describing: field)`: `fragment authorFields on Author` 29 | Example `String(reflecting: field)`: `fragment authorFields on Author` 30 | */ 31 | extension FragmentBuilder: CustomStringConvertible, CustomDebugStringConvertible { 32 | public var description: String { 33 | "fragment \(name) on \(type)" 34 | } 35 | 36 | public var debugDescription: String { 37 | "fragment \(name) on \(type)" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Sources/SociableWeaver/Helpers/Fragment/FragmentReference.swift: -------------------------------------------------------------------------------- 1 | /** 2 | A `FragmentReference` is a reference by name to a fragment in the operation. 3 | 4 | `Fragment.builder` 5 | The `FragmentBuilder` which will be used to construct the fragment reference's field. 6 | */ 7 | public struct FragmentReference: Directive { 8 | let builder: FragmentBuilder 9 | 10 | var include: Bool = true 11 | var skip: Bool = false 12 | 13 | public init(for builder: FragmentBuilder) { 14 | self.builder = builder 15 | } 16 | } 17 | 18 | public extension FragmentReference { 19 | /** 20 | Only include this fragment reference in the operation if the argument is true. 21 | 22 | - Parameter argument: A boolean argument. 23 | - Returns: A `FragmentReference` with its include value set. 24 | */ 25 | func include(if argument: Bool) -> FragmentReference { 26 | var copy = self 27 | copy.include = argument 28 | return copy 29 | } 30 | 31 | /** 32 | Skip this fragment reference if the argument is true 33 | 34 | - Parameter argument: A boolean argument. 35 | - Returns: A `FragmentReference` with its skip value set. 36 | */ 37 | func skip(if argument: Bool) -> FragmentReference { 38 | var copy = self 39 | copy.skip = argument 40 | return copy 41 | } 42 | } 43 | 44 | /** 45 | `FragmentReference` conforms to `ObjectWeavable` in order to provide a description as well as a debug description of the reference in question. 46 | 47 | Example `String(describing: field)`: `...authorFields` 48 | Example `String(reflecting: field)`: `...authorFields` 49 | */ 50 | extension FragmentReference: ObjectWeavable { 51 | public var description: String { 52 | "...\(builder.name)" 53 | } 54 | 55 | public var debugDescription: String { 56 | "...\(builder.name)" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Sources/SociableWeaver/Helpers/Fragment/InlineFragment.swift: -------------------------------------------------------------------------------- 1 | /** 2 | GraphQL `Inline Fragments` are used when querying a field that returns an interface or a union type. 3 | 4 | `Fragment.type` 5 | The object model that will be queried on. 6 | 7 | `Fragment.fieldAggregates` 8 | The aggregated fields that make up the fragment. 9 | */ 10 | public struct InlineFragment: Directive { 11 | var type: String 12 | let fieldAggregates: String 13 | 14 | var include: Bool = true 15 | var skip: Bool = false 16 | var remove: Bool = false 17 | 18 | private init(_ type: String, fieldAggregates: String) { 19 | self.type = type 20 | self.fieldAggregates = fieldAggregates 21 | } 22 | } 23 | 24 | public extension InlineFragment { 25 | /** 26 | Only include this inline fragment in the operation if the argument is true. 27 | 28 | - Parameter argument: A boolean argument. 29 | - Returns: An `Object` with its include value set. 30 | */ 31 | func include(if argument: Bool) -> InlineFragment { 32 | var copy = self 33 | copy.include = argument 34 | return copy 35 | } 36 | 37 | /** 38 | Skip this inline fragment if the argument is true 39 | 40 | - Parameter argument: A boolean argument. 41 | - Returns: An `Object` with its skip value set. 42 | */ 43 | func skip(if argument: Bool) -> InlineFragment { 44 | var copy = self 45 | copy.skip = argument 46 | return copy 47 | } 48 | } 49 | 50 | public extension InlineFragment { 51 | /** 52 | Inline fragment initializer using a class type as the object type. 53 | 54 | - parameter type: The class type. 55 | - parameter content: The object builder accepts structs/classes conforming to `ObjectWeavable`. 56 | */ 57 | init(_ type: Any.Type, @ObjectBuilder _ content: () -> String) { 58 | let type = String(describing: type) 59 | self.init(type, fieldAggregates: content()) 60 | self.remove = shouldRemove(content: content) 61 | } 62 | 63 | /** 64 | Workaround for function builders not accepting one element yet due to it still being a prototype. 65 | TODO - Remove when functionBuilders are fully implemented. 66 | 67 | Inline fragment initializer using a class type as the object type. 68 | 69 | - parameter type: The class type. 70 | - parameter content: The individual object conforming to `ObjectWeavable`. 71 | */ 72 | init(_ type: Any.Type, _ builderType: BuilderType = .individual, _ content: () -> ObjectWeavable) { 73 | let type = String(describing: type) 74 | self.init(type, fieldAggregates: String(describing: content())) 75 | self.remove = shouldRemove(content: content) 76 | } 77 | 78 | /** 79 | Inline fragment initializer using a string as the object type. 80 | 81 | - parameter type: The string representation of type. 82 | - parameter content: The object builder accepts structs/classes conforming to `ObjectWeavable`. 83 | */ 84 | init(_ type: String, @ObjectBuilder _ content: () -> String) { 85 | self.init(type, fieldAggregates: content()) 86 | self.remove = shouldRemove(content: content) 87 | } 88 | 89 | /** 90 | Workaround for function builders not accepting one element yet due to it still being a prototype. 91 | TODO - Remove when functionBuilders are fully implemented. 92 | 93 | Inline fragment initializer using a string as the object type. 94 | 95 | - parameter type: The string representation of type. 96 | - parameter content: The individual object conforming to `ObjectWeavable`. 97 | */ 98 | init(_ type: String, _ builderType: BuilderType = .individual, _ content: () -> ObjectWeavable) { 99 | self.init(type, fieldAggregates: String(describing: content())) 100 | self.remove = shouldRemove(content: content) 101 | } 102 | } 103 | 104 | /** 105 | `InlineFragment` conforms to `ObjectWeavable` in order to provide a description as well as a debug description of the inline fragment in question. 106 | 107 | Example `String(describing: field)`: `... on AnonymousUser { id }` 108 | Example `String(reflecting: field)`: `... on AnonymousUser { id }` 109 | */ 110 | extension InlineFragment: ObjectWeavable { 111 | public var description: String { 112 | "... on \(type)".withSubfields(fieldAggregates) 113 | } 114 | 115 | public var debugDescription: String { 116 | "... on \(type)".withSubfields(fieldAggregates) 117 | } 118 | } 119 | 120 | extension InlineFragment: Removable { 121 | /// Objects containing no fields are removed. 122 | func shouldRemove(content: () -> CustomStringConvertible) -> Bool { 123 | if let value = content() as? Directive { 124 | if value.skip || !value.include { 125 | return true 126 | } 127 | } 128 | 129 | if let value = content() as? String { 130 | if value == "" { 131 | return true 132 | } 133 | } 134 | 135 | return false 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /Sources/SociableWeaver/Helpers/MetaField.swift: -------------------------------------------------------------------------------- 1 | /// Includes the GraphQL meta field typename as well as any custom meta field. 2 | public enum MetaFieldType { 3 | case typename 4 | case custom(String) 5 | } 6 | 7 | /// GraphQL allows meta fields as part of a request. 8 | public struct MetaField: Directive { 9 | private var type: MetaFieldType 10 | 11 | var include: Bool = true 12 | var skip: Bool = false 13 | 14 | public init(_ type: MetaFieldType) { 15 | self.type = type 16 | } 17 | } 18 | 19 | public extension MetaField { 20 | /** 21 | Only include typename if the argument is true. 22 | 23 | - Parameter argument: A boolean argument. 24 | - Returns: A `Typename` with its include value set. 25 | */ 26 | func include(if argument: Bool) -> MetaField { 27 | var copy = self 28 | copy.include = argument 29 | return copy 30 | } 31 | 32 | /** 33 | Skip typename if the argument is true 34 | 35 | - Parameter argument: A boolean argument. 36 | - Returns: A `Typename` with its skip value set. 37 | */ 38 | func skip(if argument: Bool) -> MetaField { 39 | var copy = self 40 | copy.skip = argument 41 | return copy 42 | } 43 | } 44 | 45 | /// `MetaField` conforms to `ObjectWeavable` in order to provide a description as well as a debug description. 46 | extension MetaField: ObjectWeavable { 47 | public var description: String { 48 | buildDescription() 49 | } 50 | 51 | public var debugDescription: String { 52 | buildDescription() 53 | } 54 | } 55 | 56 | private extension MetaField { 57 | /// Determines which format is needed based on the parameters provided on initialization. 58 | func buildDescription() -> String { 59 | switch type { 60 | case .typename: 61 | return "__typename" 62 | case .custom(let field): 63 | return "__\(field)" 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Sources/SociableWeaver/Helpers/Object/Object.swift: -------------------------------------------------------------------------------- 1 | /** 2 | GraphQL objects are made up of one or more fields. 3 | 4 | `Object.name` 5 | The raw name provided to the object. 6 | 7 | `Object.fieldAggregates` 8 | The aggregated fields that make up the object. 9 | */ 10 | 11 | public struct Object: Directive { 12 | private var name: String 13 | private var nameRepresentable: String 14 | private let fieldAggregates: String 15 | 16 | private var alias: String? = nil 17 | private var arguments: [Argument]? = nil 18 | private var slice: Slice? = nil 19 | private var paginationType: PaginationType? = nil 20 | private var pageInfo: PageInfoModel? = nil 21 | 22 | var include: Bool = true 23 | var skip: Bool = false 24 | var remove: Bool = false 25 | 26 | private init(_ type: Any.Type, fieldAggregates: String) { 27 | self.name = String(describing: type) 28 | self.nameRepresentable = String(describing: type).convert(with: .camelCase) 29 | self.fieldAggregates = fieldAggregates 30 | } 31 | 32 | private init(_ key: String, fieldAggregates: String) { 33 | self.name = key 34 | self.nameRepresentable = key.convert(with: .camelCase) 35 | self.fieldAggregates = fieldAggregates 36 | } 37 | 38 | private init(_ key: CodingKey, fieldAggregates: String) { 39 | self.name = key.stringValue 40 | self.nameRepresentable = key.stringValue.convert(with: .camelCase) 41 | self.fieldAggregates = fieldAggregates 42 | } 43 | 44 | private init(fieldAggregates: String) { 45 | self.name = "" 46 | self.nameRepresentable = "" 47 | self.fieldAggregates = fieldAggregates 48 | } 49 | } 50 | 51 | public extension Object { 52 | /** 53 | Sets the alias of this object. 54 | 55 | - Parameter alias: The alias to use when constructing this object. 56 | - Returns: An `Object` with the alias in question. 57 | */ 58 | func alias(_ alias: String) -> Object { 59 | var copy = self 60 | copy.alias = alias 61 | return copy 62 | } 63 | 64 | /** 65 | Sets an argument for this object. 66 | 67 | - Parameter key: The key for the argument. 68 | - Parameter value: The value for the argument conforming to `ArgumentValueRepresentable`. 69 | - Returns: An `Object` including the argument passed. 70 | */ 71 | func argument(key: CodingKey, value: ArgumentValueRepresentable) -> Object { 72 | var copy = self 73 | let argument = Argument(key: key.stringValue, value: value) 74 | 75 | if copy.arguments != nil { 76 | copy.arguments!.append(argument) 77 | } else { 78 | copy.arguments = [argument] 79 | } 80 | 81 | return copy 82 | } 83 | 84 | /** 85 | Sets an argument for this object. 86 | 87 | - Parameter key: The key for the argument. 88 | - Parameter value: The value for the argument conforming to `ArgumentValueRepresentable`. 89 | - Parameter includeIfNil: Boolean to determine if argument should be included when value is nil. Defaults to false. 90 | - Returns: An `Object` including the argument passed. 91 | */ 92 | func argument(key: String, value: ArgumentValueRepresentable, includeIfNil: Bool = false) -> Object { 93 | if value.argumentValue == "null", includeIfNil == false { 94 | return self 95 | } 96 | 97 | var copy = self 98 | let argument = Argument(key: key, value: value) 99 | 100 | if copy.arguments != nil { 101 | copy.arguments!.append(argument) 102 | } else { 103 | copy.arguments = [argument] 104 | } 105 | 106 | return copy 107 | } 108 | 109 | /** 110 | Sets the case style of this object. 111 | 112 | - Parameter caseStyle: The case style to use when constructing this object. 113 | - Returns: An `Object` with the case style applied to the name. 114 | */ 115 | func caseStyle(_ caseStyle: CaseStyleOption) -> Object { 116 | var copy = self 117 | copy.nameRepresentable = copy.name.convert(with: caseStyle) 118 | return copy 119 | } 120 | 121 | /** 122 | Only include this object in the operation if the argument is true. 123 | 124 | - Parameter argument: A boolean argument. 125 | - Returns: An `Object` with its include value set. 126 | */ 127 | func include(if argument: Bool) -> Object { 128 | var copy = self 129 | copy.include = argument 130 | return copy 131 | } 132 | 133 | /** 134 | The pagination type for the request. Mainly implemented to handle cursor-based pagination. 135 | 136 | - Parameter type: The pagination type. 137 | - Returns: An `Object` with its pagination type set. 138 | */ 139 | func paginationType(_ type: PaginationType) -> Object { 140 | var copy = self 141 | copy.paginationType = type 142 | return copy 143 | } 144 | 145 | /** 146 | The model and keys for a page info object that might be used with cursor-based pagination. 147 | 148 | - Parameter type: The model type. 149 | - Parameter caseStyle: The case style for the model type name. 150 | - Parameter keys: The keys for the model. 151 | - Returns: An `Object` with its page info model set. 152 | */ 153 | func pageInfo(type: Any.Type, caseStyle: CaseStyleOption = .camelCase, keys: CodingKey...) -> Object { 154 | var copy = self 155 | copy.pageInfo = PageInfoModel(type: String(describing: type).convert(with: caseStyle), keys: keys.map { $0.stringValue }) 156 | return copy 157 | } 158 | 159 | /** 160 | The model and keys for a page info object that might be used with cursor-based pagination. 161 | 162 | - Parameter name: The name of the page info model. 163 | - Parameter keys: The keys for the model. 164 | - Returns: An `Object` with its page info model set. 165 | */ 166 | func pageInfo(name: String, keys: String...) -> Object { 167 | var copy = self 168 | copy.pageInfo = PageInfoModel(type: name, keys: keys) 169 | return copy 170 | } 171 | 172 | /** 173 | Sets the field name to the name of the query schema name. 174 | 175 | - Parameter name: The queries schema name for the request. 176 | - Returns: An `Object` named after the schema. 177 | */ 178 | func schemaName(_ name: String) -> Object { 179 | var copy = self 180 | copy.name = name 181 | copy.nameRepresentable = name 182 | return copy 183 | } 184 | 185 | /** 186 | Skip this object if the argument is true 187 | 188 | - Parameter argument: A boolean argument. 189 | - Returns: An `Object` with its skip value set. 190 | */ 191 | func skip(if argument: Bool) -> Object { 192 | var copy = self 193 | copy.skip = argument 194 | return copy 195 | } 196 | 197 | /** 198 | Adds a slice to fetch a specified amount. 199 | 200 | - Parameter amount: Number of desired results 201 | - Returns: An `Object` with its slice value set. 202 | */ 203 | func slice(amount: Int) -> Object { 204 | var copy = self 205 | copy.slice = Slice(first: amount) 206 | return copy 207 | } 208 | 209 | /** 210 | Adds a slice to fetch a specified amount of results at a provided offset. 211 | 212 | - Parameter amount: Number of desired results 213 | - Parameter offset: Desired offset for results lookup 214 | - Returns: An `Object` with its slice value set. 215 | */ 216 | func slice(amount: Int, offset: Int) -> Object { 217 | var copy = self 218 | copy.slice = Slice(first: amount, offset: offset) 219 | return copy 220 | } 221 | 222 | /** 223 | Adds a slice to fetch a specified amount of results at a provided offset. 224 | 225 | - Parameter amount: Number of desired results 226 | - Parameter key: Key to determine which variable to check when determining offset 227 | - Returns: An `Object` with its slice value set. 228 | */ 229 | func slice(amount: Int, after key: ArgumentValueRepresentable) -> Object { 230 | var copy = self 231 | copy.slice = Slice(first: amount, after: key) 232 | return copy 233 | } 234 | } 235 | 236 | public extension Object { 237 | /** 238 | Object initializer using the object function builder. 239 | This initializer accepts `Any.Type` which will be used as to determine the name. 240 | 241 | - parameter type: The type of `Any`. 242 | - parameter content: The object builder accepts structs/classes conforming to `ObjectWeavable`. 243 | */ 244 | init(_ type: Any.Type, @ObjectBuilder _ content: () -> String) { 245 | self.init(type, fieldAggregates: content()) 246 | self.remove = shouldRemove(content: content) 247 | } 248 | 249 | /** 250 | Workaround for function builders not accepting one element yet due to it still being a prototype. 251 | TODO - Remove when functionBuilders are fully implemented. 252 | 253 | Object initializer using the object function builder. 254 | This initializer accepts `Any.Type` which will be used as to determine the name. 255 | 256 | - parameter type: The type of `Any` used for the name. 257 | - parameter content: The individual object conforming to `ObjectWeavable`. 258 | */ 259 | init(_ type: Any.Type, _ builderType: BuilderType = .individual, _ content: () -> ObjectWeavable) { 260 | self.init(type, fieldAggregates: String(describing: content())) 261 | self.skip = shouldRemove(content: content) 262 | } 263 | 264 | /** 265 | Object initializer using the object function builder. 266 | This initializer accepts a `CodingKey` which will be used as to determine the name. 267 | 268 | - parameter key: The coding key used for the name. 269 | - parameter content: The object builder accepts structs/classes conforming to `ObjectWeavable`. 270 | */ 271 | init(_ key: CodingKey, @ObjectBuilder _ content: () -> String) { 272 | self.init(key, fieldAggregates: content()) 273 | self.remove = shouldRemove(content: content) 274 | } 275 | 276 | /** 277 | Workaround for function builders not accepting one element yet due to it still being a prototype. 278 | TODO - Remove when functionBuilders are fully implemented. 279 | 280 | Object initializer using the object function builder. 281 | This initializer accepts a `CodingKey` which will be used as to determine the name. 282 | 283 | - parameter key: The coding key used for the name. 284 | - parameter content: The individual object conforming to `ObjectWeavable`. 285 | */ 286 | init(_ key: CodingKey, _ builderType: BuilderType = .individual, _ content: () -> ObjectWeavable) { 287 | self.init(key, fieldAggregates: String(describing: content())) 288 | self.remove = shouldRemove(content: content) 289 | } 290 | 291 | /** 292 | Object initializer using the object function builder. 293 | This initializer accepts a `String` which will be used as to determine the name. 294 | 295 | - parameter key: The coding key used for the name. 296 | - parameter content: The object builder accepts structs/classes conforming to `ObjectWeavable`. 297 | */ 298 | init(_ key: String, @ObjectBuilder _ content: () -> String) { 299 | self.init(key, fieldAggregates: content()) 300 | self.remove = shouldRemove(content: content) 301 | } 302 | 303 | /** 304 | Workaround for function builders not accepting one element yet due to it still being a prototype. 305 | TODO - Remove when functionBuilders are fully implemented. 306 | 307 | Object initializer using the object function builder. 308 | This initializer accepts a `String` which will be used as to determine the name. 309 | 310 | - parameter key: The coding key used for the name. 311 | - parameter content: The individual object conforming to `ObjectWeavable`. 312 | */ 313 | init(_ key: String, _ builderType: BuilderType = .individual, _ content: () -> ObjectWeavable) { 314 | self.init(key, fieldAggregates: String(describing: content())) 315 | self.remove = shouldRemove(content: content) 316 | } 317 | 318 | /** 319 | Object initializer using the object function builder. 320 | This initializer is for objects without a name. 321 | 322 | - parameter content: The object builder accepts structs/classes conforming to `ObjectWeavable`. 323 | */ 324 | init(@ObjectBuilder _ content: () -> String) { 325 | self.init(fieldAggregates: content()) 326 | self.remove = shouldRemove(content: content) 327 | } 328 | 329 | /** 330 | Workaround for function builders not accepting one element yet due to it still being a prototype. 331 | TODO - Remove when functionBuilders are fully implemented. 332 | 333 | Object initializer using the object function builder. 334 | This initializer is for objects without a name. 335 | 336 | - parameter content: The individual object conforming to `ObjectWeavable`. 337 | */ 338 | init(_ builderType: BuilderType = .individual, _ content: () -> ObjectWeavable) { 339 | self.init(fieldAggregates: String(describing: content())) 340 | self.remove = shouldRemove(content: content) 341 | } 342 | } 343 | 344 | /** 345 | `Object` conforms to `ObjectWeavable` in order to provide a description as well as a debug description of the object model in question. 346 | 347 | Example `String(describing: object)`: `post { id title content }` 348 | Example `String(reflecting: object)`: `post { id title content }` 349 | */ 350 | extension Object: ObjectWeavable { 351 | public var description: String { 352 | if slice != nil { 353 | return buildDescription().withSubfields(fieldAggregates, paginationType: paginationType, pageInfo: pageInfo) 354 | } else { 355 | return buildDescription().withSubfields(fieldAggregates) 356 | } 357 | } 358 | 359 | public var debugDescription: String { 360 | if slice != nil { 361 | return buildDescription().withSubfields(fieldAggregates, paginationType: paginationType, pageInfo: pageInfo) 362 | } else { 363 | return buildDescription().withSubfields(fieldAggregates) 364 | } 365 | } 366 | } 367 | 368 | extension Object: Removable { 369 | /// Objects containing no fields are removed. 370 | func shouldRemove(content: () -> CustomStringConvertible) -> Bool { 371 | if let value = content() as? Directive { 372 | if value.skip || !value.include { 373 | return true 374 | } 375 | } 376 | 377 | if let value = content() as? String { 378 | if value == "" { 379 | return true 380 | } 381 | } 382 | 383 | return false 384 | } 385 | } 386 | 387 | private extension Object { 388 | /// Determines which format is needed based on the parameters provided on initialization. 389 | func buildDescription() -> String { 390 | switch(alias, arguments) { 391 | case let(.some(alias), .some(arguments)): 392 | return FieldFormatter.formatField(nameRepresentable, alias: alias, arguments: arguments, slice: slice) 393 | case let(.some(alias), nil): 394 | return FieldFormatter.formatField(nameRepresentable, alias: alias, slice: slice) 395 | case let(nil, .some(arguments)): 396 | return FieldFormatter.formatField(nameRepresentable, arguments: arguments, slice: slice) 397 | default: 398 | return FieldFormatter.formatField(nameRepresentable, slice: slice) 399 | } 400 | } 401 | } 402 | -------------------------------------------------------------------------------- /Sources/SociableWeaver/Helpers/Object/PageInfoModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PageInfo.swift 3 | // SociableWeaver 4 | // 5 | // Created by Nicholas Bellucci on 8/8/20. 6 | // 7 | 8 | import Foundation 9 | 10 | internal struct PageInfoModel { 11 | var type: String 12 | var keys: [String] 13 | } 14 | -------------------------------------------------------------------------------- /Sources/SociableWeaver/Helpers/Object/Slice.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Slice.swift 3 | // SociableWeaver 4 | // 5 | // Created by Nicholas Bellucci on 8/8/20. 6 | // 7 | 8 | import Foundation 9 | 10 | public enum PaginationType { 11 | case `default` 12 | case cursor 13 | } 14 | 15 | struct Slice { 16 | var first: Int 17 | var offset: Int? 18 | var after: ArgumentValueRepresentable? 19 | } 20 | 21 | /** 22 | `Slice` conforms to `ObjectWeavable` in order to provide a description as well as a debug description of the object model in question. 23 | 24 | Example `String(describing: slice)`: `(first: 1, offset 2)` 25 | Example `String(reflecting: slice)`: `(first: 1, offset 2)` 26 | */ 27 | extension Slice: ObjectWeavable { 28 | public var description: String { 29 | buildDescription() 30 | } 31 | 32 | public var debugDescription: String { 33 | buildDescription() 34 | } 35 | } 36 | 37 | private extension Slice { 38 | /// Determines which format is needed based on the parameters provided on initialization. 39 | func buildDescription() -> String { 40 | switch(offset, after) { 41 | case let(.some(offset), nil): 42 | return "(first: \(first), offset: \(offset))" 43 | case let(nil, .some(after)): 44 | return "(first: \(first), after: \(after.argumentValue))" 45 | default: 46 | return "(first: \(first))" 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Sources/SociableWeaver/Helpers/Weave.swift: -------------------------------------------------------------------------------- 1 | /** 2 | GraphQL supports three types of operations being queries, mutations, and subscriptions. 3 | 4 | `Weave.type` 5 | The type of GraphQL operation to be created. 6 | 7 | `Weave.fields` 8 | The aggregated fields that make up the operation. 9 | */ 10 | 11 | public struct Weave { 12 | private let type: OperationType 13 | private let fields: String 14 | 15 | private var name: String? 16 | 17 | private init(type: OperationType, fields: String) { 18 | self.type = type 19 | self.fields = fields 20 | } 21 | } 22 | 23 | public extension Weave { 24 | /** 25 | Sets the operation name. 26 | 27 | - Parameter name: The desired name of the operation. 28 | - Returns: An `Operation` with the name as the parent field. 29 | */ 30 | func name(_ name: String) -> Weave { 31 | var copy = self 32 | copy.name = name 33 | return copy 34 | } 35 | } 36 | 37 | public extension Weave { 38 | /** 39 | Operation initializer using the operation function builder. 40 | 41 | - parameter type: The operation type to be created. 42 | - parameter content: The operation builder accepts `Weavable` models. 43 | */ 44 | init(_ type: OperationType, @OperationBuilder _ content: () -> String) { 45 | self.init(type: type, fields: String(describing: content())) 46 | } 47 | 48 | /** 49 | Workaround for function builders not accepting one element yet due to it still being a prototype. 50 | TODO - Remove when functionBuilders are fully implemented. 51 | 52 | Operation initializer using the operation function builder. 53 | 54 | - parameter type: The operation type to be created. 55 | - parameter builderType: The builder type only exists to avoid ambiguous init error. 56 | - parameter content: The individual `Weavable` model. 57 | */ 58 | init(_ type: OperationType, _ builderType: BuilderType = .individual, _ content: () -> Weavable) { 59 | self.init(type: type, fields: "{ \(String(describing: content())) }") 60 | } 61 | } 62 | 63 | /** 64 | `Weave` conforms to `Weavable` in order to provide a description as well as a debug description of the operation model in question. 65 | 66 | Example `String(describing: operation)`: `query { post { id title content } }` 67 | Example `String(reflecting: operation)`: `query { post { id title content } }` 68 | */ 69 | extension Weave: Weavable { 70 | public var description: String { 71 | buildDescription() 72 | } 73 | 74 | public var debugDescription: String { 75 | buildDescription() 76 | } 77 | } 78 | 79 | private extension Weave { 80 | /// Determines which format is needed based on the parameters provided on initialization. 81 | func buildDescription() -> String { 82 | switch name { 83 | case let .some(name): 84 | return "\(type) \(name) \(fields)" 85 | default: 86 | return "\(type) \(fields)" 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Sources/SociableWeaver/Protocols/Argument.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | /** 3 | `Argument` is a type alias that defines a key value tuple. 4 | 5 | This tuple provides a `String` and an `ArgumentValueRepresentable` which is 6 | used to construct an argument for a field. 7 | */ 8 | typealias Argument = (key: String, value: ArgumentValueRepresentable) 9 | 10 | public protocol EnumValueRepresentable: ArgumentValueRepresentable { } 11 | public protocol EnumRawValueRepresentable: ArgumentValueRepresentable, RawRepresentable { } 12 | 13 | public extension EnumValueRepresentable { 14 | var argumentValue: String { 15 | "\(self)".uppercased() 16 | } 17 | } 18 | 19 | public extension EnumRawValueRepresentable { 20 | var argumentValue: String { 21 | "\(self.rawValue)".uppercased() 22 | } 23 | } 24 | 25 | /// A type that can be used as to represent an argument. 26 | public protocol ArgumentValueRepresentable { 27 | 28 | /// The argument representation of a given value. 29 | var argumentValue: String { get } 30 | } 31 | 32 | extension Bool: ArgumentValueRepresentable { 33 | public var argumentValue: String { 34 | "\(self)" 35 | } 36 | } 37 | 38 | extension String: ArgumentValueRepresentable { 39 | public var argumentValue: String { 40 | "\"\(self)\"" 41 | } 42 | } 43 | 44 | extension Int: ArgumentValueRepresentable { 45 | public var argumentValue: String { 46 | "\(self)" 47 | } 48 | } 49 | 50 | extension Double: ArgumentValueRepresentable { 51 | public var argumentValue: String { 52 | "\(self)" 53 | } 54 | } 55 | 56 | extension Float: ArgumentValueRepresentable { 57 | public var argumentValue: String { 58 | "\(self)" 59 | } 60 | } 61 | 62 | extension Optional: ArgumentValueRepresentable { 63 | public var argumentValue: String { 64 | switch self { 65 | case .some(let wrapped): 66 | if let wrapped = wrapped as? ArgumentValueRepresentable { 67 | return wrapped.argumentValue 68 | } else { 69 | return "null" 70 | } 71 | default: 72 | return "null" 73 | } 74 | } 75 | } 76 | 77 | extension Array: ArgumentValueRepresentable { 78 | public var argumentValue: String { 79 | let params = map { value -> String in 80 | if let value = value as? String { 81 | return value.argumentValue 82 | } else if let value = value as? Int { 83 | return value.argumentValue 84 | } else if let value = value as? Float { 85 | return value.argumentValue 86 | } else if let value = value as? Double { 87 | return value.argumentValue 88 | } else if let value = value as? Bool { 89 | return value.argumentValue 90 | } else if let value = value as? Array { 91 | return value.argumentValue 92 | } else if let value = value as? Dictionary { 93 | return value.argumentValue 94 | } else if let value = value as? ArgumentValueRepresentable { 95 | return value.argumentValue 96 | } 97 | 98 | return "" 99 | } 100 | 101 | return "[\(params.joined(separator: ", "))]" 102 | } 103 | } 104 | 105 | extension Dictionary: ArgumentValueRepresentable { 106 | public var argumentValue: String { 107 | let params = map { (arg) -> String in 108 | let (key, value) = arg 109 | 110 | var argumentValue: String { 111 | if let value = value as? String { 112 | return value.argumentValue 113 | } else if let value = value as? Int { 114 | return value.argumentValue 115 | } else if let value = value as? Float { 116 | return value.argumentValue 117 | } else if let value = value as? Double { 118 | return value.argumentValue 119 | } else if let value = value as? Bool { 120 | return value.argumentValue 121 | } else if let value = value as? Array { 122 | return value.argumentValue 123 | } else if let value = value as? Dictionary { 124 | return value.argumentValue 125 | } else if let value = value as? ArgumentValueRepresentable { 126 | return value.argumentValue 127 | } else if let _ = value as? NSNull { 128 | return "null" 129 | } 130 | 131 | return "" 132 | } 133 | 134 | return "\(key): \(argumentValue)" 135 | } 136 | 137 | return "{\(params.sorted().joined(separator: ", "))}" 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /Sources/SociableWeaver/Protocols/Directive.swift: -------------------------------------------------------------------------------- 1 | /** 2 | A directive can be attached to a field or fragment reference. 3 | 4 | Directives will not appear in the GraphQL query generated but will be handled properly in swift. 5 | */ 6 | protocol Directive { 7 | var include: Bool { get set } 8 | var skip: Bool { get set } 9 | } 10 | -------------------------------------------------------------------------------- /Sources/SociableWeaver/Protocols/Removable.swift: -------------------------------------------------------------------------------- 1 | /// Defines models that can be removed from an operation when empty. 2 | protocol Removable { 3 | var remove: Bool { get set } 4 | func shouldRemove(content: () -> CustomStringConvertible) -> Bool 5 | } 6 | -------------------------------------------------------------------------------- /Sources/SociableWeaver/Protocols/Weavable.swift: -------------------------------------------------------------------------------- 1 | /** 2 | A type that conforms to both `CustomStringConvertible` and `CustomDebugStringConvertible`. 3 | 4 | Weavable is inherited by models that are used to construct a GraphQL query/mutation. 5 | */ 6 | public protocol Weavable: CustomStringConvertible, CustomDebugStringConvertible { } 7 | 8 | public protocol ObjectWeavable: Weavable { } 9 | -------------------------------------------------------------------------------- /Sources/SociableWeaver/Utils/FieldFormatter.swift: -------------------------------------------------------------------------------- 1 | public class FieldFormatter { 2 | /** 3 | Formats a field with a name 4 | 5 | Example `post` 6 | */ 7 | static func formatField(_ name: String, slice: Slice? = nil) -> String { 8 | if let slice = slice { 9 | return "\(name)\(slice.description)" 10 | } else { 11 | return "\(name)" 12 | } 13 | } 14 | 15 | /** 16 | Formats a field with a name and alias. 17 | 18 | Example `newPost: post` 19 | */ 20 | static func formatField(_ name: String, alias: String, slice: Slice? = nil) -> String { 21 | if let slice = slice { 22 | return "\(alias): \(name)\(slice.description)" 23 | } else { 24 | return "\(alias): \(name)" 25 | } 26 | } 27 | 28 | /** 29 | Formats a field with a name and arguments. 30 | 31 | Example `post(id: 1)` 32 | */ 33 | static func formatField(_ name: String, arguments: [Argument], slice: Slice? = nil) -> String { 34 | if let slice = slice { 35 | return "\(name)\(slice.description)" 36 | } else { 37 | return "\(name)(\(arguments.graphQLRepresentable))" 38 | } 39 | } 40 | 41 | /** 42 | Formats a field with a name, alias, and arguments. 43 | 44 | Example `newPost: post(id: 1)` 45 | */ 46 | static func formatField(_ name: String, alias: String, arguments: [Argument], slice: Slice? = nil) -> String { 47 | if let slice = slice { 48 | return "\(alias): \(name)\(slice.description)" 49 | } else { 50 | return "\(alias): \(name)(\(arguments.graphQLRepresentable))" 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import SociableWeaverTests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += SociableWeaverTests.allTests() 7 | XCTMain(tests) 8 | -------------------------------------------------------------------------------- /Tests/SociableWeaverTests/Models/Author.swift: -------------------------------------------------------------------------------- 1 | import SociableWeaver 2 | 3 | public struct Author: Codable { 4 | public enum CodingKeys: String, CodingKey, CaseIterable { 5 | case id, name, age, birthplace 6 | } 7 | 8 | public let id: String 9 | public let name: String 10 | public let age: Int? 11 | public let birthplace: [String: String] 12 | 13 | public init(id: String, name: String, age: Int?, birthplace: [String: String]) { 14 | self.id = id 15 | self.name = name 16 | self.age = age 17 | self.birthplace = birthplace 18 | } 19 | } 20 | 21 | extension Author: ArgumentValueRepresentable { 22 | public var argumentValue: String { 23 | var params: [String: String?] = [:] 24 | 25 | params["id"] = id.argumentValue 26 | params["name"] = name.argumentValue 27 | params["age"] = age?.argumentValue 28 | params["birthplace"] = birthplace.argumentValue 29 | 30 | let paramStrings: [String] = params.compactMap { argument in 31 | guard let value = argument.value else { 32 | return nil 33 | } 34 | 35 | return "\(argument.key): \(value)" 36 | } 37 | 38 | return "{ \(paramStrings.joined(separator: ",")) }" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Tests/SociableWeaverTests/Models/Comment.swift: -------------------------------------------------------------------------------- 1 | public struct Comment: Codable { 2 | public enum CodingKeys: String, CodingKey, CaseIterable { 3 | case id, author, content, createdAt 4 | } 5 | 6 | public let id: String 7 | public let author: Author 8 | public let content: String 9 | public let createdAt: String 10 | 11 | public init(id: String, author: Author, content: String, createdAt: String) { 12 | self.id = id 13 | self.author = author 14 | self.content = content 15 | self.createdAt = createdAt 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Tests/SociableWeaverTests/Models/PageInfo.swift: -------------------------------------------------------------------------------- 1 | public struct PageInfo: Codable { 2 | public enum CodingKeys: String, CodingKey, CaseIterable { 3 | case startCursor, endCursor, hasNextPage 4 | } 5 | 6 | public let startCursor: String 7 | public let endCursor: String 8 | public let hasNextPage: Bool 9 | 10 | public init(startCursor: String, endCursor: String, hasNextPage: Bool) { 11 | self.startCursor = startCursor 12 | self.endCursor = endCursor 13 | self.hasNextPage = hasNextPage 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Tests/SociableWeaverTests/Models/Post.swift: -------------------------------------------------------------------------------- 1 | public struct Post: Codable { 2 | public enum CodingKeys: String, CodingKey, CaseIterable { 3 | case id, title, content, author, comments 4 | } 5 | 6 | public let id: String 7 | public let title: String 8 | public let content: String 9 | public let author: Author 10 | public let comments: [Comment] 11 | 12 | public init(id: String, title: String, content: String, author: Author, comments: [Comment]) { 13 | self.id = id 14 | self.title = title 15 | self.content = content 16 | self.author = author 17 | self.comments = comments 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Tests/SociableWeaverTests/SociableWeaverBuilderTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import SociableWeaver 3 | 4 | final class SociableWeaverBuilderTests: XCTestCase { 5 | func testBuildField() { 6 | let query = Weave(.query) { 7 | Field(Comment.CodingKeys.id) 8 | } 9 | 10 | let expected = "query { id }" 11 | XCTAssertEqual(String(describing: query), expected) 12 | } 13 | 14 | func testBuildObject() { 15 | let query = Weave(.query) { 16 | Object(Post.self) { 17 | Field(Author.CodingKeys.id) 18 | Field(Author.CodingKeys.name) 19 | } 20 | } 21 | 22 | let expected = "query { post { id name } }" 23 | XCTAssertEqual(String(describing: query), expected) 24 | } 25 | 26 | func testBuildObjectWithAlias() { 27 | let query = Weave(.query) { 28 | Object(Post.self) { 29 | Field(Author.CodingKeys.id) 30 | Field(Author.CodingKeys.name) 31 | } 32 | .alias("testAlias") 33 | } 34 | 35 | let expected = "query { testAlias: post { id name } }" 36 | XCTAssertEqual(String(describing: query), expected) 37 | } 38 | 39 | func testBuildObjectWithSchemaName() { 40 | let query = Weave(.query) { 41 | Object(Post.self) { 42 | Field(Author.CodingKeys.id) 43 | Field(Author.CodingKeys.name) 44 | } 45 | .schemaName("testSchema") 46 | } 47 | 48 | let expected = "query { testSchema { id name } }" 49 | XCTAssertEqual(String(describing: query), expected) 50 | } 51 | 52 | func testBuildObjectWithArgument() { 53 | let query = Weave(.query) { 54 | Object(Post.self) { 55 | Field(Author.CodingKeys.id) 56 | Field(Author.CodingKeys.name) 57 | } 58 | .argument(key: "testArgument", value: "test") 59 | } 60 | 61 | let expected = "query { post(testArgument: \"test\") { id name } }" 62 | XCTAssertEqual(String(describing: query), expected) 63 | } 64 | 65 | func testBuildObjectWithNilArgument() { 66 | let value: String? = nil 67 | 68 | let query = Weave(.query) { 69 | Object(Post.self) { 70 | Field(Author.CodingKeys.id) 71 | Field(Author.CodingKeys.name) 72 | } 73 | .argument(key: "testArgument", value: value) 74 | } 75 | 76 | let expected = "query { post { id name } }" 77 | XCTAssertEqual(String(describing: query), expected) 78 | } 79 | 80 | func testBuildObjectWithCaseStyle() { 81 | let query = Weave(.query) { 82 | Object(Post.self) { 83 | Field(Author.CodingKeys.id) 84 | Field(Author.CodingKeys.name) 85 | } 86 | .caseStyle(.capitalized) 87 | } 88 | 89 | let expected = "query { Post { id name } }" 90 | XCTAssertEqual(String(describing: query), expected) 91 | } 92 | 93 | func testBuildObjectWithFalseInclude() { 94 | let query = Weave(.query) { 95 | Object(Post.self) { 96 | Field(Author.CodingKeys.id) 97 | Field(Author.CodingKeys.name) 98 | } 99 | 100 | Object(Author.self) { 101 | Field(Author.CodingKeys.id) 102 | Field(Author.CodingKeys.name) 103 | } 104 | .include(if: false) 105 | } 106 | 107 | let expected = "query { post { id name } }" 108 | XCTAssertEqual(String(describing: query), expected) 109 | } 110 | 111 | func testBuildObjectWithTrueSkip() { 112 | let query = Weave(.query) { 113 | Object(Post.self) { 114 | Field(Author.CodingKeys.id) 115 | Field(Author.CodingKeys.name) 116 | } 117 | 118 | Object(Author.self) { 119 | Field(Author.CodingKeys.id) 120 | Field(Author.CodingKeys.name) 121 | } 122 | .skip(if: true) 123 | } 124 | 125 | let expected = "query { post { id name } }" 126 | XCTAssertEqual(String(describing: query), expected) 127 | } 128 | 129 | func testBuildObjectWithSlice() { 130 | let query = Weave(.query) { 131 | Object(Post.CodingKeys.comments) { 132 | Field(Comment.CodingKeys.id) 133 | Field(Comment.CodingKeys.author) 134 | Field(Comment.CodingKeys.content) 135 | } 136 | .slice(amount: 2) 137 | } 138 | 139 | let expected = "query { comments(first: 2) { id author content } }" 140 | XCTAssertEqual(String(describing: query), expected) 141 | } 142 | 143 | func testBuildObjectWithSliceAndOffset() { 144 | let query = Weave(.query) { 145 | Object(Post.CodingKeys.comments) { 146 | Field(Comment.CodingKeys.id) 147 | Field(Comment.CodingKeys.author) 148 | Field(Comment.CodingKeys.content) 149 | } 150 | .slice(amount: 2, offset: 2) 151 | } 152 | 153 | let expected = "query { comments(first: 2, offset: 2) { id author content } }" 154 | XCTAssertEqual(String(describing: query), expected) 155 | } 156 | 157 | func testBuildObjectWithSliceAfterKey() { 158 | let query = Weave(.query) { 159 | Object(Post.CodingKeys.comments) { 160 | Field(Comment.CodingKeys.id) 161 | Field(Comment.CodingKeys.author) 162 | Field(Comment.CodingKeys.content) 163 | } 164 | .slice(amount: 2, after: "123") 165 | } 166 | 167 | let expected = "query { comments(first: 2, after: \"123\") { id author content } }" 168 | XCTAssertEqual(String(describing: query), expected) 169 | } 170 | 171 | func testBuildObjectWithSliceWithEdges() { 172 | let query = Weave(.query) { 173 | Object(Post.CodingKeys.comments) { 174 | Field(Comment.CodingKeys.id) 175 | Field(Comment.CodingKeys.author) 176 | Field(Comment.CodingKeys.content) 177 | } 178 | .slice(amount: 2) 179 | .paginationType(.cursor) 180 | } 181 | 182 | let expected = "query { comments(first: 2) { edges { cursor node { id author content } } } }" 183 | XCTAssertEqual(String(describing: query), expected) 184 | } 185 | 186 | func testBuildObjectWithNoSliceAndEdges() { 187 | let query = Weave(.query) { 188 | Object(Post.CodingKeys.comments) { 189 | Field(Comment.CodingKeys.id) 190 | Field(Comment.CodingKeys.author) 191 | Field(Comment.CodingKeys.content) 192 | } 193 | .paginationType(.cursor) 194 | } 195 | 196 | let expected = "query { comments { id author content } }" 197 | XCTAssertEqual(String(describing: query), expected) 198 | } 199 | 200 | func testBuildObjectWithEdgesAndPageInfo() { 201 | let query = Weave(.query) { 202 | Object(Post.CodingKeys.comments) { 203 | Field(Comment.CodingKeys.id) 204 | Field(Comment.CodingKeys.author) 205 | Field(Comment.CodingKeys.content) 206 | } 207 | .slice(amount: 2) 208 | .paginationType(.cursor) 209 | .pageInfo(type: PageInfo.self, 210 | keys: PageInfo.CodingKeys.startCursor, 211 | PageInfo.CodingKeys.endCursor, 212 | PageInfo.CodingKeys.hasNextPage) 213 | } 214 | 215 | let expected = "query { comments(first: 2) { edges { cursor node { id author content } } pageInfo { startCursor endCursor hasNextPage } } }" 216 | XCTAssertEqual(String(describing: query), expected) 217 | } 218 | 219 | func testBuildObjectWithEdgesAndPageInfoStrings() { 220 | let query = Weave(.query) { 221 | Object(Post.CodingKeys.comments) { 222 | Field(Comment.CodingKeys.id) 223 | Field(Comment.CodingKeys.author) 224 | Field(Comment.CodingKeys.content) 225 | } 226 | .slice(amount: 2) 227 | .paginationType(.cursor) 228 | .pageInfo(name: "pageInfo", 229 | keys: "startCursor", 230 | "endCursor", 231 | "hasNextPage") 232 | } 233 | 234 | let expected = "query { comments(first: 2) { edges { cursor node { id author content } } pageInfo { startCursor endCursor hasNextPage } } }" 235 | XCTAssertEqual(String(describing: query), expected) 236 | } 237 | 238 | func testForEachWeavable() { 239 | let authors = [ 240 | Author(id: "1", name: "John", age: 17, birthplace: [:]), 241 | Author(id: "2", name: "Jane", age: 29, birthplace: [:]), 242 | Author(id: "3", name: "Adam", age: 41, birthplace: [:]) 243 | ] 244 | 245 | let query = Weave(.query) { 246 | ForEachWeavable(authors) { author in 247 | Object("postsForAuthor") { 248 | Field(Author.CodingKeys.id) 249 | Field(Author.CodingKeys.name) 250 | Field(Author.CodingKeys.age) 251 | Field(Author.CodingKeys.birthplace) 252 | } 253 | .argument(key: "id", value: author.id) 254 | } 255 | } 256 | 257 | let expected = "query { postsForAuthor(id: \"1\") { id name age birthplace } postsForAuthor(id: \"2\") { id name age birthplace } postsForAuthor(id: \"3\") { id name age birthplace } }" 258 | XCTAssertEqual(String(describing: query), expected) 259 | } 260 | 261 | static var allTests = [ 262 | ("testBuildField", testBuildField), 263 | ("testBuildObject", testBuildObject), 264 | ("testBuildObjectWithAlias", testBuildObjectWithAlias), 265 | ("testBuildObjectWithSchemaName", testBuildObjectWithSchemaName), 266 | ("testBuildObjectWithArgument", testBuildObjectWithArgument), 267 | ("testBuildObjectWithNilArgument", testBuildObjectWithNilArgument), 268 | ("testBuildObjectWithCaseStyle", testBuildObjectWithCaseStyle), 269 | ("testBuildObjectWithRemove", testBuildObjectWithFalseInclude), 270 | ("testBuildObjectWithTrueSkip", testBuildObjectWithTrueSkip), 271 | ("testBuildObjectWithSlice", testBuildObjectWithSlice), 272 | ("testBuildObjectWithSliceAndOffset", testBuildObjectWithSliceAndOffset), 273 | ("testBuildObjectWithNoSliceAndEdges", testBuildObjectWithNoSliceAndEdges), 274 | ("testBuildObjectWithEdgesAndPageInfo", testBuildObjectWithEdgesAndPageInfo), 275 | ("testBuildObjectWithEdgesAndPageInfoStrings", testBuildObjectWithEdgesAndPageInfoStrings), 276 | ] 277 | } 278 | 279 | -------------------------------------------------------------------------------- /Tests/SociableWeaverTests/SociableWeaverGeneralTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import SociableWeaver 3 | 4 | final class SociableWeaverGeneralTests: XCTestCase { 5 | func testOperationWithArguments() { 6 | let query = Weave(.query) { 7 | Object(Post.self) { 8 | Object(Post.CodingKeys.author) { 9 | Field(Author.CodingKeys.id) 10 | Field(Author.CodingKeys.name) 11 | .argument(key: "value", value: "AuthorName") 12 | Field(Author.CodingKeys.birthplace) 13 | .argument(key: "value", value: [ 14 | "city": "New York", 15 | "state": "New York", 16 | "postalCode": "10001", 17 | "neighborhood": "Chelsea" 18 | ]) 19 | } 20 | .alias("newAuthor") 21 | .argument(key: "id", value: 1) 22 | 23 | Object(Post.CodingKeys.comments) { 24 | Field(Comment.CodingKeys.id) 25 | Field(Comment.CodingKeys.content) 26 | Field(Comment.CodingKeys.createdAt) 27 | } 28 | .alias("newComments") 29 | } 30 | } 31 | 32 | let expected = "query { post { newAuthor: author(id: 1) { id name(value: \"AuthorName\") birthplace(value: {city: \"New York\", neighborhood: \"Chelsea\", postalCode: \"10001\", state: \"New York\"}) } newComments: comments { id content createdAt } } }" 33 | XCTAssertEqual(String(describing: query), expected) 34 | } 35 | 36 | func testOperationWithFragment() { 37 | let authorFragment = FragmentBuilder(name: "authorFields", type: Author.self) 38 | let query = Weave(.query) { 39 | Object(Post.self) { 40 | Field(Post.CodingKeys.title) 41 | Field(Post.CodingKeys.content) 42 | 43 | Object(Post.CodingKeys.author) { 44 | FragmentReference(for: authorFragment) 45 | } 46 | 47 | Object(Post.CodingKeys.comments) { 48 | Field(Comment.CodingKeys.id) 49 | Field(Comment.CodingKeys.content) 50 | 51 | Object(Comment.CodingKeys.author) { 52 | FragmentReference(for: authorFragment) 53 | } 54 | } 55 | } 56 | 57 | Fragment(authorFragment) { 58 | Field(Author.CodingKeys.id) 59 | Field(Author.CodingKeys.name) 60 | } 61 | } 62 | 63 | let expected = "query { post { title content author { ...authorFields } comments { id content author { ...authorFields } } } } fragment authorFields on Author { id name }" 64 | XCTAssertEqual(String(describing: query), expected) 65 | } 66 | 67 | func testOperationWithInlineFragment() { 68 | let query = Weave(.query) { 69 | Object(Post.self) { 70 | Field(Post.CodingKeys.title) 71 | Field(Post.CodingKeys.content) 72 | 73 | Object(Post.CodingKeys.author) { 74 | Field(Author.CodingKeys.id) 75 | Field(Author.CodingKeys.name) 76 | } 77 | 78 | Object(Post.CodingKeys.comments) { 79 | Field(Comment.CodingKeys.id) 80 | Field(Comment.CodingKeys.content) 81 | 82 | Object(Comment.CodingKeys.author) { 83 | InlineFragment("AnonymousUser") { 84 | Field(Author.CodingKeys.id) 85 | } 86 | 87 | InlineFragment("RegisteredUser") { 88 | Field(Author.CodingKeys.id) 89 | Field(Author.CodingKeys.name) 90 | } 91 | } 92 | } 93 | } 94 | } 95 | 96 | let expected = "query { post { title content author { id name } comments { id content author { ... on AnonymousUser { id } ... on RegisteredUser { id name } } } } }" 97 | XCTAssertEqual(String(describing: query), expected) 98 | } 99 | 100 | func testOperationWithDirectives() { 101 | let query = Weave(.query) { 102 | Object(Post.self) { 103 | Field(Post.CodingKeys.title) 104 | Field(Post.CodingKeys.content) 105 | .include(if: true) 106 | 107 | Object(Post.CodingKeys.author) { 108 | Field(Author.CodingKeys.name) 109 | } 110 | .include(if: false) 111 | 112 | Object(Post.CodingKeys.comments) { 113 | Field(Comment.CodingKeys.content) 114 | .include(if: true) 115 | .skip(if: true) 116 | 117 | Object(Comment.CodingKeys.author) { 118 | Field(Author.CodingKeys.name) 119 | .skip(if: true) 120 | } 121 | } 122 | } 123 | } 124 | 125 | let expected = "query { post { title content } }" 126 | XCTAssertEqual(String(describing: query), expected) 127 | } 128 | 129 | func testOperationWithMetaField() { 130 | let query = Weave(.query) { 131 | Object(Post.self) { 132 | Field(Post.CodingKeys.title) 133 | Field(Post.CodingKeys.content) 134 | 135 | Object(Post.CodingKeys.author) { 136 | MetaField(.typename) 137 | Field(Author.CodingKeys.name) 138 | } 139 | } 140 | } 141 | 142 | let expected = "query { post { title content author { __typename name } } }" 143 | XCTAssertEqual(String(describing: query), expected) 144 | } 145 | 146 | func testOperationWithSchemaName() { 147 | let query = Weave(.query) { 148 | Object(Post.self) { 149 | Field(Post.CodingKeys.title) 150 | Field(Post.CodingKeys.content) 151 | 152 | Object(Post.CodingKeys.author) { 153 | Field(Author.CodingKeys.id) 154 | Field(Author.CodingKeys.name) 155 | } 156 | } 157 | .schemaName("getFirstPost") 158 | } 159 | 160 | let expected = "query { getFirstPost { title content author { id name } } }" 161 | XCTAssertEqual(String(describing: query), expected) 162 | } 163 | 164 | func testOperationWithCustomEnum() { 165 | enum PostCategories: String, EnumValueRepresentable { 166 | case art 167 | case music 168 | case technology 169 | } 170 | 171 | let query = Weave(.query) { 172 | Object(Post.self) { 173 | Field(Post.CodingKeys.title) 174 | Field(Post.CodingKeys.content) 175 | 176 | Object(Post.CodingKeys.author) { 177 | Field(Author.CodingKeys.id) 178 | Field(Author.CodingKeys.name) 179 | } 180 | } 181 | .argument(key: "category", value: PostCategories.technology) 182 | } 183 | 184 | let expected = "query { post(category: TECHNOLOGY) { title content author { id name } } }" 185 | XCTAssertEqual(String(describing: query), expected) 186 | } 187 | 188 | func testOperationWithCustomEnumArray() { 189 | enum PostCategories: String, EnumValueRepresentable { 190 | case art 191 | case music 192 | case technology 193 | } 194 | 195 | let query = Weave(.query) { 196 | Object(Post.self) { 197 | Field(Post.CodingKeys.title) 198 | Field(Post.CodingKeys.content) 199 | 200 | Object(Post.CodingKeys.author) { 201 | Field(Author.CodingKeys.id) 202 | Field(Author.CodingKeys.name) 203 | } 204 | } 205 | .argument(key: "category", value: [PostCategories.art, PostCategories.music, PostCategories.technology]) 206 | } 207 | 208 | let expected = "query { post(category: [ART, MUSIC, TECHNOLOGY]) { title content author { id name } } }" 209 | XCTAssertEqual(String(describing: query), expected) 210 | } 211 | 212 | static var allTests = [ 213 | ("testOperationWithArguments", testOperationWithArguments), 214 | ("testOperationWithFragment", testOperationWithFragment), 215 | ("testOperationWithInlineFragment", testOperationWithInlineFragment), 216 | ("testOperationWithDirectives", testOperationWithDirectives), 217 | ("testOperationWithMetaField", testOperationWithMetaField), 218 | ("testOperationWithSchemaName", testOperationWithSchemaName), 219 | ("testOperationWithCustomEnum", testOperationWithCustomEnum) 220 | ] 221 | } 222 | -------------------------------------------------------------------------------- /Tests/SociableWeaverTests/SociableWeaverMutationTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import SociableWeaver 3 | 4 | final class SociableWeaverMutationTests: XCTestCase { 5 | func testBasicMutationOperation() { 6 | let query = Weave(.mutation) { 7 | Object(Post.self) { 8 | Field(Post.CodingKeys.title) 9 | Field(Post.CodingKeys.content) 10 | 11 | Object(Post.CodingKeys.author) { 12 | Field(Author.CodingKeys.name) 13 | } 14 | } 15 | .schemaName("updatePost") 16 | .argument(key: "id", value: 1) 17 | .argument(key: "title", value: "Updated Title") 18 | } 19 | 20 | let expected = "mutation { updatePost(id: 1, title: \"Updated Title\") { title content author { name } } }" 21 | XCTAssertEqual(String(describing: query), expected) 22 | } 23 | 24 | func testMutationOperationWithName() { 25 | let query = Weave(.mutation) { 26 | Object(Post.self) { 27 | Field(Post.CodingKeys.title) 28 | Field(Post.CodingKeys.content) 29 | 30 | Object(Post.CodingKeys.author) { 31 | Field(Author.CodingKeys.name) 32 | } 33 | } 34 | .schemaName("updatePost") 35 | .argument(key: "id", value: 1) 36 | .argument(key: "title", value: "Updated Title") 37 | } 38 | .name("UpdatePost") 39 | 40 | let expected = "mutation UpdatePost { updatePost(id: 1, title: \"Updated Title\") { title content author { name } } }" 41 | XCTAssertEqual(String(describing: query), expected) 42 | } 43 | 44 | static var allTests = [ 45 | ("testBasicMutationOperation", testBasicMutationOperation), 46 | ("testMutationOperationWithName", testMutationOperationWithName), 47 | ] 48 | } 49 | 50 | -------------------------------------------------------------------------------- /Tests/SociableWeaverTests/SociableWeaverQueryTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import SociableWeaver 3 | 4 | final class SociableWeaverQueryTests: XCTestCase { 5 | func testBasicQueryOperation() { 6 | let query = Weave(.query) { 7 | Object(Post.self) { 8 | Field(Post.CodingKeys.title) 9 | Field(Post.CodingKeys.content) 10 | 11 | Object(Post.CodingKeys.author) { 12 | Field(Author.CodingKeys.name) 13 | } 14 | 15 | Object(Post.CodingKeys.comments) { 16 | Field(Comment.CodingKeys.content) 17 | 18 | Object(Comment.CodingKeys.author) { 19 | Field(Author.CodingKeys.name) 20 | } 21 | } 22 | } 23 | } 24 | 25 | let expected = "query { post { title content author { name } comments { content author { name } } } }" 26 | XCTAssertEqual(String(describing: query), expected) 27 | } 28 | 29 | func testQueryOperationWithName() { 30 | let query = Weave(.query) { 31 | Object(Post.self) { 32 | Field(Post.CodingKeys.title) 33 | Field(Post.CodingKeys.content) 34 | 35 | Object(Post.CodingKeys.author) { 36 | Field(Author.CodingKeys.name) 37 | } 38 | 39 | Object(Post.CodingKeys.comments) { 40 | Field(Comment.CodingKeys.content) 41 | 42 | Object(Comment.CodingKeys.author) { 43 | Field(Author.CodingKeys.name) 44 | } 45 | } 46 | } 47 | } 48 | .name("GetPost") 49 | 50 | let expected = "query GetPost { post { title content author { name } comments { content author { name } } } }" 51 | XCTAssertEqual(String(describing: query), expected) 52 | } 53 | 54 | static var allTests = [ 55 | ("testBasicQueryOperation", testBasicQueryOperation), 56 | ("testQueryOperationWithName", testQueryOperationWithName), 57 | ] 58 | } 59 | -------------------------------------------------------------------------------- /Tests/SociableWeaverTests/SociableWeaverSingleComponentTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import SociableWeaver 3 | 4 | final class SociableWeaverSingleComponentTests: XCTestCase { 5 | func testWeaveSingleComponent() { 6 | let query = Weave(.query) { 7 | Object(Post.self) { 8 | Field(Post.CodingKeys.id) 9 | Field(Post.CodingKeys.content) 10 | } 11 | } 12 | 13 | let expected = "query { post { id content } }" 14 | XCTAssertEqual(String(describing: query), expected) 15 | } 16 | 17 | func testObjectSingleComponent() { 18 | let query = Weave(.query) { 19 | Object(Post.self) { 20 | Field(Post.CodingKeys.id) 21 | } 22 | } 23 | 24 | let expected = "query { post { id } }" 25 | XCTAssertEqual(String(describing: query), expected) 26 | } 27 | 28 | func testFragmentSingleComponent() { 29 | let authorFragment = FragmentBuilder(name: "authorFields", type: Author.self) 30 | let query = Weave(.query) { 31 | Object(Post.self) { 32 | Field(Post.CodingKeys.title) 33 | 34 | Object(Post.CodingKeys.author) { 35 | FragmentReference(for: authorFragment) 36 | } 37 | } 38 | 39 | Fragment(authorFragment) { 40 | Field(Author.CodingKeys.id) 41 | } 42 | } 43 | 44 | let expected = "query { post { title author { ...authorFields } } } fragment authorFields on Author { id }" 45 | XCTAssertEqual(String(describing: query), expected) 46 | } 47 | 48 | func testInlineFragmentSingleComponent() { 49 | let query = Weave(.query) { 50 | Object(Post.self) { 51 | Field(Post.CodingKeys.title) 52 | 53 | Object(Post.CodingKeys.comments) { 54 | Field(Comment.CodingKeys.id) 55 | Field(Comment.CodingKeys.content) 56 | 57 | Object(Comment.CodingKeys.author) { 58 | InlineFragment("AnonymousUser") { 59 | Field(Author.CodingKeys.id) 60 | } 61 | } 62 | } 63 | } 64 | } 65 | 66 | let expected = "query { post { title comments { id content author { ... on AnonymousUser { id } } } } }" 67 | XCTAssertEqual(String(describing: query), expected) 68 | } 69 | 70 | static var allTests = [ 71 | ("testWeaveSingleComponent", testWeaveSingleComponent), 72 | ("testObjectSingleComponent", testObjectSingleComponent), 73 | ("testFragmentSingleComponent", testFragmentSingleComponent), 74 | ("testInlineFragmentSingleComponent", testInlineFragmentSingleComponent), 75 | ] 76 | } 77 | -------------------------------------------------------------------------------- /Tests/SociableWeaverTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !canImport(ObjectiveC) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | return [ 6 | testCase(SociableWeaverGeneralTests.allTests), 7 | testCase(SociableWeaverQueryTests.allTests), 8 | testCase(SociableWeaverMutationTests.allTests), 9 | ] 10 | } 11 | #endif 12 | --------------------------------------------------------------------------------