├── .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 |
--------------------------------------------------------------------------------