├── .gitignore ├── .swift-version ├── .travis.yml ├── Images ├── Console.png ├── GraphiQL.png ├── Paw.png └── Schemes.png ├── LICENSE ├── Package.swift ├── README.md └── Sources ├── Blog ├── BlogData.swift ├── BlogSchema.swift └── main.swift └── StarWars ├── StarWarsData.swift ├── StarWarsSchema.swift └── main.swift /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 3.0 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | notifications: 2 | slack: zewo:VjyVCCQvTOw9yrbzQysZezD1 3 | os: 4 | - linux 5 | - osx 6 | language: generic 7 | sudo: required 8 | dist: trusty 9 | osx_image: xcode8 10 | install: 11 | - eval "$(curl -sL https://raw.githubusercontent.com/Zewo/Zewo/master/Scripts/Travis/install.sh)" 12 | script: 13 | - swift build 14 | - swift build -c release 15 | -------------------------------------------------------------------------------- /Images/Console.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZewoGraveyard/GraphQLExamples/ff0924f9825664e13822e39023acdd7b5f8c3712/Images/Console.png -------------------------------------------------------------------------------- /Images/GraphiQL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZewoGraveyard/GraphQLExamples/ff0924f9825664e13822e39023acdd7b5f8c3712/Images/GraphiQL.png -------------------------------------------------------------------------------- /Images/Paw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZewoGraveyard/GraphQLExamples/ff0924f9825664e13822e39023acdd7b5f8c3712/Images/Paw.png -------------------------------------------------------------------------------- /Images/Schemes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZewoGraveyard/GraphQLExamples/ff0924f9825664e13822e39023acdd7b5f8c3712/Images/Schemes.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Zewo 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. -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | import PackageDescription 2 | 3 | let package = Package( 4 | name: "GraphQLExamples", 5 | dependencies: [ 6 | .Package(url: "https://github.com/Zewo/HTTPServer.git", majorVersion: 0, minor: 14), 7 | .Package(url: "https://github.com/Zewo/GraphQLResponder.git", majorVersion: 0, minor: 14), 8 | ] 9 | ) 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GraphQL Examples 2 | 3 | [![Swift][swift-badge]][swift-url] 4 | [![License][mit-badge]][mit-url] 5 | [![Slack][slack-badge]][slack-url] 6 | [![Travis][travis-badge]][travis-url] 7 | 8 | ## Instructions 9 | 10 | ```sh 11 | git clone https://github.com/Zewo/GraphQLExamples 12 | swift package generate-xcodeproj 13 | open GraphQLExamples.xcodeproj 14 | ``` 15 | 16 | Choose an application scheme from the drop down menu: 17 | 18 | ![App Scheme](Images/Schemes.png) 19 | 20 | Click the run button ► or use the shortcut ⌘R. You should see the server running directly from your Xcode. 21 | 22 | ![App Scheme](Images/Console.png) 23 | 24 | Open your browser and go to `http://localhost:8080/graphql` 25 | 26 | ![App Scheme](Images/GraphiQL.png) 27 | 28 | You'll see [GraphiQL](https://github.com/graphql/graphiql). An in-browser IDE for exploring GraphQL. **GraphiQL** provides a text editor with auto-completion based on the apps schema. If you click `Docs` on the top right corner you'll be able to navigate the Documentation Explorer. Click the run button or ⌘Return to execute the request. 29 | 30 | You can use any HTTP client to send GraphQL requests. 31 | 32 | ![Paw](Images/Paw.png) 33 | 34 | The example above sends a POST HTTP request with the GraphQL request inside the `query` JSON object key. 35 | 36 | For more information take a look at: 37 | 38 | - [GraphQLResponder](https://github.com/Zewo/GraphQLResponder) 39 | - [Graphiti](https://github.com/GraphQLSwift/Graphiti) 40 | 41 | ## Support 42 | 43 | If you need any help you can join our [Slack](http://slack.zewo.io) and go to the **#help** channel. Or you can create a Github [issue](https://github.com/Zewo/Zewo/issues/new) in our main repository. When stating your issue be sure to add enough details, specify what module is causing the problem and reproduction steps. 44 | 45 | ## Community 46 | 47 | [![Slack][slack-image]][slack-url] 48 | 49 | The entire Zewo code base is licensed under MIT. By contributing to Zewo you are contributing to an open and engaged community of brilliant Swift programmers. Join us on [Slack](http://slack.zewo.io) to get to know us! 50 | 51 | ## License 52 | 53 | This project is released under the MIT license. See [LICENSE](LICENSE) for details. 54 | 55 | [swift-badge]: https://img.shields.io/badge/Swift-3.0-orange.svg?style=flat 56 | [swift-url]: https://swift.org 57 | [mit-badge]: https://img.shields.io/badge/License-MIT-blue.svg?style=flat 58 | [mit-url]: https://tldrlegal.com/license/mit-license 59 | [slack-image]: http://s13.postimg.org/ybwy92ktf/Slack.png 60 | [slack-badge]: https://zewo-slackin.herokuapp.com/badge.svg 61 | [slack-url]: http://slack.zewo.io 62 | [travis-badge]: https://travis-ci.org/Zewo/GraphQLExamples.svg?branch=master 63 | [travis-url]: https://travis-ci.org/Zewo/GraphQLExamples 64 | -------------------------------------------------------------------------------- /Sources/Blog/BlogData.swift: -------------------------------------------------------------------------------- 1 | // Translated from https://github.com/kadirahq/graphql-blog-schema 2 | 3 | import struct Foundation.Date 4 | import struct Foundation.UUID 5 | 6 | enum Category : String { 7 | case server = "server-side-swift" 8 | case optimization = "optimization" 9 | case release = "release" 10 | } 11 | 12 | struct Author { 13 | let id: String 14 | let name: String 15 | let twitterHandle: String? 16 | } 17 | 18 | var AuthorsMap: [String: Author] = [ 19 | "seabaylea": Author( 20 | id: "seabaylea", 21 | name: "Chris Bailey", 22 | twitterHandle: "@Chris__Bailey" 23 | ), 24 | "eeckstein": Author( 25 | id: "eeckstein", 26 | name: "Erik Eckstein", 27 | twitterHandle: "@eeckstein" 28 | ), 29 | "tkremenek": Author( 30 | id: "tkremenek", 31 | name: "Ted Kremenek", 32 | twitterHandle: "@tkremenek" 33 | ) 34 | ] 35 | 36 | struct Comment { 37 | let id: String 38 | let content: String 39 | let author: String 40 | } 41 | 42 | let CommentList: [Comment] = [ 43 | Comment( 44 | id: UUID().uuidString, 45 | content: "This is a very good blog post", 46 | author: "tkremenek" 47 | ), 48 | Comment( 49 | id: UUID().uuidString, 50 | content: "Keep up the good work", 51 | author: "seabaylea" 52 | ) 53 | ] 54 | 55 | let ReplyList: [Comment] = [ 56 | Comment( 57 | id: UUID().uuidString, 58 | content: "Thank You!", 59 | author: "eeckstein" 60 | ), 61 | Comment( 62 | id: UUID().uuidString, 63 | content: "If you need more information, just contact me.", 64 | author: "eeckstein" 65 | ), 66 | ] 67 | 68 | struct Post { 69 | let id: String 70 | let author: String 71 | let category: Category? 72 | let content: String 73 | let date: Date 74 | let summary: String 75 | let title: String 76 | } 77 | 78 | var PostsList = [ 79 | Post( 80 | id: "0176413761b289e6d64c2c14a758c1c7", 81 | author: "seabaylea", 82 | category: .server, 83 | content: "Since Swift became available on Linux there has been a huge amount of interest in using Swift on the server, resulting in the emergence of a number of Web Frameworks, including Kitura, Vapor, Perfect, and Zewo, along with many others. As an important part of the Swift ecosystem, and one that we are keen to foster, we are today announcing the formation of the Server APIs work group.\n\nThe work group provides the framework for participants in the the community with an interest in building server applications and frameworks to come together to work on providing new Swift APIs. These APIs will provide low level “server” functions as the basic building blocks for developing server-side capabilities, removing the reliance on interfacing with generally platform specific C libraries for these functions. This will enable more developers to create frameworks and server applications using pure-Swift code, without the need to also have systems programming skills and knowledge of multiple platforms.\n\nThe work group will initially be looking at APIs for networking, security, and HTTP/WebSocket parsing, with the goal of making it possible for anyone to build a simple, secure, HTTP server, or to start to build other server frameworks like pub/sub message brokers.\n\nFor more information, take a look at the Server APIs project page.", 84 | date: Date(), 85 | summary: "Since Swift became available on Linux there has been a huge amount of interest in using Swift on the server...", 86 | title: "Server APIs Work Group" 87 | ), 88 | Post( 89 | id: "03390abb5570ce03ae524397d215713b", 90 | author: "eeckstein", 91 | category: .optimization, 92 | content: "Whole-module optimization is an optimization mode of the Swift compiler. The performance win of whole-module optimization heavily depends on the project, but it can be up to two or even five times.\n\nWhole-module optimization can be enabled with the -whole-module-optimization (or -wmo) compiler flag, and in Xcode 8 it is turned on by default for new projects. Also the Swift Package Manager compiles with whole-module optimizations in release builds.\n\nSo what is it about? Let’s first look at how the compiler works without whole-module optimizations.", 93 | date: Date(), 94 | summary: "Whole-module optimization is an optimization mode of the Swift compiler. The performance win of whole-module optimization heavily depends on the project, but it can be up to two or even five times...", 95 | title: "Whole-Module Optimization in Swift 3" 96 | ), 97 | Post( 98 | id: "0be4bea0330ccb5ecf781a9f69a64bc8", 99 | author: "tkremenek", 100 | category: .release, 101 | content: "Swift 3.0, the first major release of Swift since it was open-sourced, is now officially released! Swift 3 is a huge release containing major improvements and refinements to the core language and Standard Library, major additions to the Linux port of Swift, and the first official release of the Swift Package Manager.", 102 | date: Date(), 103 | summary: "Swift 3.0, the first major release of Swift since it was open-sourced, is now officially released!..", 104 | title: "Swift 3.0 Released!" 105 | ), 106 | ] 107 | -------------------------------------------------------------------------------- /Sources/Blog/BlogSchema.swift: -------------------------------------------------------------------------------- 1 | // Translated from https://github.com/kadirahq/graphql-blog-schema 2 | 3 | import Graphiti 4 | import struct Foundation.Date 5 | 6 | extension Category : InputType, OutputType { 7 | init(map: Map) throws { 8 | guard 9 | let name = map.string, 10 | let category = Category(rawValue: name) 11 | else { 12 | throw MapError.incompatibleType 13 | } 14 | 15 | self = category 16 | } 17 | 18 | func asMap() throws -> Map { 19 | return rawValue.map 20 | } 21 | } 22 | 23 | extension Author : OutputType {} 24 | extension Comment : OutputType {} 25 | extension Post : OutputType {} 26 | 27 | protocol HasAuthor { 28 | var author: String { get } 29 | } 30 | 31 | extension Comment : HasAuthor {} 32 | extension Post : HasAuthor {} 33 | 34 | enum BlogError : Error { 35 | case postAlreadyExists(id: String) 36 | case noSuchAuthor(author: String) 37 | case authorAlreadyExists(id: String) 38 | } 39 | 40 | extension BlogError : CustomStringConvertible { 41 | var description: String { 42 | switch self { 43 | case .postAlreadyExists(let id): 44 | return "Post already exists: \(id)" 45 | case .noSuchAuthor(let author): 46 | return "No such author: \(author)" 47 | case .authorAlreadyExists(let id): 48 | return "Author already exists: \(id)" 49 | } 50 | } 51 | } 52 | 53 | let blogSchema = try! Schema { schema in 54 | try EnumType(name: "Category") { category in 55 | category.description = "A Category of the blog" 56 | 57 | try category.value(name: "SERVER", value: .server) 58 | try category.value(name: "OPTIMIZATION", value: .optimization) 59 | try category.value(name: "RELEASE", value: .release) 60 | } 61 | 62 | try ObjectType(name: "Author") { author in 63 | author.description = "Represent the type of an author of a blog post or a comment" 64 | 65 | try author.field(name: "id", type: String.self) 66 | try author.field(name: "name", type: String.self) 67 | try author.field(name: "twitterHandle", type: (String?).self) 68 | } 69 | 70 | try InterfaceType(name: "HasAuthor") { author in 71 | author.description = "This type has an author" 72 | 73 | try author.field(name: "author", type: Author.self) 74 | 75 | author.resolveType { value, _, _ in 76 | switch value { 77 | case is Post: 78 | return Post.self 79 | default: 80 | return Comment.self 81 | } 82 | } 83 | } 84 | 85 | try ObjectType(name: "Comment", interfaces: HasAuthor.self) { comment in 86 | comment.description = "Represent the type of a comment" 87 | 88 | try comment.field(name: "id", type: String.self) 89 | try comment.field(name: "content", type: String.self) 90 | 91 | try comment.field(name: "author", type: Author.self) { comment, _, _, _ in 92 | guard let author = AuthorsMap[comment.author] else { 93 | throw BlogError.noSuchAuthor(author: comment.author) 94 | } 95 | 96 | return author 97 | } 98 | 99 | try comment.field(name: "timestamp", type: (Double?).self) 100 | 101 | try comment.field( 102 | name: "replies", 103 | type: [TypeReference].self, 104 | description: "Replies for the comment" 105 | ) { _ in 106 | ReplyList 107 | } 108 | } 109 | 110 | try ObjectType(name: "Post", interfaces: HasAuthor.self) { post in 111 | post.description = "Represent the type of a blog post" 112 | 113 | try post.field(name: "id", type: String.self) 114 | try post.field(name: "title", type: String.self) 115 | try post.field(name: "category", type: (Category?).self) 116 | try post.field(name: "summary", type: String.self) 117 | try post.field(name: "content", type: String.self) 118 | 119 | try post.field(name: "timestamp", type: Double.self) { post, _, _, _ in 120 | post.date.timeIntervalSince1970 121 | } 122 | 123 | struct LimitArgument : Argument { 124 | let value: Int 125 | static let defaultValue: DefaultValue? = nil 126 | static let description: String? = "Limit the comments returing" 127 | } 128 | 129 | struct CommentsArguments : Arguments { 130 | let limit: LimitArgument 131 | } 132 | 133 | try post.field(name: "comments", type: [Comment].self) { (_, arguments: CommentsArguments, _, _) in 134 | if arguments.limit.value >= 0 { 135 | return Array(CommentList.prefix(arguments.limit.value)) 136 | } 137 | 138 | return CommentList 139 | } 140 | 141 | try post.field(name: "author", type: Author.self) { post, _, _, _ in 142 | guard let author = AuthorsMap[post.author] else { 143 | throw BlogError.noSuchAuthor(author: post.author) 144 | } 145 | 146 | return author 147 | } 148 | } 149 | 150 | schema.query = try ObjectType(name: "BlogSchema") { query in 151 | query.description = "Root of the Blog Schema" 152 | 153 | struct PostsArguments : Arguments { 154 | let category: Category? 155 | } 156 | 157 | try query.field( 158 | name: "posts", 159 | type: [Post].self, 160 | description: "List of posts in the blog" 161 | ) { (_, arguments: PostsArguments, _, _) in 162 | if let category = arguments.category { 163 | return PostsList.filter({ $0.category == category }) 164 | } 165 | 166 | return PostsList 167 | } 168 | 169 | try query.field( 170 | name: "latestPost", 171 | type: (Post?).self, 172 | description: "Latest post in the blog" 173 | ) { _ in 174 | PostsList.sorted(by: { $0.date < $1.date }).first 175 | } 176 | 177 | struct PostCountArgument : Argument { 178 | let value: Int 179 | static let defaultValue: DefaultValue? = nil 180 | static let description: String? = "Number of recent items" 181 | } 182 | 183 | struct RecentPostsArguments : Arguments { 184 | let count: PostCountArgument 185 | } 186 | 187 | try query.field( 188 | name: "recentPosts", 189 | type: [Post].self, 190 | description: "Recent posts in the blog" 191 | ) { (_, arguments: RecentPostsArguments, _, _) in 192 | Array(PostsList.sorted(by: { $0.date < $1.date }).prefix(arguments.count.value)) 193 | } 194 | 195 | struct PostArguments : Arguments { 196 | let id: String 197 | } 198 | 199 | try query.field( 200 | name: "post", 201 | type: (Post?).self, 202 | description: "Post by id" 203 | ) { (_, arguments: PostArguments, _, _) in 204 | PostsList.filter({ $0.id == arguments.id }).first 205 | } 206 | 207 | try query.field( 208 | name: "authors", 209 | type: [Author].self, 210 | description: "Available authors in the blog" 211 | ) { _ in 212 | Array(AuthorsMap.values) 213 | } 214 | 215 | struct AuthorArguments : Arguments { 216 | let id: String 217 | } 218 | 219 | try query.field( 220 | name: "author", 221 | type: (Author?).self, 222 | description: "Author by id" 223 | ) { (_, arguments: AuthorArguments, _, _) in 224 | AuthorsMap[arguments.id] 225 | } 226 | } 227 | 228 | schema.mutation = try ObjectType(name: "BlogMutations") { mutation in 229 | struct AuthorArgument : Argument { 230 | let value: String 231 | static let defaultValue: DefaultValue? = nil 232 | static let description: String? = "Id of the author" 233 | } 234 | 235 | struct CreatePostArguments : Arguments { 236 | let id: String 237 | let title: String 238 | let content: String 239 | let summary: String? 240 | let category: Category? 241 | let author: AuthorArgument 242 | } 243 | 244 | try mutation.field( 245 | name: "createPost", 246 | type: (Post).self, 247 | description: "Create a new blog post" 248 | ) { (_, arguments: CreatePostArguments, _, _) in 249 | let post = Post( 250 | id: arguments.id, 251 | author: arguments.author.value, 252 | category: arguments.category, 253 | content: arguments.content, 254 | date: Date(), 255 | summary: arguments.summary ?? String(arguments.content.characters.prefix(100)), 256 | title: arguments.title 257 | ) 258 | 259 | let alreadyExists = PostsList.contains(where: { $0.id == post.id }) 260 | 261 | if alreadyExists { 262 | throw BlogError.postAlreadyExists(id: post.id) 263 | } 264 | 265 | if AuthorsMap[post.author] == nil { 266 | throw BlogError.noSuchAuthor(author: post.author) 267 | 268 | } 269 | 270 | PostsList.append(post) 271 | 272 | return post 273 | } 274 | 275 | struct CreateAuthorArguments : Arguments { 276 | let id: String 277 | let name: String 278 | let twitterHandle: String? 279 | } 280 | 281 | try mutation.field( 282 | name: "createAuthor", 283 | type: (Author).self, 284 | description: "Create a new author" 285 | ) { (_, arguments: CreateAuthorArguments, _, _) in 286 | let author = Author( 287 | id: arguments.id, 288 | name: arguments.name, 289 | twitterHandle: arguments.twitterHandle 290 | ) 291 | 292 | if AuthorsMap[author.id] != nil { 293 | throw BlogError.authorAlreadyExists(id: author.id) 294 | } 295 | 296 | AuthorsMap[author.id] = author 297 | 298 | return author 299 | } 300 | } 301 | } 302 | -------------------------------------------------------------------------------- /Sources/Blog/main.swift: -------------------------------------------------------------------------------- 1 | import HTTPServer 2 | import Graphiti 3 | import GraphQLResponder 4 | 5 | let graphql = GraphQLResponder(schema: blogSchema, graphiQL: true, rootValue: noRootValue) 6 | 7 | let router = BasicRouter { route in 8 | route.add(methods: [.get, .post], path: "/graphql", responder: graphql) 9 | } 10 | 11 | let contentNegotiation = ContentNegotiationMiddleware(mediaTypes: [.json]) 12 | let server = try Server(port: 8080, middleware: [contentNegotiation], responder: router) 13 | try server.start() 14 | -------------------------------------------------------------------------------- /Sources/StarWars/StarWarsData.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * This defines a basic set of data for our Star Wars Schema. 3 | * 4 | * This data is hard coded for the sake of the demo, but you could imagine 5 | * fetching this data from a backend service rather than from hardcoded 6 | * values in a more complex demo. 7 | */ 8 | 9 | enum Episode : String { 10 | case newHope = "NEWHOPE" 11 | case empire = "EMPIRE" 12 | case jedi = "JEDI" 13 | 14 | init?(_ string: String?) { 15 | guard let string = string else { 16 | return nil 17 | } 18 | 19 | self.init(rawValue: string) 20 | } 21 | } 22 | 23 | protocol Character { 24 | var id: String { get } 25 | var name: String { get } 26 | var friends: [String] { get } 27 | var appearsIn: [Episode] { get } 28 | } 29 | 30 | struct Human : Character { 31 | let id: String 32 | let name: String 33 | let friends: [String] 34 | let appearsIn: [Episode] 35 | let homePlanet: String? 36 | 37 | init(id: String, name: String, friends: [String], appearsIn: [Episode], homePlanet: String? = nil) { 38 | self.id = id 39 | self.name = name 40 | self.friends = friends 41 | self.appearsIn = appearsIn 42 | self.homePlanet = homePlanet 43 | } 44 | } 45 | 46 | struct Droid : Character { 47 | let id: String 48 | let name: String 49 | let friends: [String] 50 | let appearsIn: [Episode] 51 | let primaryFunction: String 52 | } 53 | 54 | let luke = Human( 55 | id: "1000", 56 | name: "Luke Skywalker", 57 | friends: ["1002", "1003", "2000", "2001"], 58 | appearsIn: [.newHope, .empire, .jedi], 59 | homePlanet: "Tatooine" 60 | ) 61 | 62 | let vader = Human( 63 | id: "1001", 64 | name: "Darth Vader", 65 | friends: [ "1004" ], 66 | appearsIn: [.newHope, .empire, .jedi], 67 | homePlanet: "Tatooine" 68 | ) 69 | 70 | let han = Human( 71 | id: "1002", 72 | name: "Han Solo", 73 | friends: ["1000", "1003", "2001"], 74 | appearsIn: [.newHope, .empire, .jedi] 75 | ) 76 | 77 | let leia = Human( 78 | id: "1003", 79 | name: "Leia Organa", 80 | friends: ["1000", "1002", "2000", "2001"], 81 | appearsIn: [.newHope, .empire, .jedi], 82 | homePlanet: "Alderaan" 83 | ) 84 | 85 | let tarkin = Human( 86 | id: "1004", 87 | name: "Wilhuff Tarkin", 88 | friends: ["1001"], 89 | appearsIn: [.newHope] 90 | ) 91 | 92 | let humanData: [String: Human] = [ 93 | "1000": luke, 94 | "1001": vader, 95 | "1002": han, 96 | "1003": leia, 97 | "1004": tarkin, 98 | ] 99 | 100 | let c3po = Droid( 101 | id: "2000", 102 | name: "C-3PO", 103 | friends: ["1000", "1002", "1003", "2001"], 104 | appearsIn: [.newHope, .empire, .jedi], 105 | primaryFunction: "Protocol" 106 | ) 107 | 108 | let r2d2 = Droid( 109 | id: "2001", 110 | name: "R2-D2", 111 | friends: [ "1000", "1002", "1003" ], 112 | appearsIn: [.newHope, .empire, .jedi], 113 | primaryFunction: "Astromech" 114 | ) 115 | 116 | let droidData: [String: Droid] = [ 117 | "2000": c3po, 118 | "2001": r2d2, 119 | ] 120 | 121 | /** 122 | * Helper function to get a character by ID. 123 | */ 124 | func getCharacter(id: String) -> Character? { 125 | return humanData[id] ?? droidData[id] 126 | } 127 | 128 | /** 129 | * Allows us to query for a character"s friends. 130 | */ 131 | func getFriends(character: Character) -> [Character] { 132 | return character.friends.reduce([]) { friends, friendID in 133 | var friends = friends 134 | guard let friend = getCharacter(id: friendID) else { 135 | return friends 136 | } 137 | friends.append(friend) 138 | return friends 139 | } 140 | } 141 | 142 | /** 143 | * Allows us to fetch the undisputed hero of the Star Wars trilogy, R2-D2. 144 | */ 145 | func getHero(episode: Episode?) -> Character { 146 | if episode == .empire { 147 | // Luke is the hero of Episode V. 148 | return luke 149 | } 150 | // R2-D2 is the hero otherwise. 151 | return r2d2 152 | } 153 | 154 | /** 155 | * Allows us to query for the human with the given id. 156 | */ 157 | func getHuman(id: String) -> Human? { 158 | return humanData[id] 159 | } 160 | 161 | /** 162 | * Allows us to query for the droid with the given id. 163 | */ 164 | func getDroid(id: String) -> Droid? { 165 | return droidData[id] 166 | } 167 | 168 | /** 169 | * Allows us to query for the droid with the given id. 170 | */ 171 | func getSecretBackStory() throws -> String? { 172 | struct Secret : Error, CustomStringConvertible { 173 | let description: String 174 | } 175 | 176 | throw Secret(description: "secretBackstory is secret.") 177 | } 178 | -------------------------------------------------------------------------------- /Sources/StarWars/StarWarsSchema.swift: -------------------------------------------------------------------------------- 1 | import Graphiti 2 | 3 | /** 4 | * This is designed to be an end-to-end test, demonstrating 5 | * the full GraphQL stack. 6 | * 7 | * We will create a GraphQL schema that describes the major 8 | * characters in the original Star Wars trilogy. 9 | * 10 | * NOTE: This may contain spoilers for the original Star 11 | * Wars trilogy. 12 | */ 13 | 14 | extension Episode : InputType, OutputType { 15 | init(map: Map) throws { 16 | guard 17 | let name = map.string, 18 | let episode = Episode(rawValue: name) 19 | else { 20 | throw MapError.incompatibleType 21 | } 22 | 23 | self = episode 24 | } 25 | 26 | func asMap() throws -> Map { 27 | return rawValue.map 28 | } 29 | } 30 | 31 | extension Human : OutputType {} 32 | extension Droid : OutputType {} 33 | 34 | /** 35 | * Using our shorthand to describe type systems, the type system for our 36 | * Star Wars example is: 37 | * 38 | * enum Episode { NEWHOPE, EMPIRE, JEDI } 39 | * 40 | * interface Character { 41 | * id: String! 42 | * name: String 43 | * friends: [Character] 44 | * appearsIn: [Episode] 45 | * secretBackstory: String 46 | * } 47 | * 48 | * type Human : Character { 49 | * id: String! 50 | * name: String 51 | * friends: [Character] 52 | * appearsIn: [Episode] 53 | * secretBackstory: String 54 | * homePlanet: String 55 | * } 56 | * 57 | * type Droid : Character { 58 | * id: String! 59 | * name: String 60 | * friends: [Character] 61 | * appearsIn: [Episode] 62 | * secretBackstory: String 63 | * primaryFunction: String 64 | * } 65 | * 66 | * type Query { 67 | * hero(episode: Episode): Character 68 | * human(id: String!): Human 69 | * droid(id: String!): Droid 70 | * } 71 | */ 72 | let starWarsSchema = try! Schema { schema in 73 | /** 74 | * The original trilogy consists of three movies. 75 | * 76 | * This implements the following type system shorthand: 77 | * 78 | * enum Episode { NEWHOPE, EMPIRE, JEDI } 79 | */ 80 | try EnumType { episode in 81 | episode.description = "One of the films in the Star Wars Trilogy" 82 | 83 | try episode.value( 84 | name: "NEWHOPE", 85 | value: .newHope, 86 | description: "Released in 1977." 87 | ) 88 | 89 | try episode.value( 90 | name: "EMPIRE", 91 | value: .empire, 92 | description: "Released in 1980." 93 | ) 94 | 95 | try episode.value( 96 | name: "JEDI", 97 | value: .jedi, 98 | description: "Released in 1983." 99 | ) 100 | } 101 | 102 | /** 103 | * Characters in the Star Wars trilogy are either humans or droids. 104 | * 105 | * This implements the following type system shorthand: 106 | * 107 | * interface Character { 108 | * id: String! 109 | * name: String 110 | * friends: [Character] 111 | * appearsIn: [Episode] 112 | * secretBackstory: String 113 | * } 114 | */ 115 | try InterfaceType { character in 116 | character.description = "A character in the Star Wars Trilogy" 117 | 118 | try character.field( 119 | name: "id", 120 | type: String.self, 121 | description: "The id of the character." 122 | ) 123 | 124 | try character.field( 125 | name: "name", 126 | type: (String?).self, 127 | description: "The name of the character." 128 | ) 129 | 130 | try character.field( 131 | name: "friends", 132 | type: [TypeReference?].self, 133 | description: "The friends of the character, or an empty list if they have none." 134 | ) 135 | 136 | try character.field( 137 | name: "appearsIn", 138 | type: [Episode?].self, 139 | description: "Which movies they appear in." 140 | ) 141 | 142 | try character.field( 143 | name: "secretBackstory", 144 | type: (String?).self, 145 | description: "All secrets about their past." 146 | ) 147 | 148 | character.resolveType { character, _, _ in 149 | switch character { 150 | case is Human: 151 | return Human.self 152 | default: 153 | return Droid.self 154 | } 155 | } 156 | } 157 | 158 | /** 159 | * We define our human type, which implements the character interface. 160 | * 161 | * This implements the following type system shorthand: 162 | * 163 | * type Human : Character { 164 | * id: String! 165 | * name: String 166 | * friends: [Character] 167 | * appearsIn: [Episode] 168 | * secretBackstory: String 169 | * homePlanet: String 170 | * } 171 | */ 172 | try ObjectType(interfaces: Character.self) { human in 173 | human.description = "A humanoid creature in the Star Wars universe." 174 | 175 | try human.field( 176 | name: "id", 177 | type: String.self, 178 | description: "The id of the human." 179 | ) 180 | 181 | try human.field( 182 | name: "name", 183 | type: (String?).self, 184 | description: "The name of the human." 185 | ) 186 | 187 | try human.field( 188 | name: "friends", 189 | type: [Character?].self, 190 | description: "The friends of the human, or an empty list if they have none.", 191 | resolve: { human, _, _, _ in 192 | getFriends(character: human) 193 | } 194 | ) 195 | 196 | try human.field( 197 | name: "appearsIn", 198 | type: [Episode?].self, 199 | description: "Which movies they appear in." 200 | ) 201 | 202 | try human.field( 203 | name: "homePlanet", 204 | type: (String?).self, 205 | description: "The home planet of the human, or null if unknown." 206 | ) 207 | 208 | try human.field( 209 | name: "secretBackstory", 210 | type: (String?).self, 211 | description: "Where are they from and how they came to be who they are.", 212 | resolve: { _ in 213 | try getSecretBackStory() 214 | } 215 | ) 216 | } 217 | 218 | /** 219 | * The other type of character in Star Wars is a droid. 220 | * 221 | * This implements the following type system shorthand: 222 | * 223 | * type Droid : Character { 224 | * id: String! 225 | * name: String 226 | * friends: [Character] 227 | * appearsIn: [Episode] 228 | * secretBackstory: String 229 | * primaryFunction: String 230 | * } 231 | */ 232 | try ObjectType(interfaces: Character.self) { droid in 233 | droid.description = "A mechanical creature in the Star Wars universe." 234 | 235 | try droid.field( 236 | name: "id", 237 | type: String.self, 238 | description: "The id of the droid." 239 | ) 240 | 241 | try droid.field( 242 | name: "name", 243 | type: (String?).self, 244 | description: "The name of the droid." 245 | ) 246 | 247 | try droid.field( 248 | name: "friends", 249 | type: [Character?].self, 250 | description: "The friends of the droid, or an empty list if they have none.", 251 | resolve: { droid, _, _, _ in 252 | getFriends(character: droid) 253 | } 254 | ) 255 | 256 | try droid.field( 257 | name: "appearsIn", 258 | type: [Episode?].self, 259 | description: "Which movies they appear in." 260 | ) 261 | 262 | try droid.field( 263 | name: "secretBackstory", 264 | type: (String?).self, 265 | description: "Where are they from and how they came to be who they are.", 266 | resolve: { _ in 267 | try getSecretBackStory() 268 | } 269 | ) 270 | 271 | try droid.field( 272 | name: "primaryFunction", 273 | type: (String?).self, 274 | description: "The primary function of the droid." 275 | ) 276 | } 277 | 278 | /** 279 | * This is the type that will be the root of our query, and the 280 | * entry point into our schema. It gives us the ability to fetch 281 | * objects by their IDs, as well as to fetch the undisputed hero 282 | * of the Star Wars trilogy, R2-D2, directly. 283 | * 284 | * This implements the following type system shorthand: 285 | * 286 | * type Query { 287 | * hero(episode: Episode): Character 288 | * human(id: String!): Human 289 | * droid(id: String!): Droid 290 | * } 291 | */ 292 | schema.query = try ObjectType(name: "Query") { query in 293 | struct EpisodeArgument : Argument { 294 | let value: Episode? 295 | static let defaultValue: DefaultValue? = nil 296 | static let description: String? = 297 | "If omitted, returns the hero of the whole saga. If " + 298 | "provided, returns the hero of that particular episode." 299 | } 300 | 301 | struct HeroArguments : Arguments { 302 | let episode: EpisodeArgument 303 | } 304 | 305 | try query.field(name: "hero") { (_, arguments: HeroArguments, _, _) in 306 | getHero(episode: arguments.episode.value) 307 | } 308 | 309 | struct HumanIDArgument : Argument { 310 | let value: String 311 | static let defaultValue: DefaultValue? = nil 312 | static let description: String? = "id of the human" 313 | } 314 | 315 | struct HumanArguments : Arguments { 316 | let id: HumanIDArgument 317 | } 318 | 319 | try query.field(name: "human") { (_, arguments: HumanArguments, _, _) in 320 | getHuman(id: arguments.id.value) 321 | } 322 | 323 | struct DroidIDArgument : Argument { 324 | let value: String 325 | static let defaultValue: DefaultValue? = nil 326 | static let description: String? = "id of the droid" 327 | } 328 | 329 | struct DroidArguments : Arguments { 330 | let id: DroidIDArgument 331 | } 332 | 333 | try query.field(name: "droid") { (_, arguments: DroidArguments, _, _) in 334 | getDroid(id: arguments.id.value) 335 | } 336 | } 337 | 338 | schema.types = [Human.self, Droid.self] 339 | } 340 | -------------------------------------------------------------------------------- /Sources/StarWars/main.swift: -------------------------------------------------------------------------------- 1 | import HTTPServer 2 | import Graphiti 3 | import GraphQLResponder 4 | 5 | let graphql = GraphQLResponder(schema: starWarsSchema, graphiQL: true, rootValue: noRootValue) 6 | 7 | let router = BasicRouter { route in 8 | route.add(methods: [.get, .post], path: "/graphql", responder: graphql) 9 | } 10 | 11 | let contentNegotiation = ContentNegotiationMiddleware(mediaTypes: [.json]) 12 | let server = try Server(port: 8080, middleware: [contentNegotiation], responder: router) 13 | try server.start() 14 | --------------------------------------------------------------------------------