From b90873ae9c2f9dac11a86f88475ca4ebdc2731fb Mon Sep 17 00:00:00 2001 From: Sergey Kazakov Date: Sun, 23 Jun 2024 16:22:01 +0100 Subject: [PATCH 01/11] added resource controller concurrency compatible methods --- .../Resource+Create+Concurrency.swift | 83 ++++++++++++++++ .../Resources+CRUDs/Resource+Create.swift | 5 +- .../Resource+Delete+Concurrency.swift | 85 ++++++++++++++++ .../Resource+Patch+Concurrency.swift | 96 +++++++++++++++++++ .../Resource+Read+Concurrency.swift | 73 ++++++++++++++ .../Resource+Update+Concurrency.swift | 93 ++++++++++++++++++ .../ResourceCollection+All.swift | 61 ++++++++++++ .../ResourceCollection+CursorPage.swift | 70 ++++++++++++++ .../ResourceCollection+Page.swift | 59 ++++++++++++ .../ResourceRelation+Create.swift | 58 +++++++++++ .../ResourceRelation+Delete.swift | 55 +++++++++++ 11 files changed, 735 insertions(+), 3 deletions(-) create mode 100644 Sources/VaporRestKit/Resources+CRUDs/Resource+Create+Concurrency.swift create mode 100644 Sources/VaporRestKit/Resources+CRUDs/Resource+Delete+Concurrency.swift create mode 100644 Sources/VaporRestKit/Resources+CRUDs/Resource+Patch+Concurrency.swift create mode 100644 Sources/VaporRestKit/Resources+CRUDs/Resource+Read+Concurrency.swift create mode 100644 Sources/VaporRestKit/Resources+CRUDs/Resource+Update+Concurrency.swift diff --git a/Sources/VaporRestKit/Resources+CRUDs/Resource+Create+Concurrency.swift b/Sources/VaporRestKit/Resources+CRUDs/Resource+Create+Concurrency.swift new file mode 100644 index 0000000..62725a1 --- /dev/null +++ b/Sources/VaporRestKit/Resources+CRUDs/Resource+Create+Concurrency.swift @@ -0,0 +1,83 @@ +// +// File.swift +// +// +// Created by Sergey Kazakov on 23/06/2024. +// + +import Vapor +import Fluent + +public extension ResourceController { + + func create( + req: Request, + using: Input.Type) async throws -> Output + where + Input: ResourceUpdateModel, + Output.Model == Model, + Input.Model == Output.Model { + + try await create(req: req, using: Input.self).get() + } +} + +public extension RelatedResourceController { + func create( + resolver: Resolver = .byIdKeys, + req: Request, + using: Input.Type, + willAttach middleware: ControllerMiddleware = .empty, + relationKeyPath: ChildrenKeyPath) async throws -> Output + where + Input: ResourceUpdateModel, + Model == Output.Model, + Input.Model == Output.Model { + + try await create( + resolver: resolver, + req: req, using: Input.self, + willAttach: middleware, + relationKeyPath: relationKeyPath + ).get() + } + + func create( + resolver: Resolver = .byIdKeys, + req: Request, + using: Input.Type, + willAttach middleware: ControllerMiddleware = .empty, + relationKeyPath: ChildrenKeyPath) async throws -> Output + where + Input: ResourceUpdateModel, + Model == Output.Model, + Input.Model == Output.Model { + + try await create( + resolver: resolver, + req: req, using: Input.self, + willAttach: middleware, + relationKeyPath: relationKeyPath + ).get() + } + + func create( + resolver: Resolver = .byIdKeys, + req: Request, + using: Input.Type, + willAttach middleware: ControllerMiddleware = .empty, + relationKeyPath: SiblingKeyPath) async throws -> Output + where + Input: ResourceUpdateModel, + Model == Output.Model, + Input.Model == Output.Model, + Through: Fluent.Model { + + try await create( + resolver: resolver, + req: req, using: Input.self, + willAttach: middleware, + relationKeyPath: relationKeyPath + ).get() + } +} diff --git a/Sources/VaporRestKit/Resources+CRUDs/Resource+Create.swift b/Sources/VaporRestKit/Resources+CRUDs/Resource+Create.swift index 455202f..f849227 100644 --- a/Sources/VaporRestKit/Resources+CRUDs/Resource+Create.swift +++ b/Sources/VaporRestKit/Resources+CRUDs/Resource+Create.swift @@ -8,7 +8,6 @@ import Vapor import Fluent - public extension ResourceController { func create( req: Request, @@ -17,10 +16,10 @@ public extension ResourceController { Input: ResourceUpdateModel, Output.Model == Model, Input.Model == Output.Model { - + try Input.validate(content: req) let inputModel = try req.content.decode(Input.self) - + return req.db.tryTransaction { db in inputModel .update(Output.Model(), req: req, database: db) diff --git a/Sources/VaporRestKit/Resources+CRUDs/Resource+Delete+Concurrency.swift b/Sources/VaporRestKit/Resources+CRUDs/Resource+Delete+Concurrency.swift new file mode 100644 index 0000000..c4cb323 --- /dev/null +++ b/Sources/VaporRestKit/Resources+CRUDs/Resource+Delete+Concurrency.swift @@ -0,0 +1,85 @@ +// +// File.swift +// +// +// Created by Sergey Kazakov on 23/06/2024. +// + +import Vapor +import Fluent + +public extension ResourceController { + func delete( + req: Request, + using deleter: Deleter = .defaultDeleter(), + queryModifier: QueryModifier = .empty) async throws -> Output + where + Output.Model == Model { + + try await delete(req: req, using: deleter, queryModifier: queryModifier) + } +} + +public extension RelatedResourceController { + + func delete( + resolver: ChildResolver = .byIdKeys, + req: Request, + using deleter: Deleter = .defaultDeleter(), + willDetach middleware: ControllerMiddleware = .empty, + queryModifier: QueryModifier = .empty, + relationKeyPath: ChildrenKeyPath) async throws -> Output + where + Model == Output.Model { + + try await delete( + resolver: resolver, + req: req, + using: deleter, + willDetach: middleware, + queryModifier: queryModifier, + relationKeyPath: relationKeyPath + ).get() + } + + func delete( + resolver: ParentResolver = .byIdKeys, + req: Request, + using deleter: Deleter = .defaultDeleter(), + willDetach middleware: ControllerMiddleware = .empty, + queryModifier: QueryModifier = .empty, + relationKeyPath: ChildrenKeyPath) async throws -> Output + where + Model == Output.Model { + + try await delete( + resolver: resolver, + req: req, + using: deleter, + willDetach: middleware, + queryModifier: queryModifier, + relationKeyPath: relationKeyPath + ).get() + } + + func delete( + resolver: SiblingsResolver = .byIdKeys, + req: Request, + using deleter: Deleter = .defaultDeleter(), + willDetach middleware: ControllerMiddleware = .empty, + queryModifier: QueryModifier = .empty, + relationKeyPath: SiblingKeyPath) async throws -> Output + where + + Model == Output.Model { + + try await delete( + resolver: resolver, + req: req, + using: deleter, + willDetach: middleware, + queryModifier: queryModifier, + relationKeyPath: relationKeyPath + ).get() + } +} diff --git a/Sources/VaporRestKit/Resources+CRUDs/Resource+Patch+Concurrency.swift b/Sources/VaporRestKit/Resources+CRUDs/Resource+Patch+Concurrency.swift new file mode 100644 index 0000000..f7ba497 --- /dev/null +++ b/Sources/VaporRestKit/Resources+CRUDs/Resource+Patch+Concurrency.swift @@ -0,0 +1,96 @@ +// +// File.swift +// +// +// Created by Sergey Kazakov on 07.08.2021. +// + +import Vapor +import Fluent + +public extension ResourceController { + func patch( + req: Request, + using: Input.Type, + queryModifier: QueryModifier = .empty) async throws -> Output + where + Input: ResourcePatchModel, + Output.Model == Model, + Input.Model == Output.Model { + + try await patch( + req: req, + using: Input.self, + queryModifier: queryModifier + ).get() + } +} + +public extension RelatedResourceController { + + func patch( + resolver: ChildResolver = .byIdKeys, + req: Request, + using: Input.Type, + willSave middleware: ControllerMiddleware = .empty, + queryModifier: QueryModifier = .empty, + relationKeyPath: ChildrenKeyPath) async throws -> Output + where + Input: ResourcePatchModel, + Model == Output.Model, + Input.Model == Output.Model { + + try await patch( + resolver: resolver, + req: req, + using: Input.self, + willSave: middleware, + queryModifier: queryModifier, + relationKeyPath: relationKeyPath + ).get() + } + + func patch( + resolver: ParentResolver = .byIdKeys, + req: Request, + using: Input.Type, + willSave middleware: ControllerMiddleware = .empty, + queryModifier: QueryModifier = .empty, + relationKeyPath: ChildrenKeyPath) async throws -> Output + where + Input: ResourcePatchModel, + Model == Output.Model, + Input.Model == Output.Model { + + try await patch( + resolver: resolver, + req: req, + using: Input.self, + willSave: middleware, + queryModifier: queryModifier, + relationKeyPath: relationKeyPath + ).get() + } + + func patch( + resolver: SiblingsResolver = .byIdKeys, + req: Request, + using: Input.Type, + willSave middleware: ControllerMiddleware = .empty, + queryModifier: QueryModifier = .empty, + relationKeyPath: SiblingKeyPath) async throws -> Output + where + Input: ResourcePatchModel, + Model == Output.Model, + Input.Model == Output.Model { + + try await patch( + resolver: resolver, + req: req, + using: Input.self, + willSave: middleware, + queryModifier: queryModifier, + relationKeyPath: relationKeyPath + ).get() + } +} diff --git a/Sources/VaporRestKit/Resources+CRUDs/Resource+Read+Concurrency.swift b/Sources/VaporRestKit/Resources+CRUDs/Resource+Read+Concurrency.swift new file mode 100644 index 0000000..3944a49 --- /dev/null +++ b/Sources/VaporRestKit/Resources+CRUDs/Resource+Read+Concurrency.swift @@ -0,0 +1,73 @@ +// +// File.swift +// +// +// Created by Sergey Kazakov on 23.06.2024. +// + +import Vapor +import Fluent + +public extension ResourceController { + func read(req: Request, + queryModifier: QueryModifier = .empty) async throws -> Output + where + Output.Model == Model { + + try await read( + req: req, + queryModifier: queryModifier + ).get() + } +} + +public extension RelatedResourceController { + func read( + resolver: ChildResolver = .byIdKeys, + req: Request, + queryModifier: QueryModifier = .empty, + relationKeyPath: ChildrenKeyPath) async throws -> Output + where + Model == Output.Model { + + try await read( + resolver: resolver, + req: req, + queryModifier: queryModifier, + relationKeyPath: relationKeyPath + ).get() + } + + func read( + resolver: ParentResolver = .byIdKeys, + req: Request, + queryModifier: QueryModifier = .empty, + relationKeyPath: ChildrenKeyPath) async throws -> Output + where + Model == Output.Model { + + try await read( + resolver: resolver, + req: req, + queryModifier: queryModifier, + relationKeyPath: relationKeyPath + ).get() + } + + func read( + resolver: SiblingsResolver = .byIdKeys, + req: Request, + queryModifier: QueryModifier = .empty, + relationKeyPath: SiblingKeyPath) async throws -> Output + where + Model == Output.Model { + + try await read( + resolver: resolver, + req: req, + queryModifier: queryModifier, + relationKeyPath: relationKeyPath + ).get() + } +} + diff --git a/Sources/VaporRestKit/Resources+CRUDs/Resource+Update+Concurrency.swift b/Sources/VaporRestKit/Resources+CRUDs/Resource+Update+Concurrency.swift new file mode 100644 index 0000000..2759a39 --- /dev/null +++ b/Sources/VaporRestKit/Resources+CRUDs/Resource+Update+Concurrency.swift @@ -0,0 +1,93 @@ +// +// File.swift +// +// +// Created by Sergey Kazakov on 07.08.2021. +// + + +import Vapor +import Fluent + +public extension ResourceController { + func update( + req: Request, + using: Input.Type, + queryModifier: QueryModifier = .empty) async throws -> Output + where + Input: ResourceUpdateModel, + Output.Model == Model, + Input.Model == Output.Model { + + try await update(req: req, using: using, queryModifier: queryModifier).get() + } +} + +public extension RelatedResourceController { + + func update( + resolver: ChildResolver = .byIdKeys, + req: Request, + using: Input.Type, + willSave middleware: ControllerMiddleware = .empty, + queryModifier: QueryModifier = .empty, + relationKeyPath: ChildrenKeyPath) async throws -> Output + where + Input: ResourceUpdateModel, + Model == Output.Model, + Input.Model == Output.Model { + + try await update( + resolver: resolver, + req: req, + using: using, + willSave: middleware, + queryModifier: queryModifier, + relationKeyPath: relationKeyPath + ).get() + } + + func update( + resolver: ParentResolver = .byIdKeys, + req: Request, + using: Input.Type, + willSave middleware: ControllerMiddleware = .empty, + queryModifier: QueryModifier = .empty, + relationKeyPath: ChildrenKeyPath) async throws -> Output + where + Input: ResourceUpdateModel, + Model == Output.Model, + Input.Model == Output.Model { + + try await update( + resolver: resolver, + req: req, + using: using, + willSave: middleware, + queryModifier: queryModifier, + relationKeyPath: relationKeyPath + ).get() + } + + func update( + resolver: SiblingsResolver = .byIdKeys, + req: Request, + using: Input.Type, + willSave middleware: ControllerMiddleware = .empty, + queryModifier: QueryModifier = .empty, + relationKeyPath: SiblingKeyPath) async throws -> Output + where + Input: ResourceUpdateModel, + Model == Output.Model, + Input.Model == Output.Model { + + try await update( + resolver: resolver, + req: req, + using: using, + willSave: middleware, + queryModifier: queryModifier, + relationKeyPath: relationKeyPath + ).get() + } +} diff --git a/Sources/VaporRestKit/Resources+CRUDs/ResourceCollection+All.swift b/Sources/VaporRestKit/Resources+CRUDs/ResourceCollection+All.swift index 91fb370..ea70654 100644 --- a/Sources/VaporRestKit/Resources+CRUDs/ResourceCollection+All.swift +++ b/Sources/VaporRestKit/Resources+CRUDs/ResourceCollection+All.swift @@ -22,6 +22,17 @@ public extension ResourceController { .all() .flatMapThrowing { collection in try collection.map { try Output($0, req: req) } } } + + func getAll(req: Request, + queryModifier: QueryModifier = .empty) async throws -> [Output] + where + Output.Model == Model { + + try await getAll( + req: req, + queryModifier: queryModifier + ).get() + } } public extension RelatedResourceController { @@ -74,3 +85,53 @@ public extension RelatedResourceController { } } +public extension RelatedResourceController { + func getAll( + resolver: Resolver = .byIdKeys, + req: Request, + queryModifier: QueryModifier = .empty, + relationKeyPath: ChildrenKeyPath) async throws -> [Output] + where + Model == Output.Model { + + try await getAll( + resolver: resolver, + req: req, + queryModifier: queryModifier, + relationKeyPath: relationKeyPath + ).get() + } + + func getAll( + resolver: Resolver = .byIdKeys, + req: Request, + queryModifier: QueryModifier = .empty, + relationKeyPath: ChildrenKeyPath) async throws -> [Output] + where + Model == Output.Model { + + try await getAll( + resolver: resolver, + req: req, + queryModifier: queryModifier, + relationKeyPath: relationKeyPath + ).get() + } + + func getAll( + resolver: Resolver = .byIdKeys, + req: Request, + queryModifier: QueryModifier = .empty, + relationKeyPath: SiblingKeyPath) async throws -> [Output] + where + Model == Output.Model { + + try await getAll( + resolver: resolver, + req: req, + queryModifier: queryModifier, + relationKeyPath: relationKeyPath + ).get() + } +} + diff --git a/Sources/VaporRestKit/Resources+CRUDs/ResourceCollection+CursorPage.swift b/Sources/VaporRestKit/Resources+CRUDs/ResourceCollection+CursorPage.swift index cadb1e0..89f8f1b 100644 --- a/Sources/VaporRestKit/Resources+CRUDs/ResourceCollection+CursorPage.swift +++ b/Sources/VaporRestKit/Resources+CRUDs/ResourceCollection+CursorPage.swift @@ -22,6 +22,20 @@ public extension ResourceController { .paginateWithCursor(for: req, config: config) .flatMapThrowing { collection in try collection.map { try Output($0, req: req) } } } + + func getCursorPage( + req: Request, + queryModifier: QueryModifier = .empty, + config: CursorPaginationConfig = .defaultConfig) async throws -> CursorPage + where + Output.Model == Model { + + try await getCursorPage( + req: req, + queryModifier: queryModifier, + config: config + ).get() + } } public extension RelatedResourceController { @@ -76,3 +90,59 @@ public extension RelatedResourceController { .flatMapThrowing { collection in try collection.map { try Output($0, req: req) } } } } + +public extension RelatedResourceController { + func getCursorPage( + resolver: Resolver = .byIdKeys, + req: Request, + queryModifier: QueryModifier = .empty, + relationKeyPath: ChildrenKeyPath, + config: CursorPaginationConfig = .defaultConfig) async throws -> CursorPage + where + Model == Output.Model { + + try await getCursorPage( + resolver: resolver, + req: req, + queryModifier: queryModifier, + relationKeyPath: relationKeyPath, + config: config + ).get() + } + + func getCursorPage( + resolver: Resolver = .byIdKeys, + req: Request, + queryModifier: QueryModifier = .empty, + relationKeyPath: ChildrenKeyPath, + config: CursorPaginationConfig = .defaultConfig) async throws -> CursorPage + where + Model == Output.Model { + + try await getCursorPage( + resolver: resolver, + req: req, + queryModifier: queryModifier, + relationKeyPath: relationKeyPath, + config: config + ).get() + } + + func getCursorPage( + resolver: Resolver = .byIdKeys, + req: Request, + queryModifier: QueryModifier = .empty, + relationKeyPath: SiblingKeyPath, + config: CursorPaginationConfig = .defaultConfig) async throws -> CursorPage + where + Model == Output.Model { + + try await getCursorPage( + resolver: resolver, + req: req, + queryModifier: queryModifier, + relationKeyPath: relationKeyPath, + config: config + ).get() + } +} diff --git a/Sources/VaporRestKit/Resources+CRUDs/ResourceCollection+Page.swift b/Sources/VaporRestKit/Resources+CRUDs/ResourceCollection+Page.swift index 9e5a816..a1cb0d0 100644 --- a/Sources/VaporRestKit/Resources+CRUDs/ResourceCollection+Page.swift +++ b/Sources/VaporRestKit/Resources+CRUDs/ResourceCollection+Page.swift @@ -21,6 +21,15 @@ public extension ResourceController { .paginate(for: req) .flatMapThrowing { collection in try collection.map { try Output($0, req: req) } } } + + func getPage( + req: Request, + queryModifier: QueryModifier = .empty) async throws -> Page + where + Output.Model == Model { + + try await getPage(req: req, queryModifier: queryModifier).get() + } } public extension RelatedResourceController { @@ -72,3 +81,53 @@ public extension RelatedResourceController { .flatMapThrowing { collection in try collection.map { try Output($0, req: req) } } } } + +public extension RelatedResourceController { + func getPage( + resolver: Resolver = .byIdKeys, + req: Request, + queryModifier: QueryModifier = .empty, + relationKeyPath: ChildrenKeyPath) async throws -> Page + where + Model == Output.Model { + + try await getPage( + resolver: resolver, + req: req, + queryModifier: queryModifier, + relationKeyPath: relationKeyPath + ).get() + } + + func getPage( + resolver: Resolver = .byIdKeys, + req: Request, + queryModifier: QueryModifier = .empty, + relationKeyPath: ChildrenKeyPath) async throws -> Page + where + Model == Output.Model { + + try await getPage( + resolver: resolver, + req: req, + queryModifier: queryModifier, + relationKeyPath: relationKeyPath + ).get() + } + + func getPage( + resolver: Resolver = .byIdKeys, + req: Request, + queryModifier: QueryModifier = .empty, + relationKeyPath: SiblingKeyPath) async throws -> Page + where + Model == Output.Model { + + try await getPage( + resolver: resolver, + req: req, + queryModifier: queryModifier, + relationKeyPath: relationKeyPath + ).get() + } +} diff --git a/Sources/VaporRestKit/Resources+CRUDs/ResourceRelation+Create.swift b/Sources/VaporRestKit/Resources+CRUDs/ResourceRelation+Create.swift index 7e9b320..7d37b3e 100644 --- a/Sources/VaporRestKit/Resources+CRUDs/ResourceRelation+Create.swift +++ b/Sources/VaporRestKit/Resources+CRUDs/ResourceRelation+Create.swift @@ -85,3 +85,61 @@ public extension RelationsController { } } } + +public extension RelationsController { + + func createRelation( + resolver: Resolver = .byIdKeys, + req: Request, + willAttach middleware: ControllerMiddleware = .empty, + queryModifier: QueryModifier = .empty, + relationKeyPath: ChildrenKeyPath) async throws -> Output + where + Model == Output.Model { + + try await createRelation( + resolver: resolver, + req: req, + willAttach: middleware, + queryModifier: queryModifier, + relationKeyPath: relationKeyPath + ).get() + } + + func createRelation( + resolver: Resolver = .byIdKeys, + req: Request, + willAttach middleware: ControllerMiddleware = .empty, + queryModifier: QueryModifier = .empty, + relationKeyPath: ChildrenKeyPath) async throws -> Output + where + Model == Output.Model { + + try await createRelation( + resolver: resolver, + req: req, + willAttach: middleware, + queryModifier: queryModifier, + relationKeyPath: relationKeyPath + ).get() + } + + func createRelation( + resolver: Resolver = .byIdKeys, + req: Request, + willAttach middleware: ControllerMiddleware = .empty, + queryModifier: QueryModifier = .empty, + relationKeyPath: SiblingKeyPath) async throws -> Output + where + Model == Output.Model { + + try await createRelation( + resolver: resolver, + req: req, + willAttach: middleware, + queryModifier: queryModifier, + relationKeyPath: relationKeyPath + ).get() + } +} + diff --git a/Sources/VaporRestKit/Resources+CRUDs/ResourceRelation+Delete.swift b/Sources/VaporRestKit/Resources+CRUDs/ResourceRelation+Delete.swift index eecd399..d6b6e06 100644 --- a/Sources/VaporRestKit/Resources+CRUDs/ResourceRelation+Delete.swift +++ b/Sources/VaporRestKit/Resources+CRUDs/ResourceRelation+Delete.swift @@ -82,3 +82,58 @@ public extension RelationsController { } } +public extension RelationsController { + func deleteRelation( + resolver: ChildResolver = .byIdKeys, + req: Request, + willDetach middleware: ControllerMiddleware = .empty, + queryModifier: QueryModifier = .empty, + relationKeyPath: ChildrenKeyPath) async throws -> Output + where + Model == Output.Model { + + try await deleteRelation( + resolver: resolver, + req: req, + willDetach: middleware, + queryModifier: queryModifier, + relationKeyPath: relationKeyPath + ).get() + } + + func deleteRelation( + resolver: ParentResolver = .byIdKeys, + req: Request, + willDetach middleware: ControllerMiddleware = .empty, + queryModifier: QueryModifier = .empty, + relationKeyPath: ChildrenKeyPath) async throws -> Output + where + Model == Output.Model { + + try await deleteRelation( + resolver: resolver, + req: req, + willDetach: middleware, + queryModifier: queryModifier, + relationKeyPath: relationKeyPath + ).get() + } + + func deleteRelation( + resolver: SiblingsResolver = .byIdKeys, + req: Request, + willDetach middleware: ControllerMiddleware = .empty, + queryModifier: QueryModifier = .empty, + relationKeyPath: SiblingKeyPath) async throws -> Output + where + Model == Output.Model { + + try await deleteRelation( + resolver: resolver, + req: req, + willDetach: middleware, + queryModifier: queryModifier, + relationKeyPath: relationKeyPath + ).get() + } +} From 9ede7f38f6d92011cb2eaa3795bcf59068854603 Mon Sep 17 00:00:00 2001 From: Sergey Kazakov Date: Sun, 23 Jun 2024 16:53:45 +0100 Subject: [PATCH 02/11] removed odd methods from ResourceMutationModel protocol --- .../ResourceModels/ResourceMutationModel.swift | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/Sources/VaporRestKit/ResourceModels/ResourceMutationModel.swift b/Sources/VaporRestKit/ResourceModels/ResourceMutationModel.swift index 07ac72d..855f2b1 100644 --- a/Sources/VaporRestKit/ResourceModels/ResourceMutationModel.swift +++ b/Sources/VaporRestKit/ResourceModels/ResourceMutationModel.swift @@ -11,14 +11,6 @@ import Fluent public protocol ResourceMutationModel: Content, Validatable where Model: Fields { associatedtype Model - - func mutate(_: Model) throws -> Model - + func mutate(_ model: Model, req: Request, database: Database) -> EventLoopFuture } - -public extension ResourceMutationModel { - func mutate(_ model: Model, req: Request, database: Database) -> EventLoopFuture { - return req.eventLoop.tryFuture { try mutate(model) } - } -} From e065901988d6960e7507aad43ab0d00fadf9bd94 Mon Sep 17 00:00:00 2001 From: Sergey Kazakov Date: Sun, 23 Jun 2024 16:56:54 +0100 Subject: [PATCH 03/11] added eventlool concurrency extension --- .../Utils/EventLoop+Concurrency.swift | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 Sources/VaporRestKit/Utils/EventLoop+Concurrency.swift diff --git a/Sources/VaporRestKit/Utils/EventLoop+Concurrency.swift b/Sources/VaporRestKit/Utils/EventLoop+Concurrency.swift new file mode 100644 index 0000000..7011217 --- /dev/null +++ b/Sources/VaporRestKit/Utils/EventLoop+Concurrency.swift @@ -0,0 +1,16 @@ +// +// File.swift +// +// +// Created by Sergey Kazakov on 23/06/2024. +// + +import Vapor + +extension EventLoop { + func withTask(task: @escaping () async throws -> T) -> EventLoopFuture { + let promise = makePromise(of: T.self) + promise.completeWithTask { try await task() } + return promise.futureResult + } +} From c770ab0671052b29769e02cdaff30b2f1bb550cf Mon Sep 17 00:00:00 2001 From: Sergey Kazakov Date: Sun, 23 Jun 2024 17:04:09 +0100 Subject: [PATCH 04/11] minor fix --- .../Resources+CRUDs/Resource+Delete+Concurrency.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Sources/VaporRestKit/Resources+CRUDs/Resource+Delete+Concurrency.swift b/Sources/VaporRestKit/Resources+CRUDs/Resource+Delete+Concurrency.swift index c4cb323..84ece7a 100644 --- a/Sources/VaporRestKit/Resources+CRUDs/Resource+Delete+Concurrency.swift +++ b/Sources/VaporRestKit/Resources+CRUDs/Resource+Delete+Concurrency.swift @@ -16,7 +16,11 @@ public extension ResourceController { where Output.Model == Model { - try await delete(req: req, using: deleter, queryModifier: queryModifier) + try await delete( + req: req, + using: deleter, + queryModifier: queryModifier + ).get() } } From 4376ddac8cbafececd0128e3bb970ff2e5ca50b5 Mon Sep 17 00:00:00 2001 From: Sergey Kazakov Date: Sun, 23 Jun 2024 17:04:30 +0100 Subject: [PATCH 05/11] resource mutation model update and concurrency support --- .../ResourceModels/ResourcePatchModel.swift | 14 +++++++++----- .../ResourceModels/ResourceUpdateModel.swift | 14 +++++++++----- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/Sources/VaporRestKit/ResourceModels/ResourcePatchModel.swift b/Sources/VaporRestKit/ResourceModels/ResourcePatchModel.swift index 5aa31a5..3c39121 100644 --- a/Sources/VaporRestKit/ResourceModels/ResourcePatchModel.swift +++ b/Sources/VaporRestKit/ResourceModels/ResourcePatchModel.swift @@ -11,20 +11,24 @@ import Fluent public protocol ResourcePatchModel: ResourceMutationModel { func patch(_: Model) throws -> Model + func patch(_ model: Model, req: Request, database: Database) async throws -> Model + func patch(_ model: Model, req: Request, database: Database) -> EventLoopFuture } public extension ResourcePatchModel { + func patch(_ model: Model, req: Request, database: Database) async throws -> Model { + try await req.eventLoop.tryFuture { try patch(model) }.get() + } + func patch(_ model: Model, req: Request, database: Database) -> EventLoopFuture { - return req.eventLoop.tryFuture { try patch(model) } + req.eventLoop.withTask { + try await patch(model, req: req, database: database) + } } } public extension ResourcePatchModel { - func mutate(_ model: Model) throws -> Model { - try patch(model) - } - func mutate(_ model: Model, req: Request, database: Database) -> EventLoopFuture { patch(model, req: req, database: database) } diff --git a/Sources/VaporRestKit/ResourceModels/ResourceUpdateModel.swift b/Sources/VaporRestKit/ResourceModels/ResourceUpdateModel.swift index b82d632..5032a97 100644 --- a/Sources/VaporRestKit/ResourceModels/ResourceUpdateModel.swift +++ b/Sources/VaporRestKit/ResourceModels/ResourceUpdateModel.swift @@ -11,20 +11,24 @@ import Fluent public protocol ResourceUpdateModel: ResourceMutationModel { func update(_: Model) throws -> Model + func update(_ model: Model, req: Request, database: Database) async throws -> Model + func update(_ model: Model, req: Request, database: Database) -> EventLoopFuture } public extension ResourceUpdateModel { + func update(_ model: Model, req: Request, database: Database) async throws -> Model { + try await req.eventLoop.tryFuture { try update(model) }.get() + } + func update(_ model: Model, req: Request, database: Database) -> EventLoopFuture { - return req.eventLoop.tryFuture { try update(model) } + req.eventLoop.withTask { + try await update(model, req: req, database: database) + } } } public extension ResourceUpdateModel { - func mutate(_ model: Model) throws -> Model { - try update(model) - } - func mutate(_ model: Model, req: Request, database: Database) -> EventLoopFuture { update(model, req: req, database: database) } From 1b774ff7a6425635bbb0b5ad628f73ab2d76bbad Mon Sep 17 00:00:00 2001 From: Sergey Kazakov Date: Sun, 23 Jun 2024 17:12:08 +0100 Subject: [PATCH 06/11] refactoring --- .../Resource+Create+Concurrency.swift | 83 ---------------- .../Resources+CRUDs/Resource+Create.swift | 77 +++++++++++++++ .../Resource+Delete+Concurrency.swift | 89 ----------------- .../Resources+CRUDs/Resource+Delete.swift | 82 ++++++++++++++++ .../Resource+Patch+Concurrency.swift | 96 ------------------- .../Resources+CRUDs/Resource+Patch.swift | 89 +++++++++++++++++ .../Resource+Read+Concurrency.swift | 73 -------------- .../Resources+CRUDs/Resource+Read.swift | 65 +++++++++++++ .../Resource+Update+Concurrency.swift | 93 ------------------ .../Resources+CRUDs/Resource+Update.swift | 85 ++++++++++++++++ .../ResourceCollection+All.swift | 2 + .../ResourceCollection+CursorPage.swift | 32 ++++--- .../ResourceCollection+Page.swift | 22 +++-- .../ResourceRelation+Create.swift | 2 + .../ResourceRelation+Delete.swift | 2 + 15 files changed, 435 insertions(+), 457 deletions(-) delete mode 100644 Sources/VaporRestKit/Resources+CRUDs/Resource+Create+Concurrency.swift delete mode 100644 Sources/VaporRestKit/Resources+CRUDs/Resource+Delete+Concurrency.swift delete mode 100644 Sources/VaporRestKit/Resources+CRUDs/Resource+Patch+Concurrency.swift delete mode 100644 Sources/VaporRestKit/Resources+CRUDs/Resource+Read+Concurrency.swift delete mode 100644 Sources/VaporRestKit/Resources+CRUDs/Resource+Update+Concurrency.swift diff --git a/Sources/VaporRestKit/Resources+CRUDs/Resource+Create+Concurrency.swift b/Sources/VaporRestKit/Resources+CRUDs/Resource+Create+Concurrency.swift deleted file mode 100644 index 62725a1..0000000 --- a/Sources/VaporRestKit/Resources+CRUDs/Resource+Create+Concurrency.swift +++ /dev/null @@ -1,83 +0,0 @@ -// -// File.swift -// -// -// Created by Sergey Kazakov on 23/06/2024. -// - -import Vapor -import Fluent - -public extension ResourceController { - - func create( - req: Request, - using: Input.Type) async throws -> Output - where - Input: ResourceUpdateModel, - Output.Model == Model, - Input.Model == Output.Model { - - try await create(req: req, using: Input.self).get() - } -} - -public extension RelatedResourceController { - func create( - resolver: Resolver = .byIdKeys, - req: Request, - using: Input.Type, - willAttach middleware: ControllerMiddleware = .empty, - relationKeyPath: ChildrenKeyPath) async throws -> Output - where - Input: ResourceUpdateModel, - Model == Output.Model, - Input.Model == Output.Model { - - try await create( - resolver: resolver, - req: req, using: Input.self, - willAttach: middleware, - relationKeyPath: relationKeyPath - ).get() - } - - func create( - resolver: Resolver = .byIdKeys, - req: Request, - using: Input.Type, - willAttach middleware: ControllerMiddleware = .empty, - relationKeyPath: ChildrenKeyPath) async throws -> Output - where - Input: ResourceUpdateModel, - Model == Output.Model, - Input.Model == Output.Model { - - try await create( - resolver: resolver, - req: req, using: Input.self, - willAttach: middleware, - relationKeyPath: relationKeyPath - ).get() - } - - func create( - resolver: Resolver = .byIdKeys, - req: Request, - using: Input.Type, - willAttach middleware: ControllerMiddleware = .empty, - relationKeyPath: SiblingKeyPath) async throws -> Output - where - Input: ResourceUpdateModel, - Model == Output.Model, - Input.Model == Output.Model, - Through: Fluent.Model { - - try await create( - resolver: resolver, - req: req, using: Input.self, - willAttach: middleware, - relationKeyPath: relationKeyPath - ).get() - } -} diff --git a/Sources/VaporRestKit/Resources+CRUDs/Resource+Create.swift b/Sources/VaporRestKit/Resources+CRUDs/Resource+Create.swift index f849227..8b45fd4 100644 --- a/Sources/VaporRestKit/Resources+CRUDs/Resource+Create.swift +++ b/Sources/VaporRestKit/Resources+CRUDs/Resource+Create.swift @@ -119,3 +119,80 @@ public extension RelatedResourceController { } } } + +//MARK: - Concurrency + +public extension ResourceController { + + func create( + req: Request, + using: Input.Type) async throws -> Output + where + Input: ResourceUpdateModel, + Output.Model == Model, + Input.Model == Output.Model { + + try await create(req: req, using: Input.self).get() + } +} + +public extension RelatedResourceController { + func create( + resolver: Resolver = .byIdKeys, + req: Request, + using: Input.Type, + willAttach middleware: ControllerMiddleware = .empty, + relationKeyPath: ChildrenKeyPath) async throws -> Output + where + Input: ResourceUpdateModel, + Model == Output.Model, + Input.Model == Output.Model { + + try await create( + resolver: resolver, + req: req, using: Input.self, + willAttach: middleware, + relationKeyPath: relationKeyPath + ).get() + } + + func create( + resolver: Resolver = .byIdKeys, + req: Request, + using: Input.Type, + willAttach middleware: ControllerMiddleware = .empty, + relationKeyPath: ChildrenKeyPath) async throws -> Output + where + Input: ResourceUpdateModel, + Model == Output.Model, + Input.Model == Output.Model { + + try await create( + resolver: resolver, + req: req, using: Input.self, + willAttach: middleware, + relationKeyPath: relationKeyPath + ).get() + } + + func create( + resolver: Resolver = .byIdKeys, + req: Request, + using: Input.Type, + willAttach middleware: ControllerMiddleware = .empty, + relationKeyPath: SiblingKeyPath) async throws -> Output + where + Input: ResourceUpdateModel, + Model == Output.Model, + Input.Model == Output.Model, + Through: Fluent.Model { + + try await create( + resolver: resolver, + req: req, using: Input.self, + willAttach: middleware, + relationKeyPath: relationKeyPath + ).get() + } +} + diff --git a/Sources/VaporRestKit/Resources+CRUDs/Resource+Delete+Concurrency.swift b/Sources/VaporRestKit/Resources+CRUDs/Resource+Delete+Concurrency.swift deleted file mode 100644 index 84ece7a..0000000 --- a/Sources/VaporRestKit/Resources+CRUDs/Resource+Delete+Concurrency.swift +++ /dev/null @@ -1,89 +0,0 @@ -// -// File.swift -// -// -// Created by Sergey Kazakov on 23/06/2024. -// - -import Vapor -import Fluent - -public extension ResourceController { - func delete( - req: Request, - using deleter: Deleter = .defaultDeleter(), - queryModifier: QueryModifier = .empty) async throws -> Output - where - Output.Model == Model { - - try await delete( - req: req, - using: deleter, - queryModifier: queryModifier - ).get() - } -} - -public extension RelatedResourceController { - - func delete( - resolver: ChildResolver = .byIdKeys, - req: Request, - using deleter: Deleter = .defaultDeleter(), - willDetach middleware: ControllerMiddleware = .empty, - queryModifier: QueryModifier = .empty, - relationKeyPath: ChildrenKeyPath) async throws -> Output - where - Model == Output.Model { - - try await delete( - resolver: resolver, - req: req, - using: deleter, - willDetach: middleware, - queryModifier: queryModifier, - relationKeyPath: relationKeyPath - ).get() - } - - func delete( - resolver: ParentResolver = .byIdKeys, - req: Request, - using deleter: Deleter = .defaultDeleter(), - willDetach middleware: ControllerMiddleware = .empty, - queryModifier: QueryModifier = .empty, - relationKeyPath: ChildrenKeyPath) async throws -> Output - where - Model == Output.Model { - - try await delete( - resolver: resolver, - req: req, - using: deleter, - willDetach: middleware, - queryModifier: queryModifier, - relationKeyPath: relationKeyPath - ).get() - } - - func delete( - resolver: SiblingsResolver = .byIdKeys, - req: Request, - using deleter: Deleter = .defaultDeleter(), - willDetach middleware: ControllerMiddleware = .empty, - queryModifier: QueryModifier = .empty, - relationKeyPath: SiblingKeyPath) async throws -> Output - where - - Model == Output.Model { - - try await delete( - resolver: resolver, - req: req, - using: deleter, - willDetach: middleware, - queryModifier: queryModifier, - relationKeyPath: relationKeyPath - ).get() - } -} diff --git a/Sources/VaporRestKit/Resources+CRUDs/Resource+Delete.swift b/Sources/VaporRestKit/Resources+CRUDs/Resource+Delete.swift index 77178cf..b80d405 100644 --- a/Sources/VaporRestKit/Resources+CRUDs/Resource+Delete.swift +++ b/Sources/VaporRestKit/Resources+CRUDs/Resource+Delete.swift @@ -114,3 +114,85 @@ public extension RelatedResourceController { } } } + +//MARK: - Concurrency + +public extension ResourceController { + func delete( + req: Request, + using deleter: Deleter = .defaultDeleter(), + queryModifier: QueryModifier = .empty) async throws -> Output + where + Output.Model == Model { + + try await delete( + req: req, + using: deleter, + queryModifier: queryModifier + ).get() + } +} + +public extension RelatedResourceController { + + func delete( + resolver: ChildResolver = .byIdKeys, + req: Request, + using deleter: Deleter = .defaultDeleter(), + willDetach middleware: ControllerMiddleware = .empty, + queryModifier: QueryModifier = .empty, + relationKeyPath: ChildrenKeyPath) async throws -> Output + where + Model == Output.Model { + + try await delete( + resolver: resolver, + req: req, + using: deleter, + willDetach: middleware, + queryModifier: queryModifier, + relationKeyPath: relationKeyPath + ).get() + } + + func delete( + resolver: ParentResolver = .byIdKeys, + req: Request, + using deleter: Deleter = .defaultDeleter(), + willDetach middleware: ControllerMiddleware = .empty, + queryModifier: QueryModifier = .empty, + relationKeyPath: ChildrenKeyPath) async throws -> Output + where + Model == Output.Model { + + try await delete( + resolver: resolver, + req: req, + using: deleter, + willDetach: middleware, + queryModifier: queryModifier, + relationKeyPath: relationKeyPath + ).get() + } + + func delete( + resolver: SiblingsResolver = .byIdKeys, + req: Request, + using deleter: Deleter = .defaultDeleter(), + willDetach middleware: ControllerMiddleware = .empty, + queryModifier: QueryModifier = .empty, + relationKeyPath: SiblingKeyPath) async throws -> Output + where + + Model == Output.Model { + + try await delete( + resolver: resolver, + req: req, + using: deleter, + willDetach: middleware, + queryModifier: queryModifier, + relationKeyPath: relationKeyPath + ).get() + } +} diff --git a/Sources/VaporRestKit/Resources+CRUDs/Resource+Patch+Concurrency.swift b/Sources/VaporRestKit/Resources+CRUDs/Resource+Patch+Concurrency.swift deleted file mode 100644 index f7ba497..0000000 --- a/Sources/VaporRestKit/Resources+CRUDs/Resource+Patch+Concurrency.swift +++ /dev/null @@ -1,96 +0,0 @@ -// -// File.swift -// -// -// Created by Sergey Kazakov on 07.08.2021. -// - -import Vapor -import Fluent - -public extension ResourceController { - func patch( - req: Request, - using: Input.Type, - queryModifier: QueryModifier = .empty) async throws -> Output - where - Input: ResourcePatchModel, - Output.Model == Model, - Input.Model == Output.Model { - - try await patch( - req: req, - using: Input.self, - queryModifier: queryModifier - ).get() - } -} - -public extension RelatedResourceController { - - func patch( - resolver: ChildResolver = .byIdKeys, - req: Request, - using: Input.Type, - willSave middleware: ControllerMiddleware = .empty, - queryModifier: QueryModifier = .empty, - relationKeyPath: ChildrenKeyPath) async throws -> Output - where - Input: ResourcePatchModel, - Model == Output.Model, - Input.Model == Output.Model { - - try await patch( - resolver: resolver, - req: req, - using: Input.self, - willSave: middleware, - queryModifier: queryModifier, - relationKeyPath: relationKeyPath - ).get() - } - - func patch( - resolver: ParentResolver = .byIdKeys, - req: Request, - using: Input.Type, - willSave middleware: ControllerMiddleware = .empty, - queryModifier: QueryModifier = .empty, - relationKeyPath: ChildrenKeyPath) async throws -> Output - where - Input: ResourcePatchModel, - Model == Output.Model, - Input.Model == Output.Model { - - try await patch( - resolver: resolver, - req: req, - using: Input.self, - willSave: middleware, - queryModifier: queryModifier, - relationKeyPath: relationKeyPath - ).get() - } - - func patch( - resolver: SiblingsResolver = .byIdKeys, - req: Request, - using: Input.Type, - willSave middleware: ControllerMiddleware = .empty, - queryModifier: QueryModifier = .empty, - relationKeyPath: SiblingKeyPath) async throws -> Output - where - Input: ResourcePatchModel, - Model == Output.Model, - Input.Model == Output.Model { - - try await patch( - resolver: resolver, - req: req, - using: Input.self, - willSave: middleware, - queryModifier: queryModifier, - relationKeyPath: relationKeyPath - ).get() - } -} diff --git a/Sources/VaporRestKit/Resources+CRUDs/Resource+Patch.swift b/Sources/VaporRestKit/Resources+CRUDs/Resource+Patch.swift index 5dd25f7..dbba2e4 100644 --- a/Sources/VaporRestKit/Resources+CRUDs/Resource+Patch.swift +++ b/Sources/VaporRestKit/Resources+CRUDs/Resource+Patch.swift @@ -84,3 +84,92 @@ public extension RelatedResourceController { relationKeyPath: relationKeyPath) } } + +//MARK: - Concurrency + +public extension ResourceController { + func patch( + req: Request, + using: Input.Type, + queryModifier: QueryModifier = .empty) async throws -> Output + where + Input: ResourcePatchModel, + Output.Model == Model, + Input.Model == Output.Model { + + try await patch( + req: req, + using: Input.self, + queryModifier: queryModifier + ).get() + } +} + +public extension RelatedResourceController { + + func patch( + resolver: ChildResolver = .byIdKeys, + req: Request, + using: Input.Type, + willSave middleware: ControllerMiddleware = .empty, + queryModifier: QueryModifier = .empty, + relationKeyPath: ChildrenKeyPath) async throws -> Output + where + Input: ResourcePatchModel, + Model == Output.Model, + Input.Model == Output.Model { + + try await patch( + resolver: resolver, + req: req, + using: Input.self, + willSave: middleware, + queryModifier: queryModifier, + relationKeyPath: relationKeyPath + ).get() + } + + func patch( + resolver: ParentResolver = .byIdKeys, + req: Request, + using: Input.Type, + willSave middleware: ControllerMiddleware = .empty, + queryModifier: QueryModifier = .empty, + relationKeyPath: ChildrenKeyPath) async throws -> Output + where + Input: ResourcePatchModel, + Model == Output.Model, + Input.Model == Output.Model { + + try await patch( + resolver: resolver, + req: req, + using: Input.self, + willSave: middleware, + queryModifier: queryModifier, + relationKeyPath: relationKeyPath + ).get() + } + + func patch( + resolver: SiblingsResolver = .byIdKeys, + req: Request, + using: Input.Type, + willSave middleware: ControllerMiddleware = .empty, + queryModifier: QueryModifier = .empty, + relationKeyPath: SiblingKeyPath) async throws -> Output + where + Input: ResourcePatchModel, + Model == Output.Model, + Input.Model == Output.Model { + + try await patch( + resolver: resolver, + req: req, + using: Input.self, + willSave: middleware, + queryModifier: queryModifier, + relationKeyPath: relationKeyPath + ).get() + } +} diff --git a/Sources/VaporRestKit/Resources+CRUDs/Resource+Read+Concurrency.swift b/Sources/VaporRestKit/Resources+CRUDs/Resource+Read+Concurrency.swift deleted file mode 100644 index 3944a49..0000000 --- a/Sources/VaporRestKit/Resources+CRUDs/Resource+Read+Concurrency.swift +++ /dev/null @@ -1,73 +0,0 @@ -// -// File.swift -// -// -// Created by Sergey Kazakov on 23.06.2024. -// - -import Vapor -import Fluent - -public extension ResourceController { - func read(req: Request, - queryModifier: QueryModifier = .empty) async throws -> Output - where - Output.Model == Model { - - try await read( - req: req, - queryModifier: queryModifier - ).get() - } -} - -public extension RelatedResourceController { - func read( - resolver: ChildResolver = .byIdKeys, - req: Request, - queryModifier: QueryModifier = .empty, - relationKeyPath: ChildrenKeyPath) async throws -> Output - where - Model == Output.Model { - - try await read( - resolver: resolver, - req: req, - queryModifier: queryModifier, - relationKeyPath: relationKeyPath - ).get() - } - - func read( - resolver: ParentResolver = .byIdKeys, - req: Request, - queryModifier: QueryModifier = .empty, - relationKeyPath: ChildrenKeyPath) async throws -> Output - where - Model == Output.Model { - - try await read( - resolver: resolver, - req: req, - queryModifier: queryModifier, - relationKeyPath: relationKeyPath - ).get() - } - - func read( - resolver: SiblingsResolver = .byIdKeys, - req: Request, - queryModifier: QueryModifier = .empty, - relationKeyPath: SiblingKeyPath) async throws -> Output - where - Model == Output.Model { - - try await read( - resolver: resolver, - req: req, - queryModifier: queryModifier, - relationKeyPath: relationKeyPath - ).get() - } -} - diff --git a/Sources/VaporRestKit/Resources+CRUDs/Resource+Read.swift b/Sources/VaporRestKit/Resources+CRUDs/Resource+Read.swift index 49d37ef..5f46ee1 100644 --- a/Sources/VaporRestKit/Resources+CRUDs/Resource+Read.swift +++ b/Sources/VaporRestKit/Resources+CRUDs/Resource+Read.swift @@ -61,3 +61,68 @@ public extension RelatedResourceController { } } +//MARK: - Concurrency + +public extension ResourceController { + func read(req: Request, + queryModifier: QueryModifier = .empty) async throws -> Output + where + Output.Model == Model { + + try await read( + req: req, + queryModifier: queryModifier + ).get() + } +} + +public extension RelatedResourceController { + func read( + resolver: ChildResolver = .byIdKeys, + req: Request, + queryModifier: QueryModifier = .empty, + relationKeyPath: ChildrenKeyPath) async throws -> Output + where + Model == Output.Model { + + try await read( + resolver: resolver, + req: req, + queryModifier: queryModifier, + relationKeyPath: relationKeyPath + ).get() + } + + func read( + resolver: ParentResolver = .byIdKeys, + req: Request, + queryModifier: QueryModifier = .empty, + relationKeyPath: ChildrenKeyPath) async throws -> Output + where + Model == Output.Model { + + try await read( + resolver: resolver, + req: req, + queryModifier: queryModifier, + relationKeyPath: relationKeyPath + ).get() + } + + func read( + resolver: SiblingsResolver = .byIdKeys, + req: Request, + queryModifier: QueryModifier = .empty, + relationKeyPath: SiblingKeyPath) async throws -> Output + where + Model == Output.Model { + + try await read( + resolver: resolver, + req: req, + queryModifier: queryModifier, + relationKeyPath: relationKeyPath + ).get() + } +} + diff --git a/Sources/VaporRestKit/Resources+CRUDs/Resource+Update+Concurrency.swift b/Sources/VaporRestKit/Resources+CRUDs/Resource+Update+Concurrency.swift deleted file mode 100644 index 2759a39..0000000 --- a/Sources/VaporRestKit/Resources+CRUDs/Resource+Update+Concurrency.swift +++ /dev/null @@ -1,93 +0,0 @@ -// -// File.swift -// -// -// Created by Sergey Kazakov on 07.08.2021. -// - - -import Vapor -import Fluent - -public extension ResourceController { - func update( - req: Request, - using: Input.Type, - queryModifier: QueryModifier = .empty) async throws -> Output - where - Input: ResourceUpdateModel, - Output.Model == Model, - Input.Model == Output.Model { - - try await update(req: req, using: using, queryModifier: queryModifier).get() - } -} - -public extension RelatedResourceController { - - func update( - resolver: ChildResolver = .byIdKeys, - req: Request, - using: Input.Type, - willSave middleware: ControllerMiddleware = .empty, - queryModifier: QueryModifier = .empty, - relationKeyPath: ChildrenKeyPath) async throws -> Output - where - Input: ResourceUpdateModel, - Model == Output.Model, - Input.Model == Output.Model { - - try await update( - resolver: resolver, - req: req, - using: using, - willSave: middleware, - queryModifier: queryModifier, - relationKeyPath: relationKeyPath - ).get() - } - - func update( - resolver: ParentResolver = .byIdKeys, - req: Request, - using: Input.Type, - willSave middleware: ControllerMiddleware = .empty, - queryModifier: QueryModifier = .empty, - relationKeyPath: ChildrenKeyPath) async throws -> Output - where - Input: ResourceUpdateModel, - Model == Output.Model, - Input.Model == Output.Model { - - try await update( - resolver: resolver, - req: req, - using: using, - willSave: middleware, - queryModifier: queryModifier, - relationKeyPath: relationKeyPath - ).get() - } - - func update( - resolver: SiblingsResolver = .byIdKeys, - req: Request, - using: Input.Type, - willSave middleware: ControllerMiddleware = .empty, - queryModifier: QueryModifier = .empty, - relationKeyPath: SiblingKeyPath) async throws -> Output - where - Input: ResourceUpdateModel, - Model == Output.Model, - Input.Model == Output.Model { - - try await update( - resolver: resolver, - req: req, - using: using, - willSave: middleware, - queryModifier: queryModifier, - relationKeyPath: relationKeyPath - ).get() - } -} diff --git a/Sources/VaporRestKit/Resources+CRUDs/Resource+Update.swift b/Sources/VaporRestKit/Resources+CRUDs/Resource+Update.swift index 619a595..6e8faf4 100644 --- a/Sources/VaporRestKit/Resources+CRUDs/Resource+Update.swift +++ b/Sources/VaporRestKit/Resources+CRUDs/Resource+Update.swift @@ -85,3 +85,88 @@ public extension RelatedResourceController { relationKeyPath: relationKeyPath) } } + +//MARK: - Concurrency + +public extension ResourceController { + func update( + req: Request, + using: Input.Type, + queryModifier: QueryModifier = .empty) async throws -> Output + where + Input: ResourceUpdateModel, + Output.Model == Model, + Input.Model == Output.Model { + + try await update(req: req, using: using, queryModifier: queryModifier).get() + } +} + +public extension RelatedResourceController { + + func update( + resolver: ChildResolver = .byIdKeys, + req: Request, + using: Input.Type, + willSave middleware: ControllerMiddleware = .empty, + queryModifier: QueryModifier = .empty, + relationKeyPath: ChildrenKeyPath) async throws -> Output + where + Input: ResourceUpdateModel, + Model == Output.Model, + Input.Model == Output.Model { + + try await update( + resolver: resolver, + req: req, + using: using, + willSave: middleware, + queryModifier: queryModifier, + relationKeyPath: relationKeyPath + ).get() + } + + func update( + resolver: ParentResolver = .byIdKeys, + req: Request, + using: Input.Type, + willSave middleware: ControllerMiddleware = .empty, + queryModifier: QueryModifier = .empty, + relationKeyPath: ChildrenKeyPath) async throws -> Output + where + Input: ResourceUpdateModel, + Model == Output.Model, + Input.Model == Output.Model { + + try await update( + resolver: resolver, + req: req, + using: using, + willSave: middleware, + queryModifier: queryModifier, + relationKeyPath: relationKeyPath + ).get() + } + + func update( + resolver: SiblingsResolver = .byIdKeys, + req: Request, + using: Input.Type, + willSave middleware: ControllerMiddleware = .empty, + queryModifier: QueryModifier = .empty, + relationKeyPath: SiblingKeyPath) async throws -> Output + where + Input: ResourceUpdateModel, + Model == Output.Model, + Input.Model == Output.Model { + + try await update( + resolver: resolver, + req: req, + using: using, + willSave: middleware, + queryModifier: queryModifier, + relationKeyPath: relationKeyPath + ).get() + } +} diff --git a/Sources/VaporRestKit/Resources+CRUDs/ResourceCollection+All.swift b/Sources/VaporRestKit/Resources+CRUDs/ResourceCollection+All.swift index ea70654..257a086 100644 --- a/Sources/VaporRestKit/Resources+CRUDs/ResourceCollection+All.swift +++ b/Sources/VaporRestKit/Resources+CRUDs/ResourceCollection+All.swift @@ -85,6 +85,8 @@ public extension RelatedResourceController { } } +//MARK: - Concurrency + public extension RelatedResourceController { func getAll( resolver: Resolver = .byIdKeys, diff --git a/Sources/VaporRestKit/Resources+CRUDs/ResourceCollection+CursorPage.swift b/Sources/VaporRestKit/Resources+CRUDs/ResourceCollection+CursorPage.swift index 89f8f1b..83aa32e 100644 --- a/Sources/VaporRestKit/Resources+CRUDs/ResourceCollection+CursorPage.swift +++ b/Sources/VaporRestKit/Resources+CRUDs/ResourceCollection+CursorPage.swift @@ -22,20 +22,6 @@ public extension ResourceController { .paginateWithCursor(for: req, config: config) .flatMapThrowing { collection in try collection.map { try Output($0, req: req) } } } - - func getCursorPage( - req: Request, - queryModifier: QueryModifier = .empty, - config: CursorPaginationConfig = .defaultConfig) async throws -> CursorPage - where - Output.Model == Model { - - try await getCursorPage( - req: req, - queryModifier: queryModifier, - config: config - ).get() - } } public extension RelatedResourceController { @@ -91,6 +77,24 @@ public extension RelatedResourceController { } } +//MARK: - Concurrency + +public extension ResourceController { + func getCursorPage( + req: Request, + queryModifier: QueryModifier = .empty, + config: CursorPaginationConfig = .defaultConfig) async throws -> CursorPage + where + Output.Model == Model { + + try await getCursorPage( + req: req, + queryModifier: queryModifier, + config: config + ).get() + } +} + public extension RelatedResourceController { func getCursorPage( resolver: Resolver = .byIdKeys, diff --git a/Sources/VaporRestKit/Resources+CRUDs/ResourceCollection+Page.swift b/Sources/VaporRestKit/Resources+CRUDs/ResourceCollection+Page.swift index a1cb0d0..da9607f 100644 --- a/Sources/VaporRestKit/Resources+CRUDs/ResourceCollection+Page.swift +++ b/Sources/VaporRestKit/Resources+CRUDs/ResourceCollection+Page.swift @@ -21,15 +21,6 @@ public extension ResourceController { .paginate(for: req) .flatMapThrowing { collection in try collection.map { try Output($0, req: req) } } } - - func getPage( - req: Request, - queryModifier: QueryModifier = .empty) async throws -> Page - where - Output.Model == Model { - - try await getPage(req: req, queryModifier: queryModifier).get() - } } public extension RelatedResourceController { @@ -82,6 +73,19 @@ public extension RelatedResourceController { } } +//MARK: - Concurrency + +public extension ResourceController { + func getPage( + req: Request, + queryModifier: QueryModifier = .empty) async throws -> Page + where + Output.Model == Model { + + try await getPage(req: req, queryModifier: queryModifier).get() + } +} + public extension RelatedResourceController { func getPage( resolver: Resolver = .byIdKeys, diff --git a/Sources/VaporRestKit/Resources+CRUDs/ResourceRelation+Create.swift b/Sources/VaporRestKit/Resources+CRUDs/ResourceRelation+Create.swift index 7d37b3e..28ad71c 100644 --- a/Sources/VaporRestKit/Resources+CRUDs/ResourceRelation+Create.swift +++ b/Sources/VaporRestKit/Resources+CRUDs/ResourceRelation+Create.swift @@ -86,6 +86,8 @@ public extension RelationsController { } } +//MARK: - Concurrency + public extension RelationsController { func createRelation( diff --git a/Sources/VaporRestKit/Resources+CRUDs/ResourceRelation+Delete.swift b/Sources/VaporRestKit/Resources+CRUDs/ResourceRelation+Delete.swift index d6b6e06..54149b6 100644 --- a/Sources/VaporRestKit/Resources+CRUDs/ResourceRelation+Delete.swift +++ b/Sources/VaporRestKit/Resources+CRUDs/ResourceRelation+Delete.swift @@ -82,6 +82,8 @@ public extension RelationsController { } } +//MARK: - Concurrency + public extension RelationsController { func deleteRelation( resolver: ChildResolver = .byIdKeys, From 5d5d9a274d36973bf37b2368a571e14987f5191a Mon Sep 17 00:00:00 2001 From: Sergey Kazakov Date: Sun, 23 Jun 2024 17:39:16 +0100 Subject: [PATCH 07/11] added concurrency support to middleware --- .../ControllerMiddleware.swift | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/Sources/VaporRestKit/ResourcesController/ControllerMiddleware.swift b/Sources/VaporRestKit/ResourcesController/ControllerMiddleware.swift index 0391535..21f9cdc 100644 --- a/Sources/VaporRestKit/ResourcesController/ControllerMiddleware.swift +++ b/Sources/VaporRestKit/ResourcesController/ControllerMiddleware.swift @@ -13,19 +13,18 @@ public typealias RelatedResourceControllerMiddleware = ControllerMiddleware public struct ControllerMiddleware { public typealias Handler = (Model, RelatedModel, Request, Database) -> EventLoopFuture<(Model, RelatedModel)> + public typealias AsyncHandler = (Model, RelatedModel, Request, Database) async throws -> (Model, RelatedModel) - fileprivate let handler: Handler + private let handler: Handler public init(handler: @escaping Handler) { self.handler = handler } - - func handle(_ model: Model, - relatedModel: RelatedModel, - req: Request, - database: Database) -> EventLoopFuture<(Model, RelatedModel)> { - - handler(model, relatedModel, req, database) + + public init(handler: @escaping AsyncHandler) { + self.handler = { model, relatedModel, req, db in + db.eventLoop.withTask { try await handler(model, relatedModel, req, db) } + } } } @@ -36,3 +35,13 @@ public extension ControllerMiddleware { } } } + +extension ControllerMiddleware { + func handle(_ model: Model, + relatedModel: RelatedModel, + req: Request, + database: Database) -> EventLoopFuture<(Model, RelatedModel)> { + + handler(model, relatedModel, req, database) + } +} From 109d3bc38f2fcfee1fbab9172610916069e57e3f Mon Sep 17 00:00:00 2001 From: Sergey Kazakov Date: Sun, 23 Jun 2024 17:39:34 +0100 Subject: [PATCH 08/11] added concurrency support to deleter --- .../ResourcesController/Deleter.swift | 55 ++++++++++++++----- 1 file changed, 41 insertions(+), 14 deletions(-) diff --git a/Sources/VaporRestKit/ResourcesController/Deleter.swift b/Sources/VaporRestKit/ResourcesController/Deleter.swift index 8b681a8..c89f9aa 100644 --- a/Sources/VaporRestKit/ResourcesController/Deleter.swift +++ b/Sources/VaporRestKit/ResourcesController/Deleter.swift @@ -14,32 +14,50 @@ public typealias DeleteHandler = Deleter public struct Deleter { public typealias Handler = (Model, Bool, Request, Database) -> EventLoopFuture - + public typealias AsyncHandler = (Model, Bool, Request, Database) async throws -> Model + private let handler: Handler private let useForcedDelete: Bool - + public init(useForcedDelete: Bool = false, handler: @escaping Handler = Self.defaultDeleteHandler) { self.handler = handler self.useForcedDelete = useForcedDelete } + + public init(useForcedDelete: Bool = false, + handler: @escaping AsyncHandler = Self.defaultDeleteHandler) { + self.handler = { model, forcedDelete, req, db in + db.eventLoop.withTask { try await handler(model, forcedDelete, req, db) } + } + self.useForcedDelete = useForcedDelete + } +} - public static func defaultDeleteHandler(_ model: Model, - forceDelete: Bool, - req: Request, - db: Database) -> EventLoopFuture { - +public extension Deleter { + + static func defaultDeleteHandler(_ model: Model, + forceDelete: Bool, + req: Request, + db: Database) -> EventLoopFuture { + model.delete(force: forceDelete, on: db).transform(to: model) } - - func performDelete(_ model: Model, - req: Request, - database: Database) -> EventLoopFuture { - - handler(model, useForcedDelete, req, database) + + static func defaultDeleteHandler(_ model: Model, + forceDelete: Bool, + req: Request, + db: Database) async throws -> Model { + + try await defaultDeleteHandler( + model, + forceDelete: forceDelete, + req: req, + db: db + ).get() } } - + public extension Deleter { static func defaultDeleter(useForcedDelete: Bool = false) -> Deleter { Deleter(useForcedDelete: useForcedDelete) { model, forceDelete, _, db in @@ -47,3 +65,12 @@ public extension Deleter { } } } + +extension Deleter { + func performDelete(_ model: Model, + req: Request, + database: Database) -> EventLoopFuture { + + handler(model, useForcedDelete, req, database) + } +} From 6aba3353dd885c430a07156eab285312b2c4b891 Mon Sep 17 00:00:00 2001 From: Sergey Kazakov Date: Sun, 23 Jun 2024 17:40:05 +0100 Subject: [PATCH 09/11] minor fix: use db's eventloop for mutations --- Sources/VaporRestKit/ResourceModels/ResourcePatchModel.swift | 4 ++-- Sources/VaporRestKit/ResourceModels/ResourceUpdateModel.swift | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/VaporRestKit/ResourceModels/ResourcePatchModel.swift b/Sources/VaporRestKit/ResourceModels/ResourcePatchModel.swift index 3c39121..1e05584 100644 --- a/Sources/VaporRestKit/ResourceModels/ResourcePatchModel.swift +++ b/Sources/VaporRestKit/ResourceModels/ResourcePatchModel.swift @@ -18,11 +18,11 @@ public protocol ResourcePatchModel: ResourceMutationModel { public extension ResourcePatchModel { func patch(_ model: Model, req: Request, database: Database) async throws -> Model { - try await req.eventLoop.tryFuture { try patch(model) }.get() + try await database.eventLoop.tryFuture { try patch(model) }.get() } func patch(_ model: Model, req: Request, database: Database) -> EventLoopFuture { - req.eventLoop.withTask { + database.eventLoop.withTask { try await patch(model, req: req, database: database) } } diff --git a/Sources/VaporRestKit/ResourceModels/ResourceUpdateModel.swift b/Sources/VaporRestKit/ResourceModels/ResourceUpdateModel.swift index 5032a97..05d7ba1 100644 --- a/Sources/VaporRestKit/ResourceModels/ResourceUpdateModel.swift +++ b/Sources/VaporRestKit/ResourceModels/ResourceUpdateModel.swift @@ -18,11 +18,11 @@ public protocol ResourceUpdateModel: ResourceMutationModel { public extension ResourceUpdateModel { func update(_ model: Model, req: Request, database: Database) async throws -> Model { - try await req.eventLoop.tryFuture { try update(model) }.get() + try await database.eventLoop.tryFuture { try update(model) }.get() } func update(_ model: Model, req: Request, database: Database) -> EventLoopFuture { - req.eventLoop.withTask { + database.eventLoop.withTask { try await update(model, req: req, database: database) } } From 2ef9a1197b5dcc6f0e7b76e1ac5f7ec5e4e749da Mon Sep 17 00:00:00 2001 From: Sergey Kazakov Date: Sun, 23 Jun 2024 18:21:25 +0100 Subject: [PATCH 10/11] fixed non-sendable warnings --- .../Model/Model+Relations.swift | 22 +++++++++++-------- .../FluentExtensions/Model/SiblingModel.swift | 2 +- .../ModelSchema/Migrating.swift | 2 +- .../Models/Entities/Galaxy.swift | 2 +- 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/Sources/VaporRestKit/FluentExtensions/Model/Model+Relations.swift b/Sources/VaporRestKit/FluentExtensions/Model/Model+Relations.swift index d751394..f609c69 100644 --- a/Sources/VaporRestKit/FluentExtensions/Model/Model+Relations.swift +++ b/Sources/VaporRestKit/FluentExtensions/Model/Model+Relations.swift @@ -78,9 +78,10 @@ extension Model { with childrenKeyPath: SiblingKeyPath, on database: Database, method: SiblingsProperty.AttachMethod = .ifNotExists, - edit: @escaping (Through) -> () = { _ in }) -> EventLoopFuture { + edit: @Sendable @escaping (Through) -> () = { _ in }) -> EventLoopFuture { - return self[keyPath: childrenKeyPath].attach(child, method: method, on: database, edit) + self[keyPath: childrenKeyPath] + .attach(child, method: method, on: database, edit) .transform(to: self) } @@ -88,7 +89,8 @@ extension Model { @discardableResult func detached(from child: To, with childrenKeyPath: SiblingKeyPath, on database: Database) -> EventLoopFuture { - return self[keyPath: childrenKeyPath].detach(child, on: database) + self[keyPath: childrenKeyPath] + .detach(child, on: database) .transform(to: self) } @@ -97,9 +99,10 @@ extension Model { with siblingKeyPath: SiblingKeyPath, on database: Database, method: SiblingsProperty.AttachMethod = .ifNotExists, - edit: @escaping (Through) -> () = { _ in }) -> EventLoopFuture { + edit: @Sendable @escaping (Through) -> () = { _ in }) -> EventLoopFuture { - return parent[keyPath: siblingKeyPath].attach(self, method: method, on: database, edit) + parent[keyPath: siblingKeyPath] + .attach(self, method: method, on: database, edit) .transform(to: self) } @discardableResult @@ -107,14 +110,15 @@ extension Model { with siblingKeyPath: SiblingKeyPath, on database: Database) -> EventLoopFuture { - return parent[keyPath: siblingKeyPath].detach(self, on: database) + parent[keyPath: siblingKeyPath] + .detach(self, on: database) .transform(to: self) } @discardableResult func getParentKey(with childrenKeyPath: ChildrenKeyPath) -> ChildrenProperty.Key { - return From()[keyPath: childrenKeyPath].parentKey + From()[keyPath: childrenKeyPath].parentKey } func queryRelated(keyPath childrenKeyPath: ChildrenKeyPath, @@ -136,11 +140,11 @@ extension Model { func queryRelated(keyPath childrenKeyPath: ChildrenKeyPath, on database: Database) -> QueryBuilder { - return self[keyPath: childrenKeyPath].query(on: database) + self[keyPath: childrenKeyPath].query(on: database) } func queryRelated(keyPath siblingKeyPath: SiblingKeyPath, on database: Database) -> QueryBuilder { - return self[keyPath: siblingKeyPath].query(on: database) + self[keyPath: siblingKeyPath].query(on: database) } } diff --git a/Sources/VaporRestKit/FluentExtensions/Model/SiblingModel.swift b/Sources/VaporRestKit/FluentExtensions/Model/SiblingModel.swift index af9f36f..f456dd3 100644 --- a/Sources/VaporRestKit/FluentExtensions/Model/SiblingModel.swift +++ b/Sources/VaporRestKit/FluentExtensions/Model/SiblingModel.swift @@ -27,7 +27,7 @@ public extension SiblingRepresentable { //MARK:- SiblingModel -public final class SiblingModel: Model, Content +public final class SiblingModel: Model, Content, @unchecked Sendable where From: Fluent.Model, To: Fluent.Model, diff --git a/Sources/VaporRestKit/FluentExtensions/ModelSchema/Migrating.swift b/Sources/VaporRestKit/FluentExtensions/ModelSchema/Migrating.swift index ccd30f4..f94027d 100644 --- a/Sources/VaporRestKit/FluentExtensions/ModelSchema/Migrating.swift +++ b/Sources/VaporRestKit/FluentExtensions/ModelSchema/Migrating.swift @@ -11,7 +11,7 @@ import Fluent //MARK:- InitialMigration public struct Migrating { - public typealias MigratingClosure = (Database) -> EventLoopFuture + public typealias MigratingClosure = @Sendable (Database) -> EventLoopFuture public let name: String private let prepareClosure: MigratingClosure diff --git a/Tests/VaporRestKitTests/Models/Entities/Galaxy.swift b/Tests/VaporRestKitTests/Models/Entities/Galaxy.swift index 90353ee..b150353 100644 --- a/Tests/VaporRestKitTests/Models/Entities/Galaxy.swift +++ b/Tests/VaporRestKitTests/Models/Entities/Galaxy.swift @@ -17,7 +17,7 @@ extension Galaxy: FieldsProvidingModel { } } -final class Galaxy: Model, Content { +final class Galaxy: Model, Content, @unchecked Sendable { static var schema = "galaxies" init() { } From 5bdac1b8046355a254cafd6ae1e918ef86ba189c Mon Sep 17 00:00:00 2001 From: Sergey Kazakov Date: Sun, 23 Jun 2024 18:24:07 +0100 Subject: [PATCH 11/11] swift 6 warning fix --- .../Builders/ResourceControllerBuilder.swift | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Sources/VaporRestKit/Deprecated/ControllerBuilders/Builders/ResourceControllerBuilder.swift b/Sources/VaporRestKit/Deprecated/ControllerBuilders/Builders/ResourceControllerBuilder.swift index 3a1bd16..90736c2 100644 --- a/Sources/VaporRestKit/Deprecated/ControllerBuilders/Builders/ResourceControllerBuilder.swift +++ b/Sources/VaporRestKit/Deprecated/ControllerBuilders/Builders/ResourceControllerBuilder.swift @@ -16,16 +16,16 @@ public final class ResourceControllerBuilder: APIMe EagerLoading: EagerLoadProvider, EagerLoading.Model == Model { - internal init(modelType: Model.Type = Model.self, - outputType: Output.Type = Output.self, - eagerLoading: EagerLoading.Type = EagerLoading.self) { - - } - - internal var controllers: [APIMethodsProviding] = [] - - internal func adding(_ controller: APIMethodsProviding) -> ResourceControllerBuilder { - controllers.append(controller) - return self + internal init(modelType: Model.Type = Model.self, + outputType: Output.Type = Output.self, + eagerLoading: EagerLoading.Type = EagerLoading.self) { + + } + + internal var controllers: [APIMethodsProviding] = [] + + internal func adding(_ controller: APIMethodsProviding) -> ResourceControllerBuilder { + controllers.append(controller) + return self + } } -}