-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make Services in Vapor Usable #2901
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
extension Application { | ||
public struct Services { | ||
public let application: Application | ||
} | ||
|
||
public var services: Services { | ||
.init(application: self) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
extension Request { | ||
public struct Services { | ||
public let request: Request | ||
init(request: Request) { | ||
self.request = request | ||
} | ||
} | ||
|
||
public var services: Services { | ||
Services(request: self) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
public extension Application { | ||
struct Service<ServiceType> { | ||
|
||
let application: Application | ||
|
||
public init(application: Application) { | ||
self.application = application | ||
} | ||
|
||
public struct Provider { | ||
let run: (Application) -> () | ||
|
||
public init(_ run: @escaping (Application) -> ()) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't we mark these |
||
self.run = run | ||
} | ||
} | ||
|
||
final class Storage { | ||
var makeService: ((Application) -> ServiceType)? | ||
init() { } | ||
} | ||
|
||
struct Key: StorageKey { | ||
typealias Value = Storage | ||
} | ||
|
||
public var service: ServiceType { | ||
guard let makeService = self.storage.makeService else { | ||
fatalError("No service configured for \(ServiceType.self)") | ||
} | ||
return makeService(self.application) | ||
} | ||
|
||
public func use(_ provider: Provider) { | ||
provider.run(self.application) | ||
} | ||
|
||
public func use(_ makeService: @escaping (Application) -> ServiceType) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
self.storage.makeService = makeService | ||
} | ||
|
||
func initialize() { | ||
self.application.storage[Key.self] = .init() | ||
} | ||
|
||
private var storage: Storage { | ||
if self.application.storage[Key.self] == nil { | ||
self.initialize() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I know this works fine, but can we make |
||
} | ||
return self.application.storage[Key.self]! | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,55 +4,108 @@ final class ServiceTests: XCTestCase { | |
func testReadOnly() throws { | ||
let app = Application(.testing) | ||
defer { app.shutdown() } | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit, but there's a lot of whitespace changes. |
||
app.get("test") { req in | ||
req.readOnly.foos() | ||
} | ||
|
||
try app.test(.GET, "test") { res in | ||
XCTAssertEqual(res.status, .ok) | ||
try XCTAssertEqual(res.content.decode([String].self), ["foo"]) | ||
} | ||
} | ||
|
||
func testWritable() throws { | ||
let app = Application(.testing) | ||
defer { app.shutdown() } | ||
|
||
app.writable = .init(apiKey: "foo") | ||
XCTAssertEqual(app.writable?.apiKey, "foo") | ||
} | ||
|
||
func testLifecycle() throws { | ||
let app = Application(.testing) | ||
defer { app.shutdown() } | ||
|
||
app.lifecycle.use(Hello()) | ||
app.environment.arguments = ["serve"] | ||
try app.start() | ||
app.running?.stop() | ||
} | ||
|
||
func testLocks() throws { | ||
let app = Application(.testing) | ||
defer { app.shutdown() } | ||
|
||
app.sync.withLock { | ||
// Do something. | ||
} | ||
|
||
struct TestKey: LockKey { } | ||
|
||
let test = app.locks.lock(for: TestKey.self) | ||
test.withLock { | ||
// Do something. | ||
} | ||
} | ||
|
||
func testServiceHelpers() throws { | ||
let app = Application(.testing) | ||
defer { app.shutdown() } | ||
|
||
let testString = "This is a test - \(Int.random())" | ||
let myFakeServicce = MyTestService(cannedResponse: testString, eventLoop: app.eventLoopGroup.next(), logger: app.logger) | ||
|
||
app.services.myService.use { _ in | ||
myFakeServicce | ||
} | ||
|
||
app.get("myService") { req -> String in | ||
let thing = req.services.myService.doSomething() | ||
return thing | ||
} | ||
|
||
try app.test(.GET, "myService", afterResponse: { res in | ||
XCTAssertEqual(res.status, .ok) | ||
XCTAssertEqual(res.body.string, testString) | ||
}) | ||
} | ||
} | ||
|
||
protocol MyService { | ||
func `for`(_ request: Request) -> MyService | ||
func doSomething() -> String | ||
} | ||
|
||
extension Application.Services { | ||
var myService: Application.Service<MyService> { | ||
.init(application: self.application) | ||
} | ||
} | ||
|
||
extension Request.Services { | ||
var myService: MyService { | ||
self.request.application.services.myService.service.for(request) | ||
} | ||
} | ||
|
||
struct MyTestService: MyService { | ||
let cannedResponse: String | ||
let eventLoop: EventLoop | ||
let logger: Logger | ||
|
||
func `for`(_ request: Vapor.Request) -> MyService { | ||
return MyTestService(cannedResponse: self.cannedResponse, eventLoop: request.eventLoop, logger: request.logger) | ||
} | ||
|
||
func doSomething() -> String { | ||
return cannedResponse | ||
} | ||
} | ||
|
||
private struct ReadOnly { | ||
let client: Client | ||
|
||
func foos() -> EventLoopFuture<[String]> { | ||
self.client.eventLoop.makeSucceededFuture(["foo"]) | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
public struct
instead ofpublic extension
? I tend to be explicit in marking things public in libraries/frameworks, so future changes don't accidentally expose internal API.