Skip to content

Commit f5debf9

Browse files
Merge pull request #1 from NeedleInAJayStack/initial-implementation
2 parents ac1d39e + cb732e7 commit f5debf9

28 files changed

Lines changed: 3206 additions & 66 deletions

.github/workflows/test.yaml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
name: test
2+
on:
3+
push:
4+
branches: [ main ]
5+
pull_request:
6+
branches: [ main ]
7+
workflow_dispatch:
8+
jobs:
9+
lint:
10+
uses: graphqlswift/ci/.github/workflows/lint.yaml@main
11+
test:
12+
uses: graphqlswift/ci/.github/workflows/test.yaml@main
13+
with:
14+
include_android: false
15+
test-example:
16+
uses: graphqlswift/ci/.github/workflows/test.yaml@main
17+
with:
18+
package_path: "Examples/HelloWorldServer"
19+
include_android: false

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ DerivedData/
66
.swiftpm/configuration/registries.json
77
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
88
.netrc
9+
/.vscode
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
.DS_Store
2+
/.build
3+
/Packages
4+
xcuserdata/
5+
DerivedData/
6+
.swiftpm/configuration/registries.json
7+
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
8+
.netrc
9+
.vscode

Examples/HelloWorldServer/Package.resolved

Lines changed: 33 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// swift-tools-version: 6.0
2+
3+
import PackageDescription
4+
5+
let package = Package(
6+
name: "HelloWorldServer",
7+
platforms: [
8+
.macOS(.v13),
9+
],
10+
dependencies: [
11+
.package(name: "graphql-generator", path: "../.."),
12+
.package(url: "https://github.com/GraphQLSwift/GraphQL.git", from: "4.0.0"),
13+
],
14+
targets: [
15+
.target(
16+
name: "HelloWorldServer",
17+
dependencies: [
18+
.product(name: "GraphQL", package: "GraphQL"),
19+
.product(name: "GraphQLGeneratorRuntime", package: "graphql-generator"),
20+
],
21+
plugins: [
22+
.plugin(name: "GraphQLGeneratorPlugin", package: "graphql-generator"),
23+
]
24+
),
25+
.testTarget(
26+
name: "HelloWorldServerTests",
27+
dependencies: [
28+
"HelloWorldServer",
29+
.product(name: "GraphQL", package: "GraphQL"),
30+
]
31+
),
32+
]
33+
)
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
import Foundation
2+
import GraphQL
3+
import GraphQLGeneratorRuntime
4+
5+
// Must be created by user and named `Context`.
6+
public class Context: @unchecked Sendable {
7+
// User can choose structure
8+
var users: [String: User]
9+
var posts: [String: Post]
10+
var onTriggerWatch: () -> Void = {}
11+
12+
init(
13+
users: [String: User],
14+
posts: [String: Post]
15+
) {
16+
self.users = users
17+
self.posts = posts
18+
}
19+
20+
func triggerWatch() {
21+
onTriggerWatch()
22+
}
23+
}
24+
25+
// Scalars must be represented by a Swift type of the same name, conforming to the Scalar protocol
26+
public struct EmailAddress: Scalar {
27+
let email: String
28+
29+
init(email: String) {
30+
self.email = email
31+
}
32+
33+
// Codability conformance. Required for usage in InputObject
34+
public init(from decoder: any Decoder) throws {
35+
email = try decoder.singleValueContainer().decode(String.self)
36+
}
37+
38+
public func encode(to encoder: any Encoder) throws {
39+
try email.encode(to: encoder)
40+
}
41+
42+
// Scalar conformance. Not necessary, but default methods are very inefficient.
43+
public static func serialize(this: Self) throws -> Map {
44+
return .string(this.email)
45+
}
46+
47+
public static func parseValue(map: Map) throws -> Map {
48+
switch map {
49+
case .string:
50+
return map
51+
default:
52+
throw GraphQLError(message: "EmailAddress cannot represent non-string value: \(map)")
53+
}
54+
}
55+
56+
public static func parseLiteral(value: any Value) throws -> Map {
57+
guard let ast = value as? StringValue else {
58+
throw GraphQLError(
59+
message: "EmailAddress cannot represent non-string value: \(print(ast: value))",
60+
nodes: [value]
61+
)
62+
}
63+
return .string(ast.value)
64+
}
65+
}
66+
67+
// Now create types that conform to the expected protocols
68+
struct Resolvers: ResolversProtocol {
69+
typealias Query = HelloWorldServer.Query
70+
typealias Mutation = HelloWorldServer.Mutation
71+
typealias Subscription = HelloWorldServer.Subscription
72+
}
73+
74+
struct User: UserProtocol {
75+
// User can choose structure
76+
let id: String
77+
let name: String
78+
let email: String
79+
let age: Int?
80+
let role: Role?
81+
82+
// Required implementations
83+
func id(context _: Context, info _: GraphQL.GraphQLResolveInfo) async throws -> String {
84+
return id
85+
}
86+
87+
func name(context _: Context, info _: GraphQL.GraphQLResolveInfo) async throws -> String {
88+
return name
89+
}
90+
91+
func email(context _: Context, info _: GraphQL.GraphQLResolveInfo) async throws -> EmailAddress {
92+
return EmailAddress(email: email)
93+
}
94+
95+
func age(context _: Context, info _: GraphQL.GraphQLResolveInfo) async throws -> Int? {
96+
return age
97+
}
98+
99+
func role(context _: Context, info _: GraphQL.GraphQLResolveInfo) async throws -> Role? {
100+
return role
101+
}
102+
}
103+
104+
struct Contact: ContactProtocol {
105+
// User can choose structure
106+
let email: String
107+
108+
// Required implementations
109+
func email(context _: Context, info _: GraphQL.GraphQLResolveInfo) async throws -> EmailAddress {
110+
return EmailAddress(email: email)
111+
}
112+
}
113+
114+
struct Post: PostProtocol {
115+
// User can choose structure
116+
let id: String
117+
let title: String
118+
let content: String
119+
let authorId: String
120+
121+
// Required implementations
122+
func id(context _: Context, info _: GraphQL.GraphQLResolveInfo) async throws -> String {
123+
return id
124+
}
125+
126+
func title(context _: Context, info _: GraphQL.GraphQLResolveInfo) async throws -> String {
127+
return title
128+
}
129+
130+
func content(context _: Context, info _: GraphQL.GraphQLResolveInfo) async throws -> String {
131+
return content
132+
}
133+
134+
func author(context: Context, info _: GraphQL.GraphQLResolveInfo) async throws -> any UserProtocol {
135+
return context.users[authorId]!
136+
}
137+
}
138+
139+
struct Query: QueryProtocol {
140+
// Required implementations
141+
static func user(id: String, context: Context, info _: GraphQL.GraphQLResolveInfo) async throws -> (any UserProtocol)? {
142+
return context.users[id]
143+
}
144+
145+
static func users(context: Context, info _: GraphQL.GraphQLResolveInfo) async throws -> [any UserProtocol] {
146+
return context.users.values.map { $0 as any UserProtocol }
147+
}
148+
149+
static func post(id: String, context: Context, info _: GraphQL.GraphQLResolveInfo) async throws -> (any PostProtocol)? {
150+
return context.posts[id]
151+
}
152+
153+
static func posts(limit _: Int?, context: Context, info _: GraphQL.GraphQLResolveInfo) async throws -> [any PostProtocol] {
154+
return context.posts.values.map { $0 as any PostProtocol }
155+
}
156+
157+
static func userOrPost(id: String, context: Context, info _: GraphQLResolveInfo) async throws -> (any UserOrPostUnion)? {
158+
return context.users[id] ?? context.posts[id]
159+
}
160+
}
161+
162+
struct Mutation: MutationProtocol {
163+
// Required implementations
164+
static func upsertUser(userInfo: UserInfoInput, context: Context, info _: GraphQLResolveInfo) -> any UserProtocol {
165+
let user = User(
166+
id: userInfo.id,
167+
name: userInfo.name,
168+
email: userInfo.email.email,
169+
age: userInfo.age,
170+
role: userInfo.role
171+
)
172+
context.users[userInfo.id] = user
173+
return user
174+
}
175+
}
176+
177+
struct Subscription: SubscriptionProtocol {
178+
// Required implementations
179+
static func watchUser(id: String, context: Context, info _: GraphQLResolveInfo) async throws -> AnyAsyncSequence<(any UserProtocol)?> {
180+
return AsyncStream<(any UserProtocol)?> { continuation in
181+
context.onTriggerWatch = { [weak context] in
182+
continuation.yield(context?.users[id])
183+
}
184+
}.any()
185+
}
186+
}

0 commit comments

Comments
 (0)