Skip to content

Commit 63d0735

Browse files
authored
fix: Update async functionality of server (#70)
* fix: Update async functionality of server * lint * allow passing of configuration * nit
1 parent ba46b6d commit 63d0735

File tree

6 files changed

+97
-69
lines changed

6 files changed

+97
-69
lines changed

README.md

+28-13
Original file line numberDiff line numberDiff line change
@@ -99,25 +99,40 @@ The `webhookKey` should match the [webhookKey on the Parse Server](https://githu
9999
The aforementioned environment variables automatically configure [Parse-Swift<sup>OG</sup> SDK](https://github.com/netreconlab/Parse-Swift). If you need a more custom configuration, see the [documentation](https://netreconlab.github.io/Parse-Swift/release/documentation/parseswift/).
100100

101101
### Initializing ParseSwiftServer
102-
To levergage the aforementioned environment variables, you should modify `configure.swift` in your project to look similar to below:
102+
To levergage the aforementioned environment variables, you should modify `entrypoint.swift` in your project to look similar to below:
103103

104104
```swift
105-
public func configure(_ app: Application) throws {
106-
// Initialize ParseServerSwift
107-
let configuration = try ParseServerConfiguration(app: app)
108-
try ParseServerSwift.initialize(configuration, app: app)
109-
110-
// Add any additional code to configure your server here...
111-
112-
// register routes
113-
try routes(app)
105+
import Vapor
106+
import Dispatch
107+
import Logging
108+
import NIOCore
109+
import NIOPosix
110+
import ParseServerSwift
111+
112+
@main
113+
enum Entrypoint {
114+
static func main() async throws {
115+
var env = try Environment.detect()
116+
try LoggingSystem.bootstrap(from: &env)
117+
118+
let app = try await Application.make(env)
119+
120+
// This attempts to install NIO as the Swift Concurrency global executor.
121+
// You should not call any async functions before this point.
122+
let executorTakeoverSuccess = NIOSingletons.unsafeTryInstallSingletonPosixEventLoopGroupAsConcurrencyGlobalExecutor()
123+
app.logger.debug("Running with \(executorTakeoverSuccess ? "SwiftNIO" : "standard") Swift Concurrency default executor")
124+
125+
try await parseServerSwiftConfigure(app)
126+
try await app.execute()
127+
try await app.asyncShutdown()
128+
}
114129
}
115130
```
116131

117-
If you want to pass the configuration parameters programitically, your `configure` method should look similar to below:
132+
If you want to pass the configuration parameters programitically, you can add a `configure` method to `configure.swift` should look similar to below:
118133

119134
```swift
120-
public func configure(_ app: Application) throws {
135+
public func configure(_ app: Application) async throws {
121136
// Initialize ParseServerSwift
122137
let configuration = try ParseServerConfiguration(app: app,
123138
hostName: "hostName",
@@ -126,7 +141,7 @@ public func configure(_ app: Application) throws {
126141
primaryKey: "primaryKey",
127142
webhookKey: hookKey,
128143
parseServerURLString: "primaryKey")
129-
try ParseServerSwift.initialize(configuration, app: app)
144+
try await ParseServerSwift.initialize(configuration, app: app)
130145

131146
// Add any additional code to configure your server here...
132147

Sources/App/entrypoint.swift

+11-20
Original file line numberDiff line numberDiff line change
@@ -8,26 +8,10 @@
88
import Vapor
99
import Dispatch
1010
import Logging
11+
import NIOCore
12+
import NIOPosix
1113
import ParseServerSwift
1214

13-
/// This extension is temporary and can be removed once Vapor gets this support.
14-
private extension Vapor.Application {
15-
static let baseExecutionQueue = DispatchQueue(label: "vapor.codes.entrypoint")
16-
17-
func runFromAsyncMainEntrypoint() async throws {
18-
try await withCheckedThrowingContinuation { continuation in
19-
Vapor.Application.baseExecutionQueue.async { [self] in
20-
do {
21-
try self.run()
22-
continuation.resume()
23-
} catch {
24-
continuation.resume(throwing: error)
25-
}
26-
}
27-
}
28-
}
29-
}
30-
3115
@main
3216
enum Entrypoint {
3317
static func main() async throws {
@@ -36,9 +20,16 @@ enum Entrypoint {
3620

3721
let app = try await Application.make(env)
3822

39-
defer { app.shutdown() }
23+
// This attempts to install NIO as the Swift Concurrency global executor.
24+
// You should not call any async functions before this point.
25+
// swiftlint:disable:next line_length
26+
let executorTakeoverSuccess = NIOSingletons.unsafeTryInstallSingletonPosixEventLoopGroupAsConcurrencyGlobalExecutor()
27+
app.logger.debug(
28+
"Running with \(executorTakeoverSuccess ? "SwiftNIO" : "standard") Swift Concurrency default executor"
29+
)
4030

4131
try await parseServerSwiftConfigure(app)
42-
try await app.runFromAsyncMainEntrypoint()
32+
try await app.execute()
33+
try await app.asyncShutdown()
4334
}
4435
}

Sources/ParseServerSwift/Extensions/Parse+Vapor.swift

+10-11
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,10 @@ public extension ParseHookRequestable {
2222
- note: This options method should be used in a multi Parse Server environment.
2323
In a single Parse Server environment, use options().
2424
*/
25-
func options(_ request: Request,
26-
// swiftlint:disable:next line_length
27-
parseServerURLStrings: [String] = ParseServerSwift.configuration.parseServerURLStrings) throws -> API.Options {
25+
func options(
26+
_ request: Request,
27+
parseServerURLStrings: [String] = ParseServerSwift.configuration.parseServerURLStrings
28+
) throws -> API.Options {
2829
var options = self.options()
2930
options.insert(.serverURL(try serverURLString(request.url,
3031
parseServerURLStrings: parseServerURLStrings)))
@@ -42,15 +43,13 @@ public extension ParseHookRequestable {
4243
- note: The default cache policy for this method is `.reloadIgnoringLocalCacheData`. If a developer
4344
desires a different policy, it should be inserted in `options`.
4445
*/
45-
func hydrateUser(options: API.Options = [],
46-
request: Request,
47-
// swiftlint:disable:next line_length
48-
parseServerURLStrings: [String] = ParseServerSwift.configuration.parseServerURLStrings) async throws -> Self {
46+
func hydrateUser(
47+
options: API.Options = [],
48+
request: Request,
49+
parseServerURLStrings: [String] = ParseServerSwift.configuration.parseServerURLStrings
50+
) async throws -> Self {
4951
var updatedOptions = try self.options(request, parseServerURLStrings: parseServerURLStrings)
5052
updatedOptions = options.union(updatedOptions)
51-
return try await withCheckedThrowingContinuation { continuation in
52-
self.hydrateUser(options: updatedOptions,
53-
completion: continuation.resume)
54-
}
53+
return try await self.hydrateUser(options: updatedOptions)
5554
}
5655
}

Sources/ParseServerSwift/Parse.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public var configuration: ParseServerConfiguration {
2121

2222
/**
2323
Configure `ParseServerSwift`. This should only be called once when starting your
24-
Vapor app. Typically in the `configure(_ app: Application)`.
24+
Vapor app. Typically in the `parseServerSwiftConfigure(_ app: Application)`.
2525
- parameter configuration: The ParseServer configuration.
2626
- parameter app: Core type representing a Vapor application.
2727
- throws: An error of `ParseError` type.

Sources/ParseServerSwift/configure.swift

+12-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,18 @@
11
import Vapor
22

3-
public func parseServerSwiftConfigure(_ app: Application) async throws {
3+
/**
4+
A helper method for configuring your `ParseServerSwift`. This should only be called once when starting your
5+
Vapor app.
6+
- parameter app: Core type representing a Vapor application.
7+
- parameter configuration: A `ParseServerConfiguration`. If `nil`, the vapor
8+
environment variables will be used for configuration.
9+
*/
10+
public func parseServerSwiftConfigure(
11+
_ app: Application,
12+
with configuration: ParseServerConfiguration? = nil
13+
) async throws {
414
// Initialize ParseServerSwift
5-
let configuration = try ParseServerConfiguration(app: app)
15+
let configuration = try configuration ?? ParseServerConfiguration(app: app)
616
try await ParseServerSwift.initialize(configuration, app: app)
717

818
// register routes

Tests/ParseServerSwiftTests/AppTests.swift

+35-22
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ final class AppTests: XCTestCase {
1010
}
1111

1212
func setupAppForTesting(hookKey: String? = nil) async throws -> Application {
13-
let app = Application(.testing)
13+
let app = try await Application.make(.testing)
1414
let configuration = try ParseServerConfiguration(app: app,
1515
hostName: "hostName",
1616
port: 8080,
@@ -35,49 +35,52 @@ final class AppTests: XCTestCase {
3535
return app
3636
}
3737

38-
func testConfigRequiresKeys() throws {
39-
let app = Application(.testing)
40-
defer { app.shutdown() }
38+
func testConfigRequiresKeys() async throws {
39+
let app = try await Application.make(.testing)
4140
XCTAssertThrowsError(try ParseServerConfiguration(app: app))
41+
try await app.asyncShutdown()
4242
}
4343

44-
func testAllowInitConfigOnce() throws {
45-
let app = Application(.testing)
46-
defer { app.shutdown() }
44+
func testAllowInitConfigOnce() async throws {
45+
let app = try await Application.make(.testing)
4746
let configuration = try ParseServerConfiguration(app: app,
4847
hostName: "hostName",
4948
port: 8080,
5049
applicationId: "applicationId",
5150
primaryKey: "primaryKey",
5251
parseServerURLString: "primaryKey")
5352
XCTAssertNoThrow(try setConfiguration(configuration))
53+
try await app.asyncShutdown()
5454
}
5555

5656
func testDoNotInitConfigTwice() async throws {
5757
let app = try await setupAppForTesting()
58-
defer { app.shutdown() }
5958
let configuration = try ParseServerConfiguration(app: app,
6059
hostName: "hostName",
6160
port: 8080,
6261
applicationId: "applicationId",
6362
primaryKey: "primaryKey",
6463
parseServerURLString: "primaryKey")
6564
XCTAssertThrowsError(try setConfiguration(configuration))
65+
try await app.asyncShutdown()
6666
}
6767

6868
func testFooBar() async throws {
6969
let app = try await setupAppForTesting()
70-
defer { app.shutdown() }
7170

72-
try app.test(.GET, "foo", afterResponse: { res in
71+
try await app.test(
72+
.GET,
73+
"foo"
74+
) { res async throws in
7375
XCTAssertEqual(res.status, .ok)
7476
XCTAssertEqual(res.body.string, "foo bar")
75-
})
77+
}
78+
79+
try await app.asyncShutdown()
7680
}
7781

7882
func testCheckServerHealth() async throws {
7983
let app = try await setupAppForTesting()
80-
defer { app.shutdown() }
8184

8285
XCTAssertGreaterThan(configuration.parseServerURLStrings.count, 0)
8386
do {
@@ -86,6 +89,7 @@ final class AppTests: XCTestCase {
8689
} catch {
8790
XCTAssertTrue(error.localizedDescription.contains("Unable to connect"))
8891
}
92+
try await app.asyncShutdown()
8993
}
9094

9195
func testGetParseServerURLs() async throws {
@@ -106,7 +110,6 @@ final class AppTests: XCTestCase {
106110

107111
func testDeleteHooks() async throws {
108112
let app = try await setupAppForTesting()
109-
defer { app.shutdown() }
110113

111114
let urlString = "https://parse.com/parse"
112115
guard let url = URL(string: urlString) else {
@@ -132,31 +135,39 @@ final class AppTests: XCTestCase {
132135
let currentTriggers2 = await configuration.hooks.getTriggers()
133136
XCTAssertEqual(currentFunctions2.count, 0)
134137
XCTAssertEqual(currentTriggers2.count, 0)
138+
try await app.asyncShutdown()
135139
}
136140

137141
func testFunctionWebhookKeyNotEqual() async throws {
138142
let app = try await setupAppForTesting(hookKey: "wow")
139-
defer { app.shutdown() }
140143

141-
try app.test(.POST, "hello", afterResponse: { res in
144+
try await app.test(
145+
.POST,
146+
"hello"
147+
) { res async throws in
142148
XCTAssertEqual(res.status, .ok)
143149
XCTAssertTrue(res.body.string.contains("Webhook keys"))
144-
})
150+
}
151+
152+
try await app.asyncShutdown()
145153
}
146154

147155
func testTriggerWebhookKeyNotEqual() async throws {
148156
let app = try await setupAppForTesting(hookKey: "wow")
149-
defer { app.shutdown() }
150157

151-
try app.test(.POST, "score/save/before", afterResponse: { res in
158+
try await app.test(
159+
.POST,
160+
"score/save/before"
161+
) { res async throws in
152162
XCTAssertEqual(res.status, .ok)
153163
XCTAssertTrue(res.body.string.contains("Webhook keys"))
154-
})
164+
}
165+
166+
try await app.asyncShutdown()
155167
}
156168

157169
func testMatchServerURLString() async throws {
158170
let app = try await setupAppForTesting()
159-
defer { app.shutdown() }
160171
let urlString = "https://parse.com/parse"
161172
let uri = URI(stringLiteral: urlString)
162173
let serverString = try serverURLString(uri, parseServerURLStrings: [urlString])
@@ -171,21 +182,22 @@ final class AppTests: XCTestCase {
171182
let serverString3 = try serverURLString(uri,
172183
parseServerURLStrings: configuration.parseServerURLStrings)
173184
XCTAssertEqual(serverString3, configuration.parseServerURLStrings.first)
185+
186+
try await app.asyncShutdown()
174187
}
175188

176189
func testMatchServerURLStringThrowsError() async throws {
177190
let app = try await setupAppForTesting()
178191
Parse.configuration.parseServerURLStrings.removeAll()
179-
defer { app.shutdown() }
180192
let urlString = "https://parse.com/parse"
181193
let uri = URI(stringLiteral: urlString)
182194
XCTAssertThrowsError(try serverURLString(uri,
183195
parseServerURLStrings: configuration.parseServerURLStrings))
196+
try await app.asyncShutdown()
184197
}
185198

186199
func testParseHookOptions() async throws {
187200
let app = try await setupAppForTesting()
188-
defer { app.shutdown() }
189201
let installationId = "naw"
190202
let urlString = "https://parse.com/parse"
191203
Parse.configuration.parseServerURLStrings.append(urlString)
@@ -208,6 +220,7 @@ final class AppTests: XCTestCase {
208220
XCTAssertEqual(options2.count, 2)
209221
XCTAssertTrue(installationOption2.debugDescription.contains(installationId))
210222
XCTAssertTrue(serverURLOption.debugDescription.contains("\"\(urlString)\""))
223+
try await app.asyncShutdown()
211224
}
212225

213226
func testHooksFunctions() async throws {

0 commit comments

Comments
 (0)