From 4985892de7ba461e5277aea5530c5cf21ab3d660 Mon Sep 17 00:00:00 2001 From: twof Date: Sat, 16 Nov 2019 17:08:23 -0800 Subject: [PATCH 01/38] Start working on 4 upgrade --- .../contents.xcworkspacedata | 7 + .../IDEFindNavigatorScopes.plist | 5 + .../UserInterfaceState.xcuserstate | Bin 0 -> 33641 bytes .../xcschemes/xcschememanagement.plist | 27 +++ Package.resolved | 166 +++++++----------- Package.swift | 29 +-- README.md | 2 +- .../CrudChildrenControllerProtocol.swift | 70 ++++---- .../CrudControllerProtocol.swift | 52 +++--- .../CrudParentControllerProtocol.swift | 28 +-- .../CrudSiblingsControllerProtocol.swift | 78 ++++---- .../ControllerProtocols/Crudable.swift | 58 +++--- .../CrudRouter/CrudChildrenController.swift | 12 +- Sources/CrudRouter/CrudController.swift | 14 +- Sources/CrudRouter/CrudParentController.swift | 14 +- .../CrudRouter/CrudSiblingsController.swift | 28 ++- Sources/CrudRouter/Extensions/Request.swift | 4 +- .../CrudRouter/Extensions/Router+CRUD.swift | 2 +- .../Obsoleted/CrudControllerObsoleted.swift | 10 +- .../Obsoleted/RouterObsoleted.swift | 2 +- Tests/CrudRouterTests/CrudRouterTests.swift | 16 +- 21 files changed, 305 insertions(+), 319 deletions(-) create mode 100644 .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata create mode 100644 .swiftpm/xcode/package.xcworkspace/xcuserdata/fnord.xcuserdatad/IDEFindNavigatorScopes.plist create mode 100644 .swiftpm/xcode/package.xcworkspace/xcuserdata/fnord.xcuserdatad/UserInterfaceState.xcuserstate create mode 100644 .swiftpm/xcode/xcuserdata/fnord.xcuserdatad/xcschemes/xcschememanagement.plist diff --git a/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/.swiftpm/xcode/package.xcworkspace/xcuserdata/fnord.xcuserdatad/IDEFindNavigatorScopes.plist b/.swiftpm/xcode/package.xcworkspace/xcuserdata/fnord.xcuserdatad/IDEFindNavigatorScopes.plist new file mode 100644 index 0000000..5dd5da8 --- /dev/null +++ b/.swiftpm/xcode/package.xcworkspace/xcuserdata/fnord.xcuserdatad/IDEFindNavigatorScopes.plist @@ -0,0 +1,5 @@ + + + + + diff --git a/.swiftpm/xcode/package.xcworkspace/xcuserdata/fnord.xcuserdatad/UserInterfaceState.xcuserstate b/.swiftpm/xcode/package.xcworkspace/xcuserdata/fnord.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000000000000000000000000000000000000..1c849c53ee1eac43c52a0c6b92fc2dab71ec05fd GIT binary patch literal 33641 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 A(*OVf literal 0 HcmV?d00001 diff --git a/.swiftpm/xcode/xcuserdata/fnord.xcuserdatad/xcschemes/xcschememanagement.plist b/.swiftpm/xcode/xcuserdata/fnord.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..7b778e8 --- /dev/null +++ b/.swiftpm/xcode/xcuserdata/fnord.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,27 @@ + + + + + SchemeUserState + + CrudRouter.xcscheme_^#shared#^_ + + orderHint + 0 + + + SuppressBuildableAutocreation + + CrudRouter + + primary + + + CrudRouterTests + + primary + + + + + diff --git a/Package.resolved b/Package.resolved index e90b488..e01c1e3 100644 --- a/Package.resolved +++ b/Package.resolved @@ -2,111 +2,111 @@ "object": { "pins": [ { - "package": "Console", - "repositoryURL": "https://github.com/vapor/console.git", + "package": "async-http-client", + "repositoryURL": "https://github.com/swift-server/async-http-client.git", "state": { "branch": null, - "revision": "5b9796d39f201b3dd06800437abd9d774a455e57", - "version": "3.0.2" + "revision": "64851a1a0a2a9e8fa7ae7b3508ce46a1da4a2e1d", + "version": "1.0.0-alpha.2" } }, { - "package": "Core", - "repositoryURL": "https://github.com/vapor/core.git", + "package": "async-kit", + "repositoryURL": "https://github.com/vapor/async-kit.git", "state": { "branch": null, - "revision": "96ce86ebf9198328795c4b9cb711489460be083c", - "version": "3.4.4" + "revision": "b5742bfbbe2d60f3b77465a0907777261e418f23", + "version": "1.0.0-alpha.1" } }, { - "package": "Crypto", - "repositoryURL": "https://github.com/vapor/crypto.git", + "package": "console-kit", + "repositoryURL": "https://github.com/vapor/console-kit.git", "state": { "branch": null, - "revision": "5605334590affd4785a5839806b4504407e054ac", - "version": "3.3.0" + "revision": "00ce1abaac919593897b6c60cecdbd4d6290b1f9", + "version": "4.0.0-alpha.2.1" } }, { - "package": "DatabaseKit", - "repositoryURL": "https://github.com/vapor/database-kit.git", + "package": "fluent", + "repositoryURL": "https://github.com/vapor/fluent.git", "state": { "branch": null, - "revision": "3a17dbbe9be5f8c37703e4b7982c1332ad6b00c4", - "version": "1.3.1" + "revision": "1670384e5dd128b8fe1f9353d2bb518ca2ae8e4c", + "version": "4.0.0-alpha.2.1" } }, { - "package": "Fluent", - "repositoryURL": "https://github.com/vapor/fluent.git", + "package": "fluent-kit", + "repositoryURL": "https://github.com/vapor/fluent-kit.git", "state": { "branch": null, - "revision": "dc258fe53880f80508df317df3c903ee2c2b9317", - "version": "3.1.0" + "revision": "0bd08e57a8db90855d74ba6f222d2d847b68b6f0", + "version": "1.0.0-alpha.3" } }, { - "package": "FluentSQLite", - "repositoryURL": "https://github.com/vapor/fluent-sqlite.git", + "package": "fluent-sqlite-driver", + "repositoryURL": "https://github.com/vapor/fluent-sqlite-driver.git", "state": { "branch": null, - "revision": "c32f5bda84bf4ea691d19afe183d40044f579e11", - "version": "3.0.0" + "revision": "64187944b0124fdc7df0c0eead58ee1990b89083", + "version": "4.0.0-alpha.3" } }, { - "package": "HTTP", - "repositoryURL": "https://github.com/vapor/http.git", + "package": "open-crypto", + "repositoryURL": "https://github.com/vapor/open-crypto.git", "state": { "branch": null, - "revision": "272b22be8cb3364e42a4701c2e0676e37480ec5a", - "version": "3.1.5" + "revision": "06d26edb8e28295bb7103b4f950d5ea58d634c1b", + "version": "4.0.0-alpha.2" } }, { - "package": "Multipart", - "repositoryURL": "https://github.com/vapor/multipart.git", + "package": "routing-kit", + "repositoryURL": "https://github.com/vapor/routing-kit.git", "state": { "branch": null, - "revision": "e57007c23a52b68e44ebdfc70cbe882a7c4f1ec3", - "version": "3.0.2" + "revision": "6c7f4b471f9662d05045d82e64e22d5572a16a82", + "version": "4.0.0-alpha.1" } }, { - "package": "Routing", - "repositoryURL": "https://github.com/vapor/routing.git", + "package": "sql-kit", + "repositoryURL": "https://github.com/vapor/sql-kit.git", "state": { "branch": null, - "revision": "3219e328491b0853b8554c5a694add344d2c6cfb", - "version": "3.0.1" + "revision": "354c843f7868eaf93a84b766d652fac8b61d86dd", + "version": "3.0.0-alpha.1.3" } }, { - "package": "Service", - "repositoryURL": "https://github.com/vapor/service.git", + "package": "sqlite-kit", + "repositoryURL": "https://github.com/vapor/sqlite-kit.git", "state": { "branch": null, - "revision": "281a70b69783891900be31a9e70051b6fe19e146", - "version": "1.0.0" + "revision": "628f6e105b8a07b52dcc6d04cde842ce65550bba", + "version": "4.0.0-alpha.1.1" } }, { - "package": "SQL", - "repositoryURL": "https://github.com/vapor/sql.git", + "package": "sqlite-nio", + "repositoryURL": "https://github.com/vapor/sqlite-nio.git", "state": { "branch": null, - "revision": "a35cf1dced4ddd32bb2dc8b6e765aea7bcf8d6e0", - "version": "2.1.0" + "revision": "af713ceab2d186739b7ffd95f7edcfad8d8d57bf", + "version": "1.0.0-alpha.1.1" } }, { - "package": "SQLite", - "repositoryURL": "https://github.com/vapor/sqlite.git", + "package": "swift-log", + "repositoryURL": "https://github.com/apple/swift-log.git", "state": { "branch": null, - "revision": "ad2e9bc9f0ed00ef2c6a05f89c1cec605467c90f", - "version": "3.1.0" + "revision": "e8aabbe95db22e064ad42f1a4a9f8982664c70ed", + "version": "1.1.1" } }, { @@ -114,80 +114,44 @@ "repositoryURL": "https://github.com/apple/swift-nio.git", "state": { "branch": null, - "revision": "5d8148c8b45dfb449276557f22120694567dd1d2", - "version": "1.9.5" - } - }, - { - "package": "swift-nio-ssl", - "repositoryURL": "https://github.com/apple/swift-nio-ssl.git", - "state": { - "branch": null, - "revision": "8380fa29a2af784b067d8ee01c956626ca29f172", - "version": "1.3.1" - } - }, - { - "package": "swift-nio-ssl-support", - "repositoryURL": "https://github.com/apple/swift-nio-ssl-support.git", - "state": { - "branch": null, - "revision": "c02eec4e0e6d351cd092938cf44195a8e669f555", - "version": "1.0.0" + "revision": "32760eae40e6b7cb81d4d543bb0a9f548356d9a2", + "version": "2.7.1" } }, { - "package": "swift-nio-zlib-support", - "repositoryURL": "https://github.com/apple/swift-nio-zlib-support.git", + "package": "swift-nio-extras", + "repositoryURL": "https://github.com/apple/swift-nio-extras.git", "state": { "branch": null, - "revision": "37760e9a52030bb9011972c5213c3350fa9d41fd", - "version": "1.0.0" + "revision": "66f9a509ed3cc56b6eb367515e421beca4a0af53", + "version": "1.2.0" } }, { - "package": "TemplateKit", - "repositoryURL": "https://github.com/vapor/template-kit.git", + "package": "swift-nio-http2", + "repositoryURL": "https://github.com/apple/swift-nio-http2.git", "state": { "branch": null, - "revision": "db35b1c35aabd0f5db3abca0cfda7becfe9c43e2", - "version": "1.1.0" + "revision": "86ce1dcd0df501401eb1a0d445dbd90aaad84a64", + "version": "1.5.0" } }, { - "package": "URLEncodedForm", - "repositoryURL": "https://github.com/vapor/url-encoded-form.git", - "state": { - "branch": null, - "revision": "932024f363ee5ff59059cf7d67194a1c271d3d0c", - "version": "1.0.5" - } - }, - { - "package": "Validation", - "repositoryURL": "https://github.com/vapor/validation.git", + "package": "swift-nio-ssl", + "repositoryURL": "https://github.com/apple/swift-nio-ssl.git", "state": { "branch": null, - "revision": "156f8adeac3440e868da3757777884efbc6ec0cc", - "version": "2.1.0" + "revision": "f5dd7a60ff56f501ff7bf9be753e4b1875bfaf20", + "version": "2.4.0" } }, { - "package": "Vapor", + "package": "vapor", "repositoryURL": "https://github.com/vapor/vapor.git", "state": { "branch": null, - "revision": "157d3b15336caa882662cc75024dd04b2e225246", - "version": "3.1.0" - } - }, - { - "package": "WebSocket", - "repositoryURL": "https://github.com/vapor/websocket.git", - "state": { - "branch": null, - "revision": "eb4277f75f1d96a3d15c852cdd89af1799093dcd", - "version": "1.1.0" + "revision": "17c5ca37a4d70cde7c23b7eb2f7037724fa28899", + "version": "4.0.0-alpha.3.1.1" } } ] diff --git a/Package.swift b/Package.swift index 4e37402..4d270f6 100644 --- a/Package.swift +++ b/Package.swift @@ -1,33 +1,18 @@ -// swift-tools-version:4.2 -// The swift-tools-version declares the minimum version of Swift required to build this package. - +// swift-tools-version:5.1 import PackageDescription let package = Package( name: "CrudRouter", products: [ - // Products define the executables and libraries produced by a package, and make them visible to other packages. - .library( - name: "CrudRouter", - targets: ["CrudRouter"]), + .library(name: "CrudRouter", targets: ["CrudRouter"]), ], dependencies: [ - // Dependencies declare other packages that this package depends on. - // .package(url: /* package url */, from: "1.0.0"), - // 💧 A server-side Swift web framework., - .package(url: "https://github.com/vapor/vapor.git", .upToNextMajor(from: "3.0.0")), - - // 🔵 Swift ORM (queries, models, relations, etc) built on SQLite 3. - .package(url: "https://github.com/vapor/fluent-sqlite.git", .upToNextMajor(from: "3.0.0")), + .package(url: "https://github.com/vapor/vapor.git", from: "4.0.0-alpha.3"), + .package(url: "https://github.com/vapor/fluent.git", from: "4.0.0-alpha.2.1"), + .package(url: "https://github.com/vapor/fluent-sqlite-driver.git", from: "4.0.0-alpha.3"), ], targets: [ - // Targets are the basic building blocks of a package. A target can define a module or a test suite. - // Targets can depend on other targets in this package, and on products in packages which this package depends on. - .target( - name: "CrudRouter", - dependencies: ["FluentSQLite", "Vapor"]), - .testTarget( - name: "CrudRouterTests", - dependencies: ["CrudRouter", "FluentSQLite"]), + .target(name: "CrudRouter", dependencies: ["Fluent", "FluentSQLiteDriver", "Vapor"]), + .testTarget(name: "CrudRouterTests", dependencies: ["CrudRouter", "FluentSQLiteDriver"]), ] ) diff --git a/README.md b/README.md index 2fc6836..25d6d5e 100644 --- a/README.md +++ b/README.md @@ -119,7 +119,7 @@ GET /todo GET /todo/:id/tag/:id ``` -### Future features +### EventLoopFuture features - query parameter support - PATCH support - more fine grained response statuses diff --git a/Sources/CrudRouter/ControllerProtocols/CrudChildrenControllerProtocol.swift b/Sources/CrudRouter/ControllerProtocols/CrudChildrenControllerProtocol.swift index e80dc83..edd9506 100644 --- a/Sources/CrudRouter/ControllerProtocols/CrudChildrenControllerProtocol.swift +++ b/Sources/CrudRouter/ControllerProtocols/CrudChildrenControllerProtocol.swift @@ -2,65 +2,67 @@ import Vapor import Fluent public protocol CrudChildrenControllerProtocol { - associatedtype ParentType: Model & Content where ParentType.ID: Parameter - associatedtype ChildType: Model & Content where ChildType.ID: Parameter, ChildType.Database == ParentType.Database + associatedtype ParentType: Model & Content where ParentType.IDValue: LosslessStringConvertible + associatedtype ChildType: Model & Content where ChildType.IDValue: LosslessStringConvertible + + var db: Database { get } var children: KeyPath> { get } - func index(_ req: Request) throws -> Future - func indexAll(_ req: Request) throws -> Future<[ChildType]> - func create(_ req: Request) throws -> Future - func update(_ req: Request) throws -> Future - func delete(_ req: Request) throws -> Future + func index(_ req: Request) throws -> EventLoopFuture + func indexAll(_ req: Request) throws -> EventLoopFuture<[ChildType]> + func create(_ req: Request) throws -> EventLoopFuture + func update(_ req: Request) throws -> EventLoopFuture + func delete(_ req: Request) throws -> EventLoopFuture } public extension CrudChildrenControllerProtocol { - func index(_ req: Request) throws -> Future { - let parentId: ParentType.ID = try req.getId() - let childId: ChildType.ID = try req.getId() + func index(_ req: Request) throws -> EventLoopFuture { + let parentId: ParentType.IDValue = try req.getId() + let childId: ChildType.IDValue = try req.getId() - return ParentType.find(parentId, on: req).unwrap(or: Abort(.notFound)).flatMap { parent -> Future in + return ParentType.find(parentId, on: db).unwrap(or: Abort(.notFound)).flatMap { parent -> EventLoopFuture in return try parent[keyPath: self.children] - .query(on: req) + .query(on: self.db) .filter(\ChildType.fluentID == childId) .first() .unwrap(or: Abort(.notFound)) } } - func indexAll(_ req: Request) throws -> Future<[ChildType]> { - let parentId: ParentType.ID = try req.getId() + func indexAll(_ req: Request) throws -> EventLoopFuture<[ChildType]> { + let parentId: ParentType.IDValue = try req.getId() - return ParentType.find(parentId, on: req).unwrap(or: Abort(.notFound)).flatMap { parent -> Future<[ChildType]> in + return ParentType.find(parentId, on: db).unwrap(or: Abort(.notFound)).flatMap { parent -> EventLoopFuture<[ChildType]> in return try parent[keyPath: self.children] - .query(on: req) + .query(on: self.db) .all() } } - func create(_ req: Request) throws -> Future { - let parentId: ParentType.ID = try req.getId() + func create(_ req: Request) throws -> EventLoopFuture { + let parentId: ParentType.IDValue = try req.getId() - return ParentType.find(parentId, on: req).unwrap(or: Abort(.notFound)).flatMap { parent -> Future in + return ParentType.find(parentId, on: db).unwrap(or: Abort(.notFound)).flatMap { parent -> EventLoopFuture in return try req.content.decode(ChildType.self).flatMap { child in - return try parent[keyPath: self.children].query(on: req).save(child) + return try parent[keyPath: self.children].query(on: self.db).save(child) } } } - func update(_ req: Request) throws -> Future { - let parentId: ParentType.ID = try req.getId() - let childId: ChildType.ID = try req.getId() + func update(_ req: Request) throws -> EventLoopFuture { + let parentId: ParentType.IDValue = try req.getId() + let childId: ChildType.IDValue = try req.getId() return ParentType - .find(parentId, on: req) + .find(parentId, on: db) .unwrap(or: Abort(.notFound)) - .flatMap { parent -> Future in + .flatMap { parent -> EventLoopFuture in return try parent[keyPath: self.children] - .query(on: req) + .query(on: self.db) .filter(\ChildType.fluentID == childId) .first() .unwrap(or: Abort(.notFound)) @@ -68,25 +70,25 @@ public extension CrudChildrenControllerProtocol { return try req.content.decode(ChildType.self).flatMap { newChild in var temp = newChild temp.fluentID = oldChild.fluentID - return temp.update(on: req) + return temp.update(on: self.db) } } } - func delete(_ req: Request) throws -> Future { - let parentId: ParentType.ID = try req.getId() - let childId: ChildType.ID = try req.getId() + func delete(_ req: Request) throws -> EventLoopFuture { + let parentId: ParentType.IDValue = try req.getId() + let childId: ChildType.IDValue = try req.getId() return ParentType - .find(parentId, on: req) + .find(parentId, on: db) .unwrap(or: Abort(.notFound)) - .flatMap { parent -> Future in + .flatMap { parent -> EventLoopFuture in return try parent[keyPath: self.children] - .query(on: req) + .query(on: self.db) .filter(\ChildType.fluentID == childId) .first() .unwrap(or: Abort(.notFound)) - .delete(on: req) + .delete(on: self.db) .transform(to: HTTPStatus.ok) } } diff --git a/Sources/CrudRouter/ControllerProtocols/CrudControllerProtocol.swift b/Sources/CrudRouter/ControllerProtocols/CrudControllerProtocol.swift index 8a09151..6b22155 100644 --- a/Sources/CrudRouter/ControllerProtocols/CrudControllerProtocol.swift +++ b/Sources/CrudRouter/ControllerProtocols/CrudControllerProtocol.swift @@ -2,46 +2,50 @@ import Vapor import Fluent public protocol CrudControllerProtocol { - associatedtype ModelType: Model, Content where ModelType.ID: Parameter - func indexAll(_ req: Request) throws -> Future<[ModelType]> - func index(_ req: Request) throws -> Future - func update(_ req: Request) throws -> Future - func create(_ req: Request) throws -> Future - func delete(_ req: Request) throws -> Future + associatedtype ModelType: Model, Content where ModelType.IDValue: LosslessStringConvertible + var db: Database { get } + + func indexAll() throws -> EventLoopFuture<[ModelType]> + func index(_ req: Request) throws -> EventLoopFuture + func update(_ req: Request) throws -> EventLoopFuture + func create(_ req: Request) throws -> EventLoopFuture + func delete(_ req: Request) throws -> EventLoopFuture } public extension CrudControllerProtocol { - func indexAll(_ req: Request) throws -> Future<[ModelType]> { - return ModelType.query(on: req).all().map { Array($0) } + func indexAll() throws -> EventLoopFuture<[ModelType]> { + return ModelType.query(on: db).all().map { Array($0) } } - func index(_ req: Request) throws -> Future { - let id: ModelType.ID = try req.getId() - return ModelType.find(id, on: req).unwrap(or: Abort(.notFound)) + func index(_ req: Request) throws -> EventLoopFuture { + let id: ModelType.IDValue = try req.getId() + return ModelType.find(id, on: db).unwrap(or: Abort(.notFound)) } - func create(_ req: Request) throws -> Future { - return try req.content.decode(ModelType.self).flatMap { model in - return model.save(on: req) + func create(_ req: Request) throws -> EventLoopFuture { + return try req.content.decode(ModelType.self).save(on: db).map { + return } } - func update(_ req: Request) throws -> Future { - let id: ModelType.ID = try req.getId() - return try req.content.decode(ModelType.self).flatMap { model in - var temp = model - temp.fluentID = id - return temp.update(on: req) + func update(_ req: Request) throws -> EventLoopFuture { + let id: ModelType.IDValue = try req.getId() + let model = try req.content.decode(ModelType.self) + + let temp = model + temp.id = id + return temp.update(on: db).map { + return } } - func delete(_ req: Request) throws -> Future { - let id: ModelType.ID = try req.getId() + func delete(_ req: Request) throws -> EventLoopFuture { + let id: ModelType.IDValue = try req.getId() return ModelType - .find(id, on: req) + .find(id, on: db) .unwrap(or: Abort(.notFound)) .flatMap { model in - return model.delete(on: req).transform(to: HTTPStatus.ok) + return model.delete(on: self.db).transform(to: HTTPStatus.ok) } } } diff --git a/Sources/CrudRouter/ControllerProtocols/CrudParentControllerProtocol.swift b/Sources/CrudRouter/ControllerProtocols/CrudParentControllerProtocol.swift index e75ea88..94d6cb4 100644 --- a/Sources/CrudRouter/ControllerProtocols/CrudParentControllerProtocol.swift +++ b/Sources/CrudRouter/ControllerProtocols/CrudParentControllerProtocol.swift @@ -2,32 +2,34 @@ import Vapor import Fluent public protocol CrudParentControllerProtocol { - associatedtype ParentType: Model & Content where ParentType.ID: Parameter - associatedtype ChildType: Model & Content where ChildType.ID: Parameter, ChildType.Database == ParentType.Database + associatedtype ParentType: Model & Content where ParentType.IDValue: LosslessStringConvertible + associatedtype ChildType: Model & Content where ChildType.IDValue: LosslessStringConvertible + + var db: Database { get } - var relation: KeyPath> { get } + var relation: KeyPath> { get } - func index(_ req: Request) throws -> Future - func update(_ req: Request) throws -> Future + func index(_ req: Request) throws -> EventLoopFuture + func update(_ req: Request) throws -> EventLoopFuture } public extension CrudParentControllerProtocol { - func index(_ req: Request) throws -> Future { - let childId: ChildType.ID = try req.getId() + func index(_ req: Request) throws -> EventLoopFuture { + let childId: ChildType.IDValue = try req.getId() - return ChildType.find(childId, on: req).unwrap(or: Abort(.notFound)).flatMap { child in - child[keyPath: self.relation].get(on: req) + return ChildType.find(childId, on: db).unwrap(or: Abort(.notFound)).flatMap { child in + child[keyPath: self.relation].get(on: self.db) } } - func update(_ req: Request) throws -> Future { - let childId: ChildType.ID = try req.getId() + func update(_ req: Request) throws -> EventLoopFuture { + let childId: ChildType.IDValue = try req.getId() return ChildType - .find(childId, on: req) + .find(childId, on: db) .unwrap(or: Abort(.notFound)) .flatMap { child in - return child[keyPath: self.relation].get(on: req) + return child[keyPath: self.relation].get(on: self.db) } } } diff --git a/Sources/CrudRouter/ControllerProtocols/CrudSiblingsControllerProtocol.swift b/Sources/CrudRouter/ControllerProtocols/CrudSiblingsControllerProtocol.swift index 3f8fdd7..6eab97a 100644 --- a/Sources/CrudRouter/ControllerProtocols/CrudSiblingsControllerProtocol.swift +++ b/Sources/CrudRouter/ControllerProtocols/CrudSiblingsControllerProtocol.swift @@ -2,55 +2,55 @@ import Vapor import Fluent public protocol CrudSiblingsControllerProtocol { - associatedtype ParentType: Model & Content where ParentType.ID: Parameter - associatedtype ChildType: Model & Content where ChildType.ID: Parameter, ChildType.Database == ParentType.Database - associatedtype ThroughType: ModifiablePivot where - ThroughType.Database: JoinSupporting, - ChildType.Database == ThroughType.Database + associatedtype ParentType: Model & Content where ParentType.IDValue: LosslessStringConvertible + associatedtype ChildType: Model & Content where ChildType.IDValue: LosslessStringConvertible + associatedtype ThroughType: Model + + var db: Database { get } var siblings: KeyPath> { get } - func index(_ req: Request) throws -> Future - func indexAll(_ req: Request) throws -> Future<[ChildType]> - func update(_ req: Request) throws -> Future + func index(_ req: Request) throws -> EventLoopFuture + func indexAll(_ req: Request) throws -> EventLoopFuture<[ChildType]> + func update(_ req: Request) throws -> EventLoopFuture } public extension CrudSiblingsControllerProtocol { - func index(_ req: Request) throws -> Future { - let parentId: ParentType.ID = try req.getId() - let childId: ChildType.ID = try req.getId() + func index(_ req: Request) throws -> EventLoopFuture { + let parentId: ParentType.IDValue = try req.getId() + let childId: ChildType.IDValue = try req.getId() - return ParentType.find(parentId, on: req).unwrap(or: Abort(.notFound)).flatMap { parent -> Future in + return ParentType.find(parentId, on: db).unwrap(or: Abort(.notFound)).flatMap { parent -> EventLoopFuture in return try parent[keyPath: self.siblings] - .query(on: req) + .query(on: self.db) .filter(\ChildType.fluentID == childId) .first() .unwrap(or: Abort(.notFound)) } } - func indexAll(_ req: Request) throws -> Future<[ChildType]> { - let parentId: ParentType.ID = try req.getId() + func indexAll(_ req: Request) throws -> EventLoopFuture<[ChildType]> { + let parentId: ParentType.IDValue = try req.getId() - return ParentType.find(parentId, on: req).unwrap(or: Abort(.notFound)).flatMap { parent -> Future<[ChildType]> in + return ParentType.find(parentId, on: db).unwrap(or: Abort(.notFound)).flatMap { parent -> EventLoopFuture<[ChildType]> in let siblingsRelation = parent[keyPath: self.siblings] return try siblingsRelation - .query(on: req) + .query(on: self.db) .all() } } - func update(_ req: Request) throws -> Future { - let parentId: ParentType.ID = try req.getId() - let childId: ChildType.ID = try req.getId() + func update(_ req: Request) throws -> EventLoopFuture { + let parentId: ParentType.IDValue = try req.getId() + let childId: ChildType.IDValue = try req.getId() return ParentType - .find(parentId, on: req) + .find(parentId, on: db) .unwrap(or: Abort(.notFound)) - .flatMap { parent -> Future in + .flatMap { parent -> EventLoopFuture in return try parent[keyPath: self.siblings] - .query(on: req) + .query(on: self.db) .filter(\ChildType.fluentID == childId) .first() .unwrap(or: Abort(.notFound)) @@ -58,7 +58,7 @@ public extension CrudSiblingsControllerProtocol { return try req.content.decode(ChildType.self).flatMap { newChild in var temp = newChild temp.fluentID = oldChild.fluentID - return temp.update(on: req) + return temp.update(on: self.db) } } } @@ -66,26 +66,26 @@ public extension CrudSiblingsControllerProtocol { public extension CrudSiblingsControllerProtocol where ThroughType.Left == ParentType, ThroughType.Right == ChildType { - func create(_ req: Request) throws -> Future { - let parentId: ParentType.ID = try req.getId() + func create(_ req: Request) throws -> EventLoopFuture { + let parentId: ParentType.IDValue = try req.getId() - return ParentType.find(parentId, on: req).unwrap(or: Abort(.notFound)).flatMap { parent -> Future in + return ParentType.find(parentId, on: db).unwrap(or: Abort(.notFound)).flatMap { parent -> EventLoopFuture in return try req.content.decode(ChildType.self).flatMap { child in let relation = parent[keyPath: self.siblings] - return relation.attach(child, on: req).transform(to: child) + return relation.attach(child, on: self.db).transform(to: child) } } } - func delete(_ req: Request) throws -> Future { - let parentId: ParentType.ID = try req.getId() - let childId: ChildType.ID = try req.getId() + func delete(_ req: Request) throws -> EventLoopFuture { + let parentId: ParentType.IDValue = try req.getId() + let childId: ChildType.IDValue = try req.getId() return ParentType .find(parentId, on: req) .unwrap(or: Abort(.notFound)) - .flatMap { parent -> Future in + .flatMap { parent -> EventLoopFuture in let siblingsRelation = parent[keyPath: self.siblings] return try siblingsRelation .query(on: req) @@ -101,10 +101,10 @@ ThroughType.Right == ChildType { public extension CrudSiblingsControllerProtocol where ThroughType.Right == ParentType, ThroughType.Left == ChildType { - func create(_ req: Request) throws -> Future { - let parentId: ParentType.ID = try req.getId() + func create(_ req: Request) throws -> EventLoopFuture { + let parentId: ParentType.IDValue = try req.getId() - return ParentType.find(parentId, on: req).unwrap(or: Abort(.notFound)).flatMap { parent -> Future in + return ParentType.find(parentId, on: req).unwrap(or: Abort(.notFound)).flatMap { parent -> EventLoopFuture in return try req.content.decode(ChildType.self).flatMap { child in return child.create(on: req) @@ -115,14 +115,14 @@ ThroughType.Left == ChildType { } } - func delete(_ req: Request) throws -> Future { - let parentId: ParentType.ID = try req.getId() - let childId: ChildType.ID = try req.getId() + func delete(_ req: Request) throws -> EventLoopFuture { + let parentId: ParentType.IDValue = try req.getId() + let childId: ChildType.IDValue = try req.getId() return ParentType .find(parentId, on: req) .unwrap(or: Abort(.notFound)) - .flatMap { parent -> Future in + .flatMap { parent -> EventLoopFuture in let siblingsRelation = parent[keyPath: self.siblings] return try siblingsRelation .query(on: req) diff --git a/Sources/CrudRouter/ControllerProtocols/Crudable.swift b/Sources/CrudRouter/ControllerProtocols/Crudable.swift index 1fb7e3e..e40cf47 100644 --- a/Sources/CrudRouter/ControllerProtocols/Crudable.swift +++ b/Sources/CrudRouter/ControllerProtocols/Crudable.swift @@ -2,17 +2,17 @@ import Vapor import Fluent public protocol Crudable: ControllerProtocol { - associatedtype ChildType: Model, Content where ChildType.ID: Parameter + associatedtype ChildType: Model, Content where ChildType.IDValue: Parameter func crud( at path: PathComponentsRepresentable..., - parent relation: KeyPath>, + parent relation: KeyPath>, _ either: OnlyExceptEither, relationConfiguration: ((CrudParentController) -> Void)? ) where ParentType: Model & Content, ChildType.Database == ParentType.Database, - ParentType.ID: Parameter + ParentType.IDValue: LosslessStringConvertible func crud( at path: PathComponentsRepresentable..., @@ -22,7 +22,7 @@ public protocol Crudable: ControllerProtocol { ) where ChildChildType: Model & Content, ChildType.Database == ChildChildType.Database, - ChildChildType.ID: Parameter + ChildChildType.IDValue: Parameter func crud( at path: PathComponentsRepresentable..., @@ -31,11 +31,8 @@ public protocol Crudable: ControllerProtocol { relationConfiguration: ((CrudSiblingsController) -> Void)? ) where ChildChildType: Content, - ChildType.Database == ThroughType.Database, - ChildChildType.ID: Parameter, - ThroughType: ModifiablePivot, - ThroughType.Database: JoinSupporting, - ThroughType.Database == ChildChildType.Database, + ChildChildType.IDValue: LosslessStringConvertible, + ThroughType: Model, ThroughType.Left == ChildType, ThroughType.Right == ChildChildType @@ -46,11 +43,8 @@ public protocol Crudable: ControllerProtocol { relationConfiguration: ((CrudSiblingsController) -> Void)? ) where ChildChildType: Content, - ChildType.Database == ThroughType.Database, - ChildChildType.ID: Parameter, - ThroughType: ModifiablePivot, - ThroughType.Database: JoinSupporting, - ThroughType.Database == ChildChildType.Database, + ChildChildType.IDValue: LosslessStringConvertible, + ThroughType: Model, ThroughType.Right == ChildType, ThroughType.Left == ChildChildType } @@ -58,14 +52,14 @@ public protocol Crudable: ControllerProtocol { extension Crudable { public func crud( at path: PathComponentsRepresentable..., - parent relation: KeyPath>, + parent relation: KeyPath>, _ either: OnlyExceptEither = .only([.read, .update]), relationConfiguration: ((CrudParentController) -> Void)?=nil ) where ParentType: Model & Content, ChildType.Database == ParentType.Database, - ParentType.ID: Parameter { - let baseIdPath = self.path.appending(ChildType.ID.parameter) + ParentType.IDValue: LosslessStringConvertible { + let baseIdPath = self.path.appending(ChildType.IDValue.parameter) let adjustedPath = path.adjustedPath(for: ParentType.self) let fullPath = baseIdPath.appending(adjustedPath) @@ -81,7 +75,7 @@ extension Crudable { controller = CrudParentController(relation: relation, path: fullPath, router: self.router, activeMethods: allMethods.subtracting(Set(methods))) } - do { try controller.boot(router: self.router) } catch { fatalError("I have no reason to expect boot to throw") } + do { try controller.boot(routes: self.router) } catch { fatalError("I have no reason to expect boot to throw") } relationConfiguration?(controller) } @@ -94,8 +88,8 @@ extension Crudable { ) where ChildChildType: Model & Content, ChildType.Database == ChildChildType.Database, - ChildChildType.ID: Parameter { - let baseIdPath = self.path.appending(ChildType.ID.parameter) + ChildChildType.IDValue: LosslessStringConvertible { + let baseIdPath = self.path.appending(ChildType.IDValue.parameter) let adjustedPath = path.adjustedPath(for: ChildChildType.self) let fullPath = baseIdPath.appending(adjustedPath) @@ -110,7 +104,7 @@ extension Crudable { controller = CrudChildrenController(childrenRelation: relation, path: fullPath, router: self.router, activeMethods: allMethods.subtracting(Set(methods))) } - do { try controller.boot(router: self.router) } catch { fatalError("I have no reason to expect boot to throw") } + do { try controller.boot(routes: self.router) } catch { fatalError("I have no reason to expect boot to throw") } relationConfiguration?(controller) } @@ -122,14 +116,11 @@ extension Crudable { relationConfiguration: ((CrudSiblingsController) -> Void)?=nil ) where ChildChildType: Content, - ChildType.Database == ThroughType.Database, - ChildChildType.ID: Parameter, - ThroughType: ModifiablePivot, - ThroughType.Database: JoinSupporting, - ThroughType.Database == ChildChildType.Database, + ChildChildType.IDValue: LosslessStringConvertible, + ThroughType: Model, ThroughType.Left == ChildType, ThroughType.Right == ChildChildType { - let baseIdPath = self.path.appending(ChildType.ID.parameter) + let baseIdPath = self.path.appending(ChildType.IDValue.parameter) let adjustedPath = path.adjustedPath(for: ChildChildType.self) let fullPath = baseIdPath.appending(adjustedPath) @@ -145,7 +136,7 @@ extension Crudable { controller = CrudSiblingsController(siblingRelation: relation, path: fullPath, router: self.router, activeMethods: allMethods.subtracting(Set(methods))) } - do { try controller.boot(router: self.router) } catch { fatalError("I have no reason to expect boot to throw") } + do { try controller.boot(routes: self.router) } catch { fatalError("I have no reason to expect boot to throw") } relationConfiguration?(controller) } @@ -157,14 +148,11 @@ extension Crudable { relationConfiguration: ((CrudSiblingsController) -> Void)?=nil ) where ChildChildType: Content, - ChildType.Database == ThroughType.Database, - ChildChildType.ID: Parameter, - ThroughType: ModifiablePivot, - ThroughType.Database: JoinSupporting, - ThroughType.Database == ChildChildType.Database, + ChildChildType.IDValue: LosslessStringConvertible, + ThroughType: Model, ThroughType.Right == ChildType, ThroughType.Left == ChildChildType { - let baseIdPath = self.path.appending(ChildType.ID.parameter) + let baseIdPath = self.path.appending(ChildType.IDValue.parameter) let adjustedPath = path.adjustedPath(for: ChildChildType.self) let fullPath = baseIdPath.appending(adjustedPath) @@ -179,7 +167,7 @@ extension Crudable { controller = CrudSiblingsController(siblingRelation: relation, path: fullPath, router: self.router, activeMethods: allMethods.subtracting(Set(methods))) } - do { try controller.boot(router: self.router) } catch { fatalError("I have no reason to expect boot to throw") } + do { try controller.boot(routes: self.router) } catch { fatalError("I have no reason to expect boot to throw") } relationConfiguration?(controller) } diff --git a/Sources/CrudRouter/CrudChildrenController.swift b/Sources/CrudRouter/CrudChildrenController.swift index d096b83..2df2ac7 100644 --- a/Sources/CrudRouter/CrudChildrenController.swift +++ b/Sources/CrudRouter/CrudChildrenController.swift @@ -1,19 +1,21 @@ import Vapor import Fluent -public struct CrudChildrenController: CrudChildrenControllerProtocol, Crudable where ChildT.ID: Parameter, ParentT.ID: Parameter, ChildT.Database == ParentT.Database { +public struct CrudChildrenController: CrudChildrenControllerProtocol, Crudable where ChildT.IDValue: LosslessStringConvertible, ParentT.IDValue: LosslessStringConvertible { + public var db: Database + public var router: RoutesBuilder + public typealias ParentType = ParentT public typealias ChildType = ChildT public var children: KeyPath> public let path: [PathComponentsRepresentable] - public let router: Router let activeMethods: Set init( childrenRelation: KeyPath>, path: [PathComponentsRepresentable], - router: Router, + router: RoutesBuilder, activeMethods: Set ) { self.children = childrenRelation @@ -24,9 +26,9 @@ public struct CrudChildrenController: CrudControllerProtocol, Crudable where ModelT.ID: Parameter { +public struct CrudController: CrudControllerProtocol, Crudable where ModelT.IDValue: LosslessStringConvertible { public typealias ChildType = ModelT - public typealias ModelType = ModelT + public let db: Database public let path: [PathComponentsRepresentable] - public let router: Router + public let router: RoutesBuilder let activeMethods: Set - init(path: [PathComponentsRepresentable], router: Router, activeMethods: Set) { + init(path: [PathComponentsRepresentable], router: RoutesBuilder, activeMethods: Set) { let adjustedPath = path.adjustedPath(for: ModelType.self) self.path = adjustedPath @@ -25,9 +25,9 @@ public struct CrudController: CrudControllerProtocol, C } extension CrudController: RouteCollection { - public func boot(router: Router) throws { + public func boot(routes router: RoutesBuilder) throws { let basePath = self.path - let baseIdPath = self.path.appending(ModelType.ID.parameter) + let baseIdPath = self.path.appending(ModelType.IDValue.parameter) self.activeMethods.forEach { $0.register( diff --git a/Sources/CrudRouter/CrudParentController.swift b/Sources/CrudRouter/CrudParentController.swift index fecb806..1eebbb7 100644 --- a/Sources/CrudRouter/CrudParentController.swift +++ b/Sources/CrudRouter/CrudParentController.swift @@ -1,19 +1,21 @@ import Vapor import Fluent -public struct CrudParentController: CrudParentControllerProtocol, Crudable where ChildT.ID: Parameter, ParentT.ID: Parameter, ChildT.Database == ParentT.Database { +public struct CrudParentController: CrudParentControllerProtocol, Crudable where ChildT.IDValue: LosslessStringConvertible, ParentT.IDValue: LosslessStringConvertible { + public var db: Database + public typealias ParentType = ParentT public typealias ChildType = ChildT - public let relation: KeyPath> + public let relation: KeyPath> public let path: [PathComponentsRepresentable] - public let router: Router + public let router: RoutesBuilder let activeMethods: Set init( - relation: KeyPath>, + relation: KeyPath>, path: [PathComponentsRepresentable], - router: Router, + router: RoutesBuilder, activeMethods: Set ) { self.relation = relation @@ -24,7 +26,7 @@ public struct CrudParentController: CrudSiblingsControllerProtocol, Crudable where - ChildT.ID: Parameter, - ParentT.ID: Parameter, - ChildT.Database == ParentT.Database, - ThroughT.Database: JoinSupporting, - ThroughT.Database == ChildT.Database { - +public struct CrudSiblingsController: CrudSiblingsControllerProtocol, Crudable where + ChildT.IDValue: LosslessStringConvertible, + ParentT.IDValue: LosslessStringConvertible { + public var db: Database + public typealias ThroughType = ThroughT public typealias ParentType = ParentT public typealias ChildType = ChildT public var siblings: KeyPath> public let path: [PathComponentsRepresentable] - public let router: Router + public let router: RoutesBuilder let activeMethods: Set init( siblingRelation: KeyPath>, path: [PathComponentsRepresentable], - router: Router, + router: RoutesBuilder, activeMethods: Set ) { self.siblings = siblingRelation @@ -34,9 +32,9 @@ extension CrudSiblingsController: RouteCollection {} public extension CrudSiblingsController where ThroughType.Right == ParentType, ThroughType.Left == ChildType { - public func boot(router: Router) throws { + func boot(routes router: RoutesBuilder) throws { let parentPath = self.path - let parentIdPath = self.path.appending(ParentType.ID.parameter) + let parentIdPath = self.path.appending(ParentType.IDValue.parameter) self.activeMethods.forEach { $0.register( @@ -51,9 +49,9 @@ ThroughType.Left == ChildType { public extension CrudSiblingsController where ThroughType.Left == ParentType, ThroughType.Right == ChildType { - public func boot(router: Router) throws { + func boot(routes router: RoutesBuilder) throws { let parentPath = self.path - let parentIdPath = self.path.appending(ParentType.ID.parameter) + let parentIdPath = self.path.appending(ParentType.IDValue.parameter) self.activeMethods.forEach { $0.register( @@ -67,9 +65,9 @@ ThroughType.Right == ChildType { } public extension CrudSiblingsController { - public func boot(router: Router) throws { + func boot(routes router: RoutesBuilder) throws { let parentPath = self.path - let parentIdPath = self.path.appending(ParentType.ID.parameter) + let parentIdPath = self.path.appending(ParentType.IDValue.parameter) router.get(parentIdPath, use: self.index) router.get(parentPath, use: self.indexAll) diff --git a/Sources/CrudRouter/Extensions/Request.swift b/Sources/CrudRouter/Extensions/Request.swift index 18a52f9..1de28c7 100644 --- a/Sources/CrudRouter/Extensions/Request.swift +++ b/Sources/CrudRouter/Extensions/Request.swift @@ -2,8 +2,8 @@ import Vapor import Fluent extension Request { - func getId() throws -> T { - guard let id = try self.parameters.next(T.self) as? T else { fatalError() } + func getId() throws -> T where T: LosslessStringConvertible { + guard let id: T = self.parameters.get("id") else { fatalError() } return id } diff --git a/Sources/CrudRouter/Extensions/Router+CRUD.swift b/Sources/CrudRouter/Extensions/Router+CRUD.swift index 7cea3ae..3054e82 100644 --- a/Sources/CrudRouter/Extensions/Router+CRUD.swift +++ b/Sources/CrudRouter/Extensions/Router+CRUD.swift @@ -7,7 +7,7 @@ public extension Router { register type: ModelType.Type, _ either: OnlyExceptEither = .only([.read, .readAll, .create, .update, .delete]), relationConfiguration: ((CrudController) -> ())?=nil - ) where ModelType.ID: Parameter { + ) where ModelType.IDValue: Parameter { let allMethods: Set = Set([.read, .readAll, .create, .update, .delete]) let controller: CrudController diff --git a/Sources/CrudRouter/Obsoleted/CrudControllerObsoleted.swift b/Sources/CrudRouter/Obsoleted/CrudControllerObsoleted.swift index 594bc66..1defe47 100644 --- a/Sources/CrudRouter/Obsoleted/CrudControllerObsoleted.swift +++ b/Sources/CrudRouter/Obsoleted/CrudControllerObsoleted.swift @@ -6,12 +6,12 @@ extension CrudController { @available(swift, obsoleted: 4.0, renamed: "crud(at:parent:relationConfiguration:)") public func crudRegister( at path: PathComponentsRepresentable..., - forParent relation: KeyPath>, + forParent relation: KeyPath>, relationConfiguration: ((CrudParentController) throws -> Void)?=nil ) throws where ParentType: Model & Content, ModelType.Database == ParentType.Database, - ParentType.ID: Parameter { + ParentType.IDValue: Parameter { fatalError() } } @@ -27,7 +27,7 @@ extension CrudController { ) throws where ChildType: Model & Content, ModelType.Database == ChildType.Database, - ChildType.ID: Parameter { + ChildType.IDValue: Parameter { fatalError() } } @@ -42,7 +42,7 @@ public extension CrudController { ) throws where ChildType: Content, ModelType.Database == ThroughType.Database, - ChildType.ID: Parameter, + ChildType.IDValue: Parameter, ThroughType: ModifiablePivot, ThroughType.Database: JoinSupporting, ThroughType.Database == ChildType.Database, @@ -59,7 +59,7 @@ public extension CrudController { ) throws where ChildType: Content, ModelType.Database == ThroughType.Database, - ChildType.ID: Parameter, + ChildType.IDValue: Parameter, ThroughType: ModifiablePivot, ThroughType.Database: JoinSupporting, ThroughType.Database == ChildType.Database, diff --git a/Sources/CrudRouter/Obsoleted/RouterObsoleted.swift b/Sources/CrudRouter/Obsoleted/RouterObsoleted.swift index 5aa3c0a..d2972ea 100644 --- a/Sources/CrudRouter/Obsoleted/RouterObsoleted.swift +++ b/Sources/CrudRouter/Obsoleted/RouterObsoleted.swift @@ -7,5 +7,5 @@ public extension Router { _ path: PathComponentsRepresentable..., for type: ModelType.Type, relationConfiguration: ((CrudController) throws -> ())?=nil - ) throws where ModelType.ID: Parameter {} + ) throws where ModelType.IDValue: Parameter {} } diff --git a/Tests/CrudRouterTests/CrudRouterTests.swift b/Tests/CrudRouterTests/CrudRouterTests.swift index 5170cdb..4a064ec 100644 --- a/Tests/CrudRouterTests/CrudRouterTests.swift +++ b/Tests/CrudRouterTests/CrudRouterTests.swift @@ -15,28 +15,28 @@ extension PathComponent { } extension Array where Element: Model, Element.Database: TransactionSupporting { - func save(on conn: Element.Database.Connection) -> Future<[Element]> { + func save(on conn: Element.Database.Connection) -> EventLoopFuture<[Element]> { let databaseIdentifier = Element.defaultDatabase! - return conn.transaction(on: databaseIdentifier) { (dbConn) -> EventLoopFuture<[Element]> in + return conn.transaction(on: databaseIdentifier) { (dbConn) -> EventLoopEventLoopFuture<[Element]> in return self.map { $0.save(on: dbConn) }.flatten(on: dbConn) } } - func delete(on conn: Element.Database.Connection) -> Future<[Element]> { + func delete(on conn: Element.Database.Connection) -> EventLoopFuture<[Element]> { let databaseIdentifier = Element.defaultDatabase! - return conn.transaction(on: databaseIdentifier) { (dbConn) -> EventLoopFuture<[Element]> in + return conn.transaction(on: databaseIdentifier) { (dbConn) -> EventLoopEventLoopFuture<[Element]> in return self.map { element in return element.delete(on: dbConn).transform(to: element) }.flatten(on: dbConn) } } - func create(on conn: Element.Database.Connection) -> Future<[Element]> { + func create(on conn: Element.Database.Connection) -> EventLoopFuture<[Element]> { let databaseIdentifier = Element.defaultDatabase! - return conn.transaction(on: databaseIdentifier) { (dbConn) -> EventLoopFuture<[Element]> in + return conn.transaction(on: databaseIdentifier) { (dbConn) -> EventLoopEventLoopFuture<[Element]> in return self.map { $0.create(on: dbConn) }.flatten(on: dbConn) } } @@ -46,11 +46,11 @@ extension Array where Element: Model, Element.Database: TransactionSupporting { struct TestSeeding: SQLiteMigration { static let galaxies = [Galaxy(name: "Milky Way")] - static func prepare(on conn: SQLiteConnection) -> EventLoopFuture { + static func prepare(on conn: SQLiteConnection) -> EventLoopEventLoopFuture { return TestSeeding.galaxies.create(on: conn).transform(to: ()) } - static func revert(on conn: SQLiteConnection) -> EventLoopFuture { + static func revert(on conn: SQLiteConnection) -> EventLoopEventLoopFuture { return TestSeeding.galaxies.delete(on: conn).transform(to: ()) } } From 219c58f43c17a6c0ecc7b0e19f722d6f60cac8dd Mon Sep 17 00:00:00 2001 From: twof Date: Sun, 17 Nov 2019 10:43:53 -0800 Subject: [PATCH 02/38] removed parameter errors --- .../UserInterfaceState.xcuserstate | Bin 33641 -> 47716 bytes Package.resolved | 78 +++++++++++------- Package.swift | 5 +- .../CrudChildrenControllerProtocol.swift | 57 +++++++------ .../ControllerProtocols/Crudable.swift | 29 ++++--- .../CrudRouter/CrudChildrenController.swift | 8 +- Sources/CrudRouter/CrudController.swift | 8 +- Sources/CrudRouter/CrudParentController.swift | 4 +- .../CrudRouter/CrudSiblingsController.swift | 72 ++++++++-------- Sources/CrudRouter/Extensions/Array.swift | 6 +- .../CrudRouter/Extensions/Router+CRUD.swift | 4 +- .../Obsoleted/CrudControllerObsoleted.swift | 70 ---------------- .../Obsoleted/RouterObsoleted.swift | 11 --- .../RouterMethods/ChildrenRouterMethod.swift | 6 +- .../RouterMethods/ParentRouterMethod.swift | 4 +- .../RouterMethods/RouterMethod.swift | 6 +- .../RouterMethods/SiblingRouterMethod.swift | 12 +-- 17 files changed, 165 insertions(+), 215 deletions(-) delete mode 100644 Sources/CrudRouter/Obsoleted/CrudControllerObsoleted.swift delete mode 100644 Sources/CrudRouter/Obsoleted/RouterObsoleted.swift diff --git a/.swiftpm/xcode/package.xcworkspace/xcuserdata/fnord.xcuserdatad/UserInterfaceState.xcuserstate b/.swiftpm/xcode/package.xcworkspace/xcuserdata/fnord.xcuserdatad/UserInterfaceState.xcuserstate index 1c849c53ee1eac43c52a0c6b92fc2dab71ec05fd..cdb32f6d83352d77993a0a1697728a7c42c9d06b 100644 GIT binary patch delta 23030 zcma&O2V4}#8}Pq7d$+gk^iV;t(3^<#BGQY9^dizbs3?j8B8YH16p>yIQDceNG4|ej zkH%i2#%`j<9;1o%oxS7G-)sJ#|Lc8r-JP9h_W8~;^Gw+p9?t|1-hk#*K-_Lgca)FC zCSW;O9+r<4V#U}_Y%jJCJA@s^j$)^r~$R04k$nySPgyxYrtBt4y*?o zz(%kMYzEuGKCmAg2PeQua2{L$kHBN_L=OH0Pr)XpqQ4 zWF>MGxr;nRo}v+=08z3iMU*N^6QzqXM46&2(HPNK(F9SRC|^_{nkp(4m5Jn{YSApY zXp!hg(PB}DXo+a4XqjlaXoYB{=qJ%8(Js+$(H_we(NWP2(M{2BqFbWdqC29yqI;tI zq6eZ!q8FlOCluEl0-?8Bv~>>GFCE9GFdW3 zQZ1P!sgcx5>Lm4&21%o&NzyE7lgyVakSvodm#mPildP8nN{aq0whk~&5GOvz7EXQ*GOv(!22JavJ(NL`{XQ@5!* z)LrTx^@Msu{Y|~4-ckQh|5ER15iO=kT1IQoz3ATbKw6j9qm5`Y+L<0oyU@dESK5>I zp?zt8dITLrN6^u9ES*JX)8pwGbSYg%%W0m5bU9r?SJG|tY`UGEBd6!m^XMPw9rR9m z7rmR_L+_>c(fjEG^g;R%eV9H%AEl4cr|C2F1^Ooa8-0tuP2ZvK(tpxV>1XtF`ZfKQ ze$R*)G1HULWVD!mj4q?c7%|3-IWveE!dNkOj6LJb3}r?z!Av+aib-Ztm{cZ>NoU3| z;~DvMhG%9m^-KfP!n88&%p7Juvw&I5bTG@APG&W8fH}w}BjzddlKIGdVm>opSd8UZDcghX$;wzQwl6z?HD%3MbJmWvXC2sK ztSjryj$k9$NH&U1Vw2@;3Y*Q2XD6`5>~yw-oxzr}Wo$Fs!nU#swvC<5wzG5Cx$GkL zM|LH~3}sdxSm8o?(AsFR|CzJM8c5Q}#9c7yB>!i4$`qC*gW?eK<`{ zi|fm2b2^*}XUW-c4%|@Ag&W5Cal<)(ZUiR};DWi)Tnrb>#c}amI+wv^a#>t9m(LY& zQ@KKJ8dt>e9OTNm3a*ly#r?p|=N51axkcQM++wbSTgENt4s%Diquep>ICp|O$(`bU z=1y~GxL>%l+&S(dcZIvkUFU9bx48%0bM6KAl6%Fy=H7B2xli0@Z7C(CrBZ2cX&5(2*Dx<4nS}ag2NCT+1yU{#3j5wubm>Z zRD4bxpg1Y%g_{XiL~!m{#d*ol&Gu9yZkUN#bzm8MzYZ*mAHZv0#6qy1SSS{Tg=6D+ zjMw7(E4-cDWY|P3SBN=@AJ~CS=5-Z~PP+07SPvAr0Gqm)*Smm?!lJQhSW!%9ly_cU zTJaKe6MGSpD&tJY=3!RrWx7}iHUlfg$}l;`V-PFHDzHkd3Y&>lW3#XttQM=o>ahl_ z5o^Mlu@fe#91I9oQ0VDYgt-j;+8}Vym!D zY&G^1wgy{^t;5!18?cSoCTugd1>1^k!?yED{4~CnU&wFZ5A&D#NBl>K_lSVFKE&-H z?hEl4h>wGK3B>Cmz8K;gA$|zrmmvNK;_o5gAQ%XN4Fo(R$1i0|L1)9 z{*7A7l>Ad8Nk(b%Dy`-;FGB!n)D@eM2%am{JI}R0dg!*mh_elgO zlZZ!E=MaU5C~p$c%_4btUhd?qyn<|0iPWW1Ev@vjcsL%9S#{zOcqAT$kHVwz(Rd6V zi^uWFdwa_J|A5b2yI6{Rkiia z&CJ?tV`Pj{%*Cd5;FI}rg3CwlAJ508F2f7(slvf$^W&9=o=hYgMj7pYi7EeeH3N7V z4l%7IxE$yCNqp`Syd1CKC-YNKJGMvDz;l+eA(35IH6^Kd{Sqt{itC{x*%zN>D!ogSYmC8d;hRbAX zU60}?RHVld>C7eAWc(Db%UAPx$(gwsQzxS09-K94Y{57U%|2?8v-kxS-g$&q^NolS z>>7Sg0Q(icj^Ds<;=kdy@Z0zu{4QU|*Ygd0Bj3a~^DTTUujs_@cY{4rg8j+2b%V`S zg3a#&dnbVX!_QWOeGtGt;-7?rZ|CPI4?P(t>))j%5CMr243G$H9x?);kqLkJ9*n%L z7U=;rlwd%Hz!rQXqBw8dH>@u(Kwv-{=zxBpKNtW80$rd7^!Y{nkNjf3gI~fg<(Kix z`4yeOPz?sm1TbL1uj~fM>8uE0$=_)dPc^3aosWjbml zeSyD!AP&TX1ds@l zKr%?-H}aeK&HNUAE5D83&hOxNb^@UW3cnOEMhTY9@9GBIrvy8w0-Gv;74o~)V8u!> zya3Dqr3fmU-^1_a^<)wmsn)U_RH?u!5!ikq_p6b?4tx*BIjKeJL6Zus5rG~0RzwLl z8~iAMwSzfeE|>>?0Q12Dun;WbkMKwNWBhUc1b>o0#sAEo?gWcfU|<;B z@w)wVPJuHjkkbg{(zj2Al6MjOCU9H=m%$Zq6{5{OxW7-B)t_t}@VTf#Wa!j+*1GV4!#4AK~Ed^7oX7o{W`s7o#r( zP$CdGLU@33n-C!bJ^bE4;)E^(5j4Rmxd;|<{qe1c!be*(oajXiKx9O3q7R`-Xc2u0 zZ9<3WNA%|(^H2Cc`KSCd{yG1Gf62ev)MB=Dd3&-@pNV-Uw7 z4j@iITm*4(Cy~@ennt7}$DK%IL7Y^fLY#s)jlB2-h;v=EQv}*Ph&p$whK@j0ZyEB8 z`-B5|#!BU(XXMrAe_=}rUIi=XCqbMMXv_J@5NE%~?HQnkttM(!ur>Tth)b8?%vUJ^ zH<4&2eh_e5h*m;Dv=Ot3c47`OmzW3fo)DKoTm$00Al@6|eITw0aV?1V?Ih-_OD(Zj zD7C~=h--HlHwU85B5LL#uGq30s_pE(?Qwtp>luctZaa>^4>k<+xrBtlH5Z46Ov&1>#JaK`zNL(T= z6IY0<5I2ChA;gU!ZVYh~h?_#(4C3YxAJj?w+Rb`P$$A&!7GGJ1s90^3tYO46f%Q4W z2di0MDXS^*7kEef#UoKm)k#m*TdnrL#77nD2Z7Z}Q2VpMYK?r&B4eEaB0@weZ7328 zT(i)AqTZrDB4jLkh&w1YSZkVhiu$T4L<0m0ksid| zzEXIoD7;k`6AeZbB1?$7cTtF}l@$11@DAUL98HDt(CZ=)If-191frn=fhST>BT)H#BZTKE5LUuSQIvocC<+n2`HLAs(s1jPAl5CtzknJXDRDgD^!CMUzCi zcpAjRARZ3!2qdE?>m$>bX{uE(6cwv@iv->%R1-xdLQNd?AH0XuLcFL##ak}$juvWS zx0I5%Mx+pUYejXUdQpR@QPd=A7PW|4As!3yIEcqXJOSbeC<)@p5Kn=4YNx2Jn|Gd) zcL78$vzs?V#XF{pca^}~3Gs9_?;63ZsDW2D@# znh9rQS3>5X*8H{TZx!wv0e709`8xr(=zCoHg<9yN=!**Xvw%At3DMnBN}nSZi+dto zF)5aaDKRZ(#H^STOT|4PJ_F*V5HEwc9O690A;ileUIFpSPO(hQE7lZv#o7?B`pR3a z;;mKk28oRkuNXP4nO(eMbHt0pls^=;tBcd6ve;T|r{omd3Y@c0#S=RSwY3KMn_z35 z0b&=io03WFDlpakPXyFtB#V8-!2*)6*iSrM>@OZ64iJwN2Z~V*Y=C$p#G4@A4Dl9- zqq3`jcpJoLcZx$)Na6@_q&Ny8MMJz@g#>Zb*!%$Tg+imTNM%HEngEp!@i}U!ECeMU zgMSnbh|lHQl_z9EGb1mH|7j`3lf+Y0gp&or`N$~Ze8G|nzPDsgBblx`V~WKyRNN)J z41xdnt(?l1<>Gn)yh2H5A_` zdV=pmb(61Dp7?Ig$Kt0d&OZgteS*%<1N|W7C;Yn3lT_4Mf#B> z1=3IA&*Cp6M&cwO2~tFgA$}O*M<9L_;>RF<9O8)mB*afa{O3+mq9!Fdft2hC@zY;P z&#Fi-s7T5Fh?E=v@iSedq#hzA^~F)b0r6i@saKs*L1wxXC(TF;B`rBfpgo5aCoKiq z^WW2o)#GQ`I^o96kh+ly? z>IPkdIC8DmA$|kmHzEF8Cplb2OO7N1$sj}<0`XhwYKyuv_aOdIsJ6l&1$E`gIDr?n zRJYZsm_Sk#K?>LpNdP4A+uHB;{@>g0>cCW;lcOdk`^#sv{wb% zYY>RlX1*cN-o!r&2Sigi?aC8k1!}eLlfSE2A0k#j32Hw=tZ0_!yQwSEQ!Vt2e5qo6 zfmi|CB_vi#saW4iFoE?Q`49Oo`JVhhek4DUpGgEKg`funJt2@mpaDTI2zo=%2LjDb z39e?9kOHfOhCu5ptG0@@zlv3&iC86C5cKV0mFOT=P>QBTO35J9XUgXH3Y9?Zt0ejo zBPFQBPyp3II!jCh(0<>8;yb%QB^Ht)N>GWV06O5S5KtL4Nn$T?N1zf1iKE0x;w%{| zaghv@xJuk0(1kz`0(}S!ATWf$2m)gWOdv4rlz6B>B|Z{gG>w`l@rS^ytM&p52nHkN zCqQ7;MH(rPMnPb%CLN7P@qLn5;Xpna3{su+WCM)6R5e#UUm{77q^qdY1ZqnGAXA_o z@;$X^ubMhrlB1%YfT()_>u;r$=9T10W(d^zk^;$8Nugw#q)1XMnJy`Tz!m~K2<#zn zfWQ#~CkUJ&7z%+)r=+yoyyZ&sRzWcAt9jj3=Jo1=ZV{kcA#hbg&sLgOG6$4O<{;Ce zc|GMxPe!ZNUMN|tV*OEI^$@gPBCvXX&k6$7LMtV!Rji$Yg}u9kP*FxwD%K5>T>|Sy z$tKBW$rj00$u`M$$qva*2z(*%gJ3uW$bKUr2!LQD1c49)bxL-3vmQ{gqHnapUs=Oc ztdU);X9d=C5QL~%FREBCBi73TW~l0Pf)HXwQYzNxl79r&7m}BfSCZF~za(!Ye@os<-a!xz!Dt9#Ac%z^4uW_H z5+F!~AgNRGZ#U~_ft5nOEBPyHs){v3#Y#yLE7b#{X;IZEfYLy$k~w&RWDXf5ST!4+ zzOA^FHq~E=O7#;^(~#EGKmj%VdsOhL3zagYOq8gUv4AQJl9WOsK}tYPrUp}v2$iy= zhEP_NHDyEDQg)O*g^KMM2*yG%4g#dgcnDBU%zao$CY2LJX2-mnlA7<0lXf9 zDm8eMP@><7CJKiz)e@#Usl7ARPR&!X&J|dz1$)gGj6Cam);{mlq>HJgD$*qaXl=I; zfusb|K2#_56Q~sa+5> zLC_3A3k1mdC?IHqU^WD(-JH`&?ZHft&G%CWsDsoY>M&*k!CbzFVDNbm`~bmx2o^xF z5C!FJ4sfP$Q|bzJ9kW_WU8Sy3ze4aM1dAc)SW4ZXZc@KNumpnr5Uf!Y4xJQ5-KQQe zryfubso$wTs7DYig#f9&7J`isY=dB*8v0M_g^=V^>KXMMf@KgahhW80>LvAxdJVx! z2v$MRDSXN^RTzZmDxzKLlBf^Vm;aEF_|UkZh6MyaAvJjI2>oc~m-((&(Gr^d4>6jf zrF0Jn)8CFf?zWQTX=0F&#o_o8JNBa!1OQB`dCle zfHvH`a@buBIoPWF7DyY@CP|@DAyW&|(sQ!>(hAa+q99W&c64ZzaHrPKoVLcSR!~oA z3wkj1oE}13DKlg{1UpckK!9BIt`*cvd@pTB+oLI9FzsW~LSErxobi+6;4W)DI6)Mf9VGCwHSL7s@Q9{V=ptM)pKG3ql7_k2|nl zJn_}Uv^$`RpH ze(!MQr)GKrosU_qq;u$r^dvf$o=i`n^B_0@!AS^CLGUvKry)24!7nT60(vT*NKd0D z3bh4-vk;s^RfM>O;tG{z^XX6=PuDIfv9lR!sXxS~)Kb61*3RC}Qs2eKrnFRT+bSB3 z=~vJ*>1uiwT|?K>br76~-~t2}A-Dv=WeBdUpwS?nZlasfxfOz|!fS#d5hMJ6Ltz=- zDyQeuotV{HdI7zVUPS*$FQz-_CG=8y8NHldL9e7yT|wPtR5hiG`$VM z9SH71a1Vm}5Ik6mr_n#rYv{G~I)RGbi0(JjTj;HLqTs_HLhw5TuOa#nq;v;p5CXwl zNU$sU~G7k!*YBl!**<=!71G|Ih4irbO>z35-4$4lt5^f?F~ zL-698@^F#9ifJvSFVUCjD-b+^;7i!SuE)hYECQi9-< zQZPlPtMJm$?S81QdibU5p)q=BBJWbc1W!YP=#w2X?1vOt@2|^)&?=3B+>G3ueDyml zR8a|K)Og>#shO(vAL{4Ul6cI7bA|`|hRn*5=0wzqcE5^=^i9r5` ziG&3AwQw@gOdKk3%xETtiG_p|5gyzGYLJ&WhOIIm^>z*DPX2Dg-kE3jwx0gj$JP& z93Wu`2_s0LY;+Wgfj1=VzbS)E2{S_|gQ$;1=tDwNRsQ&VrJH0RHg!2u&Qvg!OcgT| z5?YYZhD1L|41k30*FsmLEDc0ob!iX`sa(*b7^yKpT`U?^*3$WZC>9D;RP+%J8TLPl z#avaf82I1CVxdY}uj@ra{_kROk#RwJ=81F6QY?)3frJ5%_G+Ldn#>AjrDA=&7O{j` zrC1y9n9BUbY(-rkW(~8JS;wqrHZU8RP0VIy3nYvoVFC$LNSHyw91?>dVF8K3kg)7z zwyAo5;s|E9Fip(tgT#=ojxU-fCahJ{#Dp#C`5f$Li-$0$P!Ev#84^}qJwWCc)C2sl ziQ+D2!dzyqDSLp-Rn!9{Y*1&edv;jp%MD;|F?W@{J?0MT?GbkW6X9v1Cy^o(YOFD> z!hXy=Q6wkzMc98T5|eaA%roY>qAW>A#JoU0p)5&Tu72?=<}GHmjCsxc#k^tuhQv@v zxIkhUBwUv<@0fp3F?bINH%NG+l0XFV+Ofe|V+uxRXH83yX`;?U)a0CO<*i><`qZ)B zd0A=k!D+>4xnl@gLlqa9J$9V(Y1i9WoJDh+%UHnb6Re08L&6;r9+2>agqN^BDGi&9 znV^i#M@w!algoRt8f?S^wH2%mi%P8@B+%GX-92OnvIhU* z|5-!Kghl2Zj@k;spU>-NF!HL{pd59m^&7;Z8ISMU57r9F+dv{fkRQp*^S*)^d6l2| zpA;NfXF+2pNCb7DuU`2=>!*SIrLd;XKRZWQ(28Da<<7vgReBIKetOV}O4?^Z3ShUWlgWZZ$?qF|0V%pb7ciH>GBc$Mz z4)y^gioQPjgMBPKLaO9< zMW0mEc6tYi8DI5$&wfz#(bQ>u=5PV}3x|4IWss0B;Q&WKf``OXg*40FhNCzs@=zSj zF&xWrkbsaVhXnF{mCNY0Tu)BMX$XB?q6!kIv{fr|vx>F3eq4WU05_1+#d>mjoIW;` zGvtgoy9#jYL_otIDyQ_hSt=LTV6cmp?B5k1B%hN$C_J~bE6!y%j% zXB~$|Uik%iXvsJvYNNxm3v&yUex0-B>`)Qx9#KUp<19tN_E>CeXq4(+71fb*QrsA$ z12|{J+c6$yoGa&vswn5ixpTC z+(K0kRobKX=lD=v!BGz{nw*9@^O++HvL>o}Yupmm2v@Oh zyqTO^!EHgKIc_DlitFT7b3buwxV79mZauew+sJL=kc&i>ZW|=FLt+Occ0ytoBz8k$ z4y*mJjgKN31+%|4Iw}V6DHx5nJqA?v>AjTcw4nhKXj3baZ3W;OrTJVo2 zluT%?7zGJb(XS(<)Euq8T1a12bB4x9s8O4wDtd}36JE%Z1s9o}kM?V1MWQw5D%+mt zE-Z$`VO80@#9fX?r8TQy33o}DKG4cQ-Ah3LmH7OO?!5;p$!p(?^0^z_g%0i}B#xuB zQe;M7z1pa({DS-_VLymm-#2&S@1k@n6b?y9-f4gO%d-?yjfvTMVDKZ`JiRsy6r{+%0 z*Poi7rJp-S-wg4i%?oBqt4p;|agp|g#IMTY()0f+E~tN0QetDNubhQZ{;!&V>4Wu@ z4v^}iMn^gj5;r@fdXV@{aXDwOrqrmbFbXqwQd8P6OhYtgv*yGA9GGS(QYH0`r@d^Wnv0$AqewUYwz zUf)kKDagrdqpQ%S@keM)=V!Fbdmu1DJG`CI&Td!W4m^Q32ml#q2lsTe@~R9hM|-um zfrH=_S}1%IJOoeCCTt7B5^cM-A%chuw8eTFF`bYT^+c0`nxZFP1i@B}!gMf0%o4N3 zhGCvq1U4E=#FEkeRa5kB!4>UU4WS~@e$~-b0+obztEN-M)M9Eano&MP9YK3hPoRCM zr_mPFb7=eNLo^Tk7|rv(pkAYmr~jZy+|M*d_oW?ZZ?u)P4$WxoLVH2~pdZnH(;w;2 zj2vSCno?mIDcb(2fu;q?8Iu`zQMbwfP#HE;kr8!8V*d=g2vu&5{0G02j!~QA5zgwQy}*J2#ix!tLeu zA%}UAJCF7>-bb4hjpb5PskzibYALmn+DPrB4pJxSQ0XwKo76+6<9(g?qdKC63>S@{2 zrKf98_nw|TH}u@wbAQi+JrB$BWxT9hRw&9O-kc&k4-{ z&1lUS%{a{j&7GRZb^C7>$F|91GEFRgSA7o-|I+pXdPBZs810`^EM< z)bDJ+^ZhRNyWD?P|JnWL^q<#%{s4A>_JDo^1`N<0P(Gk(K+6EdfY}4(BLvVc?~KS9Ak(qjh6+<8%}Bdg>*4P}PC4Eq>r84fTUY&g^~$S}k(%rL?*$}rk6#xTw>!7#~i zvSFTKfnlLxk>Pa18HQzsykWUvrD2ER0mBDIq>-gjs8OC#i_r$7okn|%c7@ah_ zV06joiqSQr>qa+?ZW-M%dS>+6=#9}^qYp-(jJ_D-#xmpH#`?x4#%9KYjIE4qjO~ma zjD3y6jWdk1jK>;h8&5EvXq;<2#W>%1s_`u2TH|`-M&oAVR^v9~cH_CmKNv4C-f4Wz z_@jxgiJ!?BlUkEblM^QAO)i;Sk(*pIxoh&oY|5B&raer1 zn`)Z&HPtaSHyvy`)YRS7)70D4-!#B9&@|XI!E~HyiD{{++!UHtm{yrqo7R}tnKqa% zHeF)6%yfn6D$~`bYfRUfZZO?sy2bRo=}WUdW=>{_W@Tne%nq8}F?($G)a<#L{H58y z=D=KJPMTBZj5%lC!(3)Q(A?16*xb~7u=x;kYjaz3FLPh>aPw&M81p#uWb;(>bn{H} zd~@DhVLsb@j`=+E`Q{7Fe>CqfUuwSGe24ii^F8MK%nz6!GCyK|%>0D;Df83jj|WkM zEC+=SDj3v0XxE@$2mND#TZk-33rcRGVbRZGfQ7DwzJ;NMv4yFHxrL*}Fbg*e4+~$5 z;T9t-Mq0#LBv_2Im}rq}F~y?LqR3*p#SDu&i@6p*S**2KZ?Vy0v&B}6?G`&Nc3bSV zIA?Le;*!M`i)$9wEpA%evbbY$&*H&ge6aptzrkY$*AHGd_`={*jljI@lgjI&IzOtnn6%(NV1In}brvdXg7vfi@MQeipUa*pLZ z%T<Pin5Bfim{5bO0Y_@8gG?rHN`65 zs>o`()eNgLs|KrPtA$oetd?1=u=>ert<`#~jaCP&&RE^Cx@Yyk>UXP0R!^*+T0OUV zY4zG#Y%Q^-tyycSbx&&z>)zIy)_tvYtevc*t&6SaTOYK(ZzHiWwHa;`Y7=1-WfN_a zY%|tIo^3P1W};25%@mt_n;AAWHjOsTHmx>uZ06a_w^?Yj#%8_EMw@*$hi#799Je`b z^NY?QI=xhuXT@y4!l%`q=u} zrrGkg%WRL?KDFy%XJ;2-H`%V(Zib!Q4%*e)Ddcvu?dI6cvzu?X&~Am@X1g7ByX^Mb z9kM%Ociirz-8H)#c8~0y*}brPZTHUZU%QWXpY5gg{p^R>+t}ONJK4L~yV-l%``8b+ z53o9E>it;2eU z9S#Q_jyTAVIh=I(+2M@CS%;ercOC9K{O<6l!!w7M4zC??N0Fn3qn4w#V}D0|M?*&w zM>9t!M_e%Mk?l{kJzT*zZ ztBxO>20HmVWjWP2t#UfnQRveIR>%UYKWE}LDpx$Jb=t1iE~+;I8L<&Miemj^C?xI7+44l@}x za@d4nEyFesyE^QxtEQ{2tAVSrtEsEC>rhu$R}WWjS6|oRt|MGW%3Xt8V_lP6Q(V(s z$GT>_=D1FBEp_Ex8(dpm+g#_kE^uAs+Tps?b(8CU*9)$fU9Y*`aJ}Vv*Y$zxAFfYa zpSfXfgqzq+;zql%Zav&&ZoS+z-TJyYxJ9{5bDQV3-|eou*xlIO*FD%h%stY5lzWo< z8254RI~9`}Rphux34pK!nG z{=og6`+N6K9+(H=L3+?0oJUWOULGbM<{lOvmL66fHXimKjvme)!#vzP;yj8x7I^IQ zc;HETT6m819OpU3v%qtjXR&9wXPsxGXNzZ>=N!*@p7T8ydam|dFZbN&xy5s*=Wfq^ zo(DY7dS39n?fJm-chAS3&plsy{^j|%m&i-Q%h=1z%ff4jmyMUbmy?%^mz$TTSEN_8 zSBzJjSAtiPSE^UKSEkokuWYZGUdz2sdj09G;cf37<6Y=I+k26Bhxan?72fN;cX;pe z-s8Q``+)Z$@3Y>Qysvow>Mg(JeaHL0_e1YD-tT-wKC}<()5E8?kEV~dPd}eQK2AO( zeS&?$d?J0KePVqQe3E_Ad@_Be`Aqkj;Zx?r`&9VM^r`Ww^J(;H_F3z5+UKQjFW;fQ z@xEog3w`(a9`!xp`?K#E-z&bieed}`@cqO0vG1S0&wStd5q^{(<0tj&CHL#&*Vj+S z&)jdY-w?lHex82betv!<{et{L{lfiH{Br!{e&v2uezW}Q{2KjQ{M!8H`2FCw)^CH~ zCciCy+x&L;?e^R2x8LuO-x0sN!?EGU!~KWn3~wC1Y54i!FNgm-{NwO1{)dN4F4?uasCtibN%!D3;ZkmoBfyi zuki2mU*o^tf0O@K{~i9j{rCBw_rK(S#s8ZBb^n|GxBc(>-}nFB|IrBX2;&g}BgT(t z9lff3j zBZJ2UPYEsvo)%mjTpnB(+!)*v+!j11cwX@Q;AO!ZgSQ3m2;LohAox)5(ct63SAu^H zz9A2O6#OjsMeyt3cftP#e+>Q{A`R&mG9<(%#6H9+#3jTn#52SvWOztGNJ2<*NNPxW zNM^{GknE5NArnI;hvbDcg{%v?5b`FpU#MGXYN$MPQRvFh)uC%c*N5&1Js5f<^myp0 z&@-WDL(hla3cVltF!WLAv(OizuS4I25n*Il?=ZP`Sii7=VTNJGVP;{2!iI(o4~q>; z2ulu23(E`}8#X>{V%X%c{IHo}HDPsO4Pi}TEn#h8?O}7n=7%i|+ZFa}*r#y4@ZsTO z!|TFVho20;5PmuQTKM(w`{7T+UxdF3e-r*T{Gagm5t0ZlqDO>AgjR%hME{6^5tb3w z5pEIQ5x(*W|A?T7kcjYz$cVIvNf9ujGNL-7HliV-IYJT99x*RsLB#rqO%YonwnglS z*cGuiVt>TJh$9ilBJM|mNYluG$VrjSky|1!M!t^x5cxR@k0PSDD9xz8Q94omqjaP6 zqlQG;ML9${N4Z6LM0rQ~Mny(NN2N!NjmnP7iJB56&yOmMDvGL(YKvMKwK{5T)P|_d zQQM++M(v5(A9X0|a@4h`>rpqOZbjXVdJy${)T5|Bqn?doMhzMjGAeh}>`~iC-5B*g zS|{2t+9cXM+9KLM+AZ26+B@1e+CMrVIxspaIyE{gdTjLg=-lWj(FM_k(N)p2qGw0{ z5WOJ!$LM9zE9B9g(LY7+ias8FJ^HukJJI)}e~*3~{WSVT^y}!qM^mHO(bCa9M{A7k zJz8tD_UL}22aeVo?KV1Lw0v~O=;Naw$4Fx=V}fEv#l*zK$0WvN#Y~Es9Fre2HKr(L zdd!TNnwZ9z<`_lH+?XF?7RLM-vo2<1%)XezF-K!g#GHva8*?E>ektaD%*$9JmW-ui zx!9huy<#c-l}I>b7~4vifa>lW)7>mBPG>mM5sn-yCbyDIin?8~@baZYj3 zaRqUuaeQ1wTvc3S+?=?1aSP%W#dX9jjaweKDQ|MH{n6Tu?`dnWct)JoJz)J@b+G)go{bV&409F-`KNsLcSN=!}6 zNF0-xotTrDn^>M$l~|ovlUSG7nAnoome`&+FL8e2j>M~pACd+p`6gv0)g-M-I-Ybc z>0;8Aq-#lclb$3!OL~#?I_XW)+oXSz-X{~uvSiKVzRCTP^^y&ejgw829g~M94@(}A z9Go1Q9FaUaIW{>VIVm|OxmccDpWKw(nmjvsZu0!(Madn>%aT_n?@HdAyg&J1^5Nv8 z$tRLeC7(_{n|wa`d5Sc}HYF;hIAwmy{*+rOpHj(GI+aW9k=i#^Kh-eRB-Jd{BGoe0 zCDkLc`YCX&_BAO*^e$+JH2@G{ZELH1o837oar~jV*IQ?n*zw!($gUBE==nPp#?+mRBos9k&ei?Zgi!xSbtjjo% zaVFzZ#?_4L8NX#b$oM1UNyf8`7a6ZI-ern1*~}i98kv1E`)BHA8e|%0nr2#O+GV?S5jL%HV%*-5{IX*Kdb86;{%&N?W!mPq^g;NUUg)<8q3V$eES-84TzP4~f z;pW0^g*yw66`m~ox$u|5^Mw}+FBe`d{5Z{ETF|tq)5@mJoYpk0b=v%COQx-ywtL!< zX&0wmpLTQF!)cGEJ(>1w+WTprrhO>_MdBh!k*uh9k!F#0kwKARkx7wNk#EuPq7g+S zi-L+mio%K_ilT}}7sVFE7mY2-E}Bp@sn|eXY*p-D>{aYr>|Z>xIJh{hII=jcIJ0C;H_C37-75P_PRcdq=5jyz2zj79L>?}Wl2^$a{1Jue_9h@hX7N<_po^Hz`4X*?db1E$j_laU2q5e#es)tqyHy}p)YqN>;d~jU8oO@pd(C%;~|9Ya5dZu&%x{P z1=@2iE|-)u+*lgKbC)~02Oov zSJAVgS4E!+{ffaA-W5?5i4`do=@nTOlPYTD74;QO6&or}SG=qQmHL$qm0p#}m1&ik zm18T%S5B;)T$xunwX(Ldp|ZJBQQ2NOx3Z&hP38K^O_f_KcU11K+*^67@=WE~$_thM zR*_Y76ZR2ys#jJ2RK2!(ef6H|gVl$t zk5&I%eWvqFKFX`O0UFpOrtWc2?7@6|>gQIzH?4 ztOv7R&3ZrUQw>%_)Q~kjYkJja*7U9ES2Li-q{h7_peC{=rDj~sgqle;Q)&uoN^9gb zu%@D>s;0VTdCj4khc%yT+1ehp8nu0D`_}5z4ykpjb*=TN^{(}+ji`;SO{h(-O{*PO zJE3-B?d00LT6sxrWo=9C+}ioIi)uS+SJnPhyS8>i?WWp2wMT0&)LyQ=R(qrNx7ypa zf7M}iL>*a2*Ku_{>iX8{))~|p*O}E>)H&CA)OpwW)s3hNt&6COsvBJwTQ{Mupbpl} zs;jGOtZS*8TQ|RMVcp`oC3PF>cGexMJ5zVA?qc1Qy5H*V$m{OaJ*@kq?tMK`FRSlg zuUl_WZ(MIyZ&B}9KeXPp-lN{T-ml)jKD)lUer5gc`Wy9+>fhDBum9A5H4qJC1Kq$i z^l#8@&~GqmFljJruxJ?4VBO%+kknAnFt_1w!{tU%WB*3;#=(tNjkb*rjZTdLjnR$q zjY*BEjaiN38s!rjCpOkL&Td@NxUz9|J7jn5lDG+|9d6WPQz^=Q&)>fNN{$+gL&$-Bw7DXwWuQ+88MQ*KjU zQ$Z7Ks%)xms%>g$YHC{0^kdVKrsYkmnpQV$Z`#$gx9LFBVR_ThrVCA%o31t8Xu8#O zr|DVK%cj4Y-ZuT)^r6|X*}B=T*|B+Ovum?^b6|5&b8vHLb3$`!b4K%+<_XP{nx{19 zH#apeY+l{Gxp`aj&gMPM`m6N9*3!Q>{-Fw4#?nQ=zTsr!Z4kDQp!E z3TK6f!du~|7@-)c2vx)?QWO&uMT$}duc%PWQq(CL70rs-ip6rpGR0cO2E}H@R>dL3 zNyTZ!S;Ym#uZo+B+lqUN2a0EkzZIX`s5ZJytF3>VZks`yY1^PS%QmaFp>2L`C2du0 zjcx1OwzOStyVv%r?M>Uew)btHW@EF7+2m}^*?nj0%|L|3&i>HeqrGptcDuZPyK8$udq(@1_U!hY_T2Wo_Nnbf?IrDH?XbPFy}G@&eR=z; z_Mh6hdl~Qt(J92PUYLi;NkvTm*|HIQ$l#-OCD(7-3*K#AbawqrlB%hklZXMM*UDE?S z)eC*lH?8TnhKQgYNpzwQX=ISa1g0>99M-XgUF;#hgMBCtafD->;0#5SP(c+}c*Ha6 z2rXhUYqfq$*^s4e#Ik1gYVTIJFKbxH7Phg22`1UY0S delta 15212 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 diff --git a/Package.resolved b/Package.resolved index e01c1e3..17ad7da 100644 --- a/Package.resolved +++ b/Package.resolved @@ -6,8 +6,8 @@ "repositoryURL": "https://github.com/swift-server/async-http-client.git", "state": { "branch": null, - "revision": "64851a1a0a2a9e8fa7ae7b3508ce46a1da4a2e1d", - "version": "1.0.0-alpha.2" + "revision": "51dc885a30ca704b02fa803099b0a9b5b38067b6", + "version": "1.0.0" } }, { @@ -15,8 +15,8 @@ "repositoryURL": "https://github.com/vapor/async-kit.git", "state": { "branch": null, - "revision": "b5742bfbbe2d60f3b77465a0907777261e418f23", - "version": "1.0.0-alpha.1" + "revision": "d9fd2be441af6d1428b62ab694848396e7072a14", + "version": "1.0.0-beta.1" } }, { @@ -24,8 +24,8 @@ "repositoryURL": "https://github.com/vapor/console-kit.git", "state": { "branch": null, - "revision": "00ce1abaac919593897b6c60cecdbd4d6290b1f9", - "version": "4.0.0-alpha.2.1" + "revision": "5b91c2dc93781e4b36cb4c667972670eac90e6e7", + "version": "4.0.0-beta.1" } }, { @@ -33,8 +33,8 @@ "repositoryURL": "https://github.com/vapor/fluent.git", "state": { "branch": null, - "revision": "1670384e5dd128b8fe1f9353d2bb518ca2ae8e4c", - "version": "4.0.0-alpha.2.1" + "revision": "96f2ab8599b0507ba593da80ba2c56101932679b", + "version": "4.0.0-beta.1" } }, { @@ -42,8 +42,8 @@ "repositoryURL": "https://github.com/vapor/fluent-kit.git", "state": { "branch": null, - "revision": "0bd08e57a8db90855d74ba6f222d2d847b68b6f0", - "version": "1.0.0-alpha.3" + "revision": "2907bfd11743909c63ccde89cb2c66b1c8d1d645", + "version": "1.0.0-beta.1" } }, { @@ -51,8 +51,17 @@ "repositoryURL": "https://github.com/vapor/fluent-sqlite-driver.git", "state": { "branch": null, - "revision": "64187944b0124fdc7df0c0eead58ee1990b89083", - "version": "4.0.0-alpha.3" + "revision": "987900d8ea4b77f00eb9df5aac1b11f0cdb8ae87", + "version": "4.0.0-beta.1" + } + }, + { + "package": "multipart-kit", + "repositoryURL": "https://github.com/vapor/multipart-kit.git", + "state": { + "branch": null, + "revision": "a941d7a1d685c83df09077f6190808ff2a7f4dce", + "version": "4.0.0-beta.1" } }, { @@ -78,8 +87,8 @@ "repositoryURL": "https://github.com/vapor/sql-kit.git", "state": { "branch": null, - "revision": "354c843f7868eaf93a84b766d652fac8b61d86dd", - "version": "3.0.0-alpha.1.3" + "revision": "2a05245bd3b1a27acc83597dca25ac52358073e5", + "version": "3.0.0-beta.1" } }, { @@ -87,8 +96,8 @@ "repositoryURL": "https://github.com/vapor/sqlite-kit.git", "state": { "branch": null, - "revision": "628f6e105b8a07b52dcc6d04cde842ce65550bba", - "version": "4.0.0-alpha.1.1" + "revision": "f4058e695ecb09fff24d061826d324ec0fa68713", + "version": "4.0.0-beta.1" } }, { @@ -96,8 +105,8 @@ "repositoryURL": "https://github.com/vapor/sqlite-nio.git", "state": { "branch": null, - "revision": "af713ceab2d186739b7ffd95f7edcfad8d8d57bf", - "version": "1.0.0-alpha.1.1" + "revision": "e3a36bc2b1c958da8d38de20b1396aefdea78478", + "version": "1.0.0-alpha.1.3" } }, { @@ -105,8 +114,8 @@ "repositoryURL": "https://github.com/apple/swift-log.git", "state": { "branch": null, - "revision": "e8aabbe95db22e064ad42f1a4a9f8982664c70ed", - "version": "1.1.1" + "revision": "74d7b91ceebc85daf387ebb206003f78813f71aa", + "version": "1.2.0" } }, { @@ -114,8 +123,8 @@ "repositoryURL": "https://github.com/apple/swift-nio.git", "state": { "branch": null, - "revision": "32760eae40e6b7cb81d4d543bb0a9f548356d9a2", - "version": "2.7.1" + "revision": "ff01888051cd7efceb1bf8319c1dd3986c4bf6fc", + "version": "2.10.1" } }, { @@ -123,8 +132,8 @@ "repositoryURL": "https://github.com/apple/swift-nio-extras.git", "state": { "branch": null, - "revision": "66f9a509ed3cc56b6eb367515e421beca4a0af53", - "version": "1.2.0" + "revision": "53808818c2015c45247cad74dc05c7a032c96a2f", + "version": "1.3.2" } }, { @@ -132,8 +141,8 @@ "repositoryURL": "https://github.com/apple/swift-nio-http2.git", "state": { "branch": null, - "revision": "86ce1dcd0df501401eb1a0d445dbd90aaad84a64", - "version": "1.5.0" + "revision": "7cc32a9a41e348230a4a452b25b7308cae1f3652", + "version": "1.7.1" } }, { @@ -141,17 +150,26 @@ "repositoryURL": "https://github.com/apple/swift-nio-ssl.git", "state": { "branch": null, - "revision": "f5dd7a60ff56f501ff7bf9be753e4b1875bfaf20", - "version": "2.4.0" + "revision": "ccf96bbe65ecc7c1558ab0dba7ffabdea5c1d31f", + "version": "2.4.4" } }, { "package": "vapor", "repositoryURL": "https://github.com/vapor/vapor.git", + "state": { + "branch": "ArrayRouting", + "revision": "7df058aa79117f1cf1b83f202267e4171d4cf8f6", + "version": null + } + }, + { + "package": "websocket-kit", + "repositoryURL": "https://github.com/vapor/websocket-kit.git", "state": { "branch": null, - "revision": "17c5ca37a4d70cde7c23b7eb2f7037724fa28899", - "version": "4.0.0-alpha.3.1.1" + "revision": "66c0ea58398f055b5a0d92b0d5f4c32ef0c02eeb", + "version": "2.0.0-beta.1" } } ] diff --git a/Package.swift b/Package.swift index 4d270f6..80d6624 100644 --- a/Package.swift +++ b/Package.swift @@ -3,11 +3,14 @@ import PackageDescription let package = Package( name: "CrudRouter", + platforms: [ + .macOS(.v10_14) + ], products: [ .library(name: "CrudRouter", targets: ["CrudRouter"]), ], dependencies: [ - .package(url: "https://github.com/vapor/vapor.git", from: "4.0.0-alpha.3"), + .package(url: "https://github.com/vapor/vapor.git", .branch("ArrayRouting")), .package(url: "https://github.com/vapor/fluent.git", from: "4.0.0-alpha.2.1"), .package(url: "https://github.com/vapor/fluent-sqlite-driver.git", from: "4.0.0-alpha.3"), ], diff --git a/Sources/CrudRouter/ControllerProtocols/CrudChildrenControllerProtocol.swift b/Sources/CrudRouter/ControllerProtocols/CrudChildrenControllerProtocol.swift index edd9506..0aad4bf 100644 --- a/Sources/CrudRouter/ControllerProtocols/CrudChildrenControllerProtocol.swift +++ b/Sources/CrudRouter/ControllerProtocols/CrudChildrenControllerProtocol.swift @@ -1,5 +1,7 @@ import Vapor +import FluentKit import Fluent +import NIOExtras public protocol CrudChildrenControllerProtocol { associatedtype ParentType: Model & Content where ParentType.IDValue: LosslessStringConvertible @@ -20,36 +22,45 @@ public extension CrudChildrenControllerProtocol { func index(_ req: Request) throws -> EventLoopFuture { let parentId: ParentType.IDValue = try req.getId() let childId: ChildType.IDValue = try req.getId() - - return ParentType.find(parentId, on: db).unwrap(or: Abort(.notFound)).flatMap { parent -> EventLoopFuture in - - return try parent[keyPath: self.children] - .query(on: self.db) - .filter(\ChildType.fluentID == childId) - .first() - .unwrap(or: Abort(.notFound)) - } + + let thing = ParentType + .query(on: db) + .join(ChildType.self, on: children == \ParentType.$id) + + return + +// .find(parentId, on: db) +// .unwrap(or: Abort(.notFound)) +// .flatMap { parent -> EventLoopFuture in +// return try! parent[keyPath: self.children] +// .query(on: self.db) +// .filter(\ChildType.id == childId) +// .first() +// .unwrap(or: Abort(.notFound)) +// } } func indexAll(_ req: Request) throws -> EventLoopFuture<[ChildType]> { let parentId: ParentType.IDValue = try req.getId() - - return ParentType.find(parentId, on: db).unwrap(or: Abort(.notFound)).flatMap { parent -> EventLoopFuture<[ChildType]> in - - return try parent[keyPath: self.children] - .query(on: self.db) - .all() - } + return ParentType + .find(parentId, on: db) + .unwrap(or: Abort(.notFound)) + .flatMap { parent -> EventLoopFuture<[ChildType]> in + return try! parent[keyPath: self.children] + .query(on: self.db) + .all() + } } func create(_ req: Request) throws -> EventLoopFuture { let parentId: ParentType.IDValue = try req.getId() - return ParentType.find(parentId, on: db).unwrap(or: Abort(.notFound)).flatMap { parent -> EventLoopFuture in - - return try req.content.decode(ChildType.self).flatMap { child in - return try parent[keyPath: self.children].query(on: self.db).save(child) - } + return ParentType + .find(parentId, on: db) + .unwrap(or: Abort(.notFound)) + .flatMap { parent -> EventLoopFuture in + let child = try! req.content.decode(ChildType.self) + return try! parent[keyPath: self.children]. } } @@ -61,13 +72,13 @@ public extension CrudChildrenControllerProtocol { .find(parentId, on: db) .unwrap(or: Abort(.notFound)) .flatMap { parent -> EventLoopFuture in - return try parent[keyPath: self.children] + return try! parent[keyPath: self.children] .query(on: self.db) .filter(\ChildType.fluentID == childId) .first() .unwrap(or: Abort(.notFound)) }.flatMap { oldChild in - return try req.content.decode(ChildType.self).flatMap { newChild in + return try! req.content.decode(ChildType.self).flatMap { newChild in var temp = newChild temp.fluentID = oldChild.fluentID return temp.update(on: self.db) diff --git a/Sources/CrudRouter/ControllerProtocols/Crudable.swift b/Sources/CrudRouter/ControllerProtocols/Crudable.swift index e40cf47..6184a26 100644 --- a/Sources/CrudRouter/ControllerProtocols/Crudable.swift +++ b/Sources/CrudRouter/ControllerProtocols/Crudable.swift @@ -2,10 +2,10 @@ import Vapor import Fluent public protocol Crudable: ControllerProtocol { - associatedtype ChildType: Model, Content where ChildType.IDValue: Parameter + associatedtype ChildType: Model, Content func crud( - at path: PathComponentsRepresentable..., + at path: PathComponent..., parent relation: KeyPath>, _ either: OnlyExceptEither, relationConfiguration: ((CrudParentController) -> Void)? @@ -15,17 +15,16 @@ public protocol Crudable: ControllerProtocol { ParentType.IDValue: LosslessStringConvertible func crud( - at path: PathComponentsRepresentable..., + at path: PathComponent..., children relation: KeyPath>, _ either: OnlyExceptEither, relationConfiguration: ((CrudChildrenController) -> Void)? ) where ChildChildType: Model & Content, - ChildType.Database == ChildChildType.Database, - ChildChildType.IDValue: Parameter + ChildType.Database == ChildChildType.Database func crud( - at path: PathComponentsRepresentable..., + at path: PathComponent..., siblings relation: KeyPath>, _ either: OnlyExceptEither, relationConfiguration: ((CrudSiblingsController) -> Void)? @@ -37,7 +36,7 @@ public protocol Crudable: ControllerProtocol { ThroughType.Right == ChildChildType func crud( - at path: PathComponentsRepresentable..., + at path: PathComponent..., siblings relation: KeyPath>, _ either: OnlyExceptEither, relationConfiguration: ((CrudSiblingsController) -> Void)? @@ -51,7 +50,7 @@ public protocol Crudable: ControllerProtocol { extension Crudable { public func crud( - at path: PathComponentsRepresentable..., + at path: PathComponent..., parent relation: KeyPath>, _ either: OnlyExceptEither = .only([.read, .update]), relationConfiguration: ((CrudParentController) -> Void)?=nil @@ -59,7 +58,7 @@ extension Crudable { ParentType: Model & Content, ChildType.Database == ParentType.Database, ParentType.IDValue: LosslessStringConvertible { - let baseIdPath = self.path.appending(ChildType.IDValue.parameter) + let baseIdPath = self.path.appending(.parameter("\(ChildType.schema)ID")) let adjustedPath = path.adjustedPath(for: ParentType.self) let fullPath = baseIdPath.appending(adjustedPath) @@ -81,7 +80,7 @@ extension Crudable { } public func crud( - at path: PathComponentsRepresentable..., + at path: PathComponent..., children relation: KeyPath>, _ either: OnlyExceptEither = .only([.read, .readAll, .create, .update, .delete]), relationConfiguration: ((CrudChildrenController) -> Void)?=nil @@ -89,7 +88,7 @@ extension Crudable { ChildChildType: Model & Content, ChildType.Database == ChildChildType.Database, ChildChildType.IDValue: LosslessStringConvertible { - let baseIdPath = self.path.appending(ChildType.IDValue.parameter) + let baseIdPath = self.path.appending(.parameter("\(ChildType.schema)ID")) let adjustedPath = path.adjustedPath(for: ChildChildType.self) let fullPath = baseIdPath.appending(adjustedPath) @@ -110,7 +109,7 @@ extension Crudable { } public func crud( - at path: PathComponentsRepresentable..., + at path: PathComponent..., siblings relation: KeyPath>, _ either: OnlyExceptEither = .only([.read, .readAll, .create, .update, .delete]), relationConfiguration: ((CrudSiblingsController) -> Void)?=nil @@ -120,7 +119,7 @@ extension Crudable { ThroughType: Model, ThroughType.Left == ChildType, ThroughType.Right == ChildChildType { - let baseIdPath = self.path.appending(ChildType.IDValue.parameter) + let baseIdPath = self.path.appending(.parameter("\(ChildType.schema)ID")) let adjustedPath = path.adjustedPath(for: ChildChildType.self) let fullPath = baseIdPath.appending(adjustedPath) @@ -142,7 +141,7 @@ extension Crudable { } public func crud( - at path: PathComponentsRepresentable..., + at path: PathComponent..., siblings relation: KeyPath>, _ either: OnlyExceptEither = .only([.read, .readAll, .create, .update, .delete]), relationConfiguration: ((CrudSiblingsController) -> Void)?=nil @@ -152,7 +151,7 @@ extension Crudable { ThroughType: Model, ThroughType.Right == ChildType, ThroughType.Left == ChildChildType { - let baseIdPath = self.path.appending(ChildType.IDValue.parameter) + let baseIdPath = self.path.appending(.parameter("\(ChildType.schema)ID")) let adjustedPath = path.adjustedPath(for: ChildChildType.self) let fullPath = baseIdPath.appending(adjustedPath) diff --git a/Sources/CrudRouter/CrudChildrenController.swift b/Sources/CrudRouter/CrudChildrenController.swift index 2df2ac7..a57612e 100644 --- a/Sources/CrudRouter/CrudChildrenController.swift +++ b/Sources/CrudRouter/CrudChildrenController.swift @@ -9,12 +9,12 @@ public struct CrudChildrenController> - public let path: [PathComponentsRepresentable] + public let path: [PathComponent] let activeMethods: Set init( childrenRelation: KeyPath>, - path: [PathComponentsRepresentable], + path: [PathComponent], router: RoutesBuilder, activeMethods: Set ) { @@ -26,9 +26,9 @@ public struct CrudChildrenController: CrudControllerProtocol, C public typealias ModelType = ModelT public let db: Database - public let path: [PathComponentsRepresentable] + public let path: [PathComponent] public let router: RoutesBuilder let activeMethods: Set - init(path: [PathComponentsRepresentable], router: RoutesBuilder, activeMethods: Set) { + init(path: [PathComponent], router: RoutesBuilder, activeMethods: Set) { let adjustedPath = path.adjustedPath(for: ModelType.self) self.path = adjustedPath @@ -27,7 +27,7 @@ public struct CrudController: CrudControllerProtocol, C extension CrudController: RouteCollection { public func boot(routes router: RoutesBuilder) throws { let basePath = self.path - let baseIdPath = self.path.appending(ModelType.IDValue.parameter) + let baseIdPath = self.path.appending(PathComponent.parameter("\(ModelType.schema)ID")) self.activeMethods.forEach { $0.register( diff --git a/Sources/CrudRouter/CrudParentController.swift b/Sources/CrudRouter/CrudParentController.swift index 1eebbb7..b325dc1 100644 --- a/Sources/CrudRouter/CrudParentController.swift +++ b/Sources/CrudRouter/CrudParentController.swift @@ -8,13 +8,13 @@ public struct CrudParentController> - public let path: [PathComponentsRepresentable] + public let path: [PathComponent] public let router: RoutesBuilder let activeMethods: Set init( relation: KeyPath>, - path: [PathComponentsRepresentable], + path: [PathComponent], router: RoutesBuilder, activeMethods: Set ) { diff --git a/Sources/CrudRouter/CrudSiblingsController.swift b/Sources/CrudRouter/CrudSiblingsController.swift index 311e7b0..c0b98be 100644 --- a/Sources/CrudRouter/CrudSiblingsController.swift +++ b/Sources/CrudRouter/CrudSiblingsController.swift @@ -11,13 +11,13 @@ public struct CrudSiblingsController> - public let path: [PathComponentsRepresentable] + public let path: [PathComponent] public let router: RoutesBuilder let activeMethods: Set init( siblingRelation: KeyPath>, - path: [PathComponentsRepresentable], + path: [PathComponent], router: RoutesBuilder, activeMethods: Set ) { @@ -30,44 +30,44 @@ public struct CrudSiblingsController(for type: T.Type) -> [PathComponentsRepresentable] { +extension Array where Element == PathComponent { + func adjustedPath(for type: T.Type) -> [PathComponent] { return self.count == 0 - ? [String(describing: T.self).snakeCased()! as PathComponentsRepresentable] + ? [String(describing: T.self).snakeCased()! as PathComponent] : self } } diff --git a/Sources/CrudRouter/Extensions/Router+CRUD.swift b/Sources/CrudRouter/Extensions/Router+CRUD.swift index 3054e82..72741bc 100644 --- a/Sources/CrudRouter/Extensions/Router+CRUD.swift +++ b/Sources/CrudRouter/Extensions/Router+CRUD.swift @@ -3,11 +3,11 @@ import Fluent public extension Router { public func crud( - _ path: PathComponentsRepresentable..., + _ path: PathComponent..., register type: ModelType.Type, _ either: OnlyExceptEither = .only([.read, .readAll, .create, .update, .delete]), relationConfiguration: ((CrudController) -> ())?=nil - ) where ModelType.IDValue: Parameter { + ) { let allMethods: Set = Set([.read, .readAll, .create, .update, .delete]) let controller: CrudController diff --git a/Sources/CrudRouter/Obsoleted/CrudControllerObsoleted.swift b/Sources/CrudRouter/Obsoleted/CrudControllerObsoleted.swift deleted file mode 100644 index 1defe47..0000000 --- a/Sources/CrudRouter/Obsoleted/CrudControllerObsoleted.swift +++ /dev/null @@ -1,70 +0,0 @@ -import Vapor -import Fluent - -// MARK: Obsolted ParentsController methods -extension CrudController { - @available(swift, obsoleted: 4.0, renamed: "crud(at:parent:relationConfiguration:)") - public func crudRegister( - at path: PathComponentsRepresentable..., - forParent relation: KeyPath>, - relationConfiguration: ((CrudParentController) throws -> Void)?=nil - ) throws where - ParentType: Model & Content, - ModelType.Database == ParentType.Database, - ParentType.IDValue: Parameter { - fatalError() - } -} - - -// MARK: ChildController methods -extension CrudController { - @available(swift, obsoleted: 4.0, renamed: "crud(at:children:relationConfiguration:)") - public func crudRegister( - at path: PathComponentsRepresentable..., - forChildren relation: KeyPath>, - relationConfiguration: ((CrudChildrenController) throws -> Void)?=nil - ) throws where - ChildType: Model & Content, - ModelType.Database == ChildType.Database, - ChildType.IDValue: Parameter { - fatalError() - } -} - -// MARK: SiblingController methods -public extension CrudController { - @available(swift, obsoleted: 4.0, renamed: "crud(at:siblings:relationConfiguration:)") - public func crudRegister( - at path: PathComponentsRepresentable..., - forSiblings relation: KeyPath>, - relationConfiguration: ((CrudSiblingsController) throws -> Void)?=nil - ) throws where - ChildType: Content, - ModelType.Database == ThroughType.Database, - ChildType.IDValue: Parameter, - ThroughType: ModifiablePivot, - ThroughType.Database: JoinSupporting, - ThroughType.Database == ChildType.Database, - ThroughType.Left == ModelType, - ThroughType.Right == ChildType { - fatalError() - } - - @available(swift, obsoleted: 4.0, renamed: "crud(at:siblings:relationConfiguration:)") - public func crudRegister( - at path: PathComponentsRepresentable..., - forSiblings relation: KeyPath>, - relationConfiguration: ((CrudSiblingsController) throws -> Void)?=nil - ) throws where - ChildType: Content, - ModelType.Database == ThroughType.Database, - ChildType.IDValue: Parameter, - ThroughType: ModifiablePivot, - ThroughType.Database: JoinSupporting, - ThroughType.Database == ChildType.Database, - ThroughType.Right == ModelType, - ThroughType.Left == ChildType { - fatalError() - } -} diff --git a/Sources/CrudRouter/Obsoleted/RouterObsoleted.swift b/Sources/CrudRouter/Obsoleted/RouterObsoleted.swift deleted file mode 100644 index d2972ea..0000000 --- a/Sources/CrudRouter/Obsoleted/RouterObsoleted.swift +++ /dev/null @@ -1,11 +0,0 @@ -import Vapor -import Fluent - -public extension Router { - @available(swift, obsoleted: 4.0, renamed: "crud(_:register:relationConfiguration:)") - public func crudRegister( - _ path: PathComponentsRepresentable..., - for type: ModelType.Type, - relationConfiguration: ((CrudController) throws -> ())?=nil - ) throws where ModelType.IDValue: Parameter {} -} diff --git a/Sources/CrudRouter/RouterMethods/ChildrenRouterMethod.swift b/Sources/CrudRouter/RouterMethods/ChildrenRouterMethod.swift index 2a0df96..b954be3 100644 --- a/Sources/CrudRouter/RouterMethods/ChildrenRouterMethod.swift +++ b/Sources/CrudRouter/RouterMethods/ChildrenRouterMethod.swift @@ -8,10 +8,10 @@ public enum ChildrenRouterMethod { case delete func register( - router: Router, + router: RoutesBuilder, controller: CrudChildrenController, - path: [PathComponentsRepresentable], - idPath: [PathComponentsRepresentable] + path: [PathComponent], + idPath: [PathComponent] ) { switch self { case .read: diff --git a/Sources/CrudRouter/RouterMethods/ParentRouterMethod.swift b/Sources/CrudRouter/RouterMethods/ParentRouterMethod.swift index 08d9cf2..bf67211 100644 --- a/Sources/CrudRouter/RouterMethods/ParentRouterMethod.swift +++ b/Sources/CrudRouter/RouterMethods/ParentRouterMethod.swift @@ -5,9 +5,9 @@ public enum ParentRouterMethod { case update func register( - router: Router, + router: RoutesBuilder, controller: CrudParentController, - path: [PathComponentsRepresentable] + path: [PathComponent] ) { switch self { case .read: diff --git a/Sources/CrudRouter/RouterMethods/RouterMethod.swift b/Sources/CrudRouter/RouterMethods/RouterMethod.swift index be01609..ade5fd4 100644 --- a/Sources/CrudRouter/RouterMethods/RouterMethod.swift +++ b/Sources/CrudRouter/RouterMethods/RouterMethod.swift @@ -8,10 +8,10 @@ public enum RouterMethod { case delete func register( - router: Router, + router: RoutesBuilder, controller: CrudController, - path: [PathComponentsRepresentable], - idPath: [PathComponentsRepresentable] + path: [PathComponent], + idPath: [PathComponent] ) { switch self { case .read: diff --git a/Sources/CrudRouter/RouterMethods/SiblingRouterMethod.swift b/Sources/CrudRouter/RouterMethods/SiblingRouterMethod.swift index bb311a0..ba1db7f 100644 --- a/Sources/CrudRouter/RouterMethods/SiblingRouterMethod.swift +++ b/Sources/CrudRouter/RouterMethods/SiblingRouterMethod.swift @@ -9,10 +9,10 @@ public enum ModifiableSiblingRouterMethod { case delete func register( - router: Router, + router: RoutesBuilder, controller: CrudSiblingsController, - path: [PathComponentsRepresentable], - idPath: [PathComponentsRepresentable] + path: [PathComponent], + idPath: [PathComponent] ) where ThroughType.Left == ParentType, ThroughType.Right == ChildType { switch self { @@ -30,10 +30,10 @@ public enum ModifiableSiblingRouterMethod { } func register( - router: Router, + router: RoutesBuilder, controller: CrudSiblingsController, - path: [PathComponentsRepresentable], - idPath: [PathComponentsRepresentable] + path: [PathComponent], + idPath: [PathComponent] ) where ThroughType.Left == ChildType, ThroughType.Right == ParentType { switch self { From 2b217dab6c810a55d3d74e0899f89fd50c5f39c0 Mon Sep 17 00:00:00 2001 From: twof Date: Sun, 17 Nov 2019 10:45:18 -0800 Subject: [PATCH 03/38] Remove modifiable pivot --- Sources/CrudRouter/RouterMethods/SiblingRouterMethod.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/CrudRouter/RouterMethods/SiblingRouterMethod.swift b/Sources/CrudRouter/RouterMethods/SiblingRouterMethod.swift index ba1db7f..9f2131c 100644 --- a/Sources/CrudRouter/RouterMethods/SiblingRouterMethod.swift +++ b/Sources/CrudRouter/RouterMethods/SiblingRouterMethod.swift @@ -8,7 +8,7 @@ public enum ModifiableSiblingRouterMethod { case update case delete - func register( + func register( router: RoutesBuilder, controller: CrudSiblingsController, path: [PathComponent], @@ -29,7 +29,7 @@ public enum ModifiableSiblingRouterMethod { } } - func register( + func register( router: RoutesBuilder, controller: CrudSiblingsController, path: [PathComponent], From a46e6d83146cab1812eaa66115810d3e7bdc762c Mon Sep 17 00:00:00 2001 From: twof Date: Sun, 17 Nov 2019 11:39:29 -0800 Subject: [PATCH 04/38] indexall gets its req back --- .../CrudChildrenControllerProtocol.swift | 50 ++++++------ .../CrudControllerProtocol.swift | 18 ++--- .../CrudParentControllerProtocol.swift | 10 +-- .../CrudSiblingsControllerProtocol.swift | 61 ++++++++------ .../ControllerProtocols/Crudable.swift | 10 +-- .../CrudRouter/CrudChildrenController.swift | 2 +- Sources/CrudRouter/CrudParentController.swift | 2 +- .../CrudRouter/CrudSiblingsController.swift | 81 ++++++++++--------- 8 files changed, 125 insertions(+), 109 deletions(-) diff --git a/Sources/CrudRouter/ControllerProtocols/CrudChildrenControllerProtocol.swift b/Sources/CrudRouter/ControllerProtocols/CrudChildrenControllerProtocol.swift index 0aad4bf..43e54c6 100644 --- a/Sources/CrudRouter/ControllerProtocols/CrudChildrenControllerProtocol.swift +++ b/Sources/CrudRouter/ControllerProtocols/CrudChildrenControllerProtocol.swift @@ -6,8 +6,6 @@ import NIOExtras public protocol CrudChildrenControllerProtocol { associatedtype ParentType: Model & Content where ParentType.IDValue: LosslessStringConvertible associatedtype ChildType: Model & Content where ChildType.IDValue: LosslessStringConvertible - - var db: Database { get } var children: KeyPath> { get } @@ -24,7 +22,7 @@ public extension CrudChildrenControllerProtocol { let childId: ChildType.IDValue = try req.getId() let thing = ParentType - .query(on: db) + .query(on: req.db) .join(ChildType.self, on: children == \ParentType.$id) return @@ -42,12 +40,12 @@ public extension CrudChildrenControllerProtocol { func indexAll(_ req: Request) throws -> EventLoopFuture<[ChildType]> { let parentId: ParentType.IDValue = try req.getId() - return ParentType - .find(parentId, on: db) + return try ParentType + .find(parentId, on: req.db) .unwrap(or: Abort(.notFound)) - .flatMap { parent -> EventLoopFuture<[ChildType]> in - return try! parent[keyPath: self.children] - .query(on: self.db) + .throwingFlatMap { parent -> EventLoopFuture<[ChildType]> in + return try parent[keyPath: self.children] + .query(on: req.db) .all() } } @@ -55,12 +53,12 @@ public extension CrudChildrenControllerProtocol { func create(_ req: Request) throws -> EventLoopFuture { let parentId: ParentType.IDValue = try req.getId() - return ParentType - .find(parentId, on: db) + return try ParentType + .find(parentId, on: req.db) .unwrap(or: Abort(.notFound)) - .flatMap { parent -> EventLoopFuture in - let child = try! req.content.decode(ChildType.self) - return try! parent[keyPath: self.children]. + .throwingFlatMap { parent -> EventLoopFuture in + let child = try req.content.decode(ChildType.self) + return try parent[keyPath: self.children].query(on: req.db) } } @@ -68,20 +66,20 @@ public extension CrudChildrenControllerProtocol { let parentId: ParentType.IDValue = try req.getId() let childId: ChildType.IDValue = try req.getId() - return ParentType - .find(parentId, on: db) + return try ParentType + .find(parentId, on: req.db) .unwrap(or: Abort(.notFound)) - .flatMap { parent -> EventLoopFuture in - return try! parent[keyPath: self.children] - .query(on: self.db) + .throwingFlatMap { parent -> EventLoopFuture in + return try parent[keyPath: self.children] + .query(on: req.db) .filter(\ChildType.fluentID == childId) .first() .unwrap(or: Abort(.notFound)) - }.flatMap { oldChild in - return try! req.content.decode(ChildType.self).flatMap { newChild in + }.throwingFlatMap { oldChild in + return try req.content.decode(ChildType.self).flatMap { newChild in var temp = newChild temp.fluentID = oldChild.fluentID - return temp.update(on: self.db) + return temp.update(on: req.db) } } } @@ -90,16 +88,16 @@ public extension CrudChildrenControllerProtocol { let parentId: ParentType.IDValue = try req.getId() let childId: ChildType.IDValue = try req.getId() - return ParentType - .find(parentId, on: db) + return try ParentType + .find(parentId, on: req.db) .unwrap(or: Abort(.notFound)) - .flatMap { parent -> EventLoopFuture in + .throwingFlatMap { parent -> EventLoopFuture in return try parent[keyPath: self.children] - .query(on: self.db) + .query(on: req.db) .filter(\ChildType.fluentID == childId) .first() .unwrap(or: Abort(.notFound)) - .delete(on: self.db) + .delete(on: req.db) .transform(to: HTTPStatus.ok) } } diff --git a/Sources/CrudRouter/ControllerProtocols/CrudControllerProtocol.swift b/Sources/CrudRouter/ControllerProtocols/CrudControllerProtocol.swift index 6b22155..c8dd69e 100644 --- a/Sources/CrudRouter/ControllerProtocols/CrudControllerProtocol.swift +++ b/Sources/CrudRouter/ControllerProtocols/CrudControllerProtocol.swift @@ -3,9 +3,9 @@ import Fluent public protocol CrudControllerProtocol { associatedtype ModelType: Model, Content where ModelType.IDValue: LosslessStringConvertible - var db: Database { get } +// var db: Database { get } - func indexAll() throws -> EventLoopFuture<[ModelType]> + func indexAll(_ req: Request) throws -> EventLoopFuture<[ModelType]> func index(_ req: Request) throws -> EventLoopFuture func update(_ req: Request) throws -> EventLoopFuture func create(_ req: Request) throws -> EventLoopFuture @@ -13,17 +13,17 @@ public protocol CrudControllerProtocol { } public extension CrudControllerProtocol { - func indexAll() throws -> EventLoopFuture<[ModelType]> { - return ModelType.query(on: db).all().map { Array($0) } + func indexAll(_ req: Request) throws -> EventLoopFuture<[ModelType]> { + return ModelType.query(on: req.db).all().map { Array($0) } } func index(_ req: Request) throws -> EventLoopFuture { let id: ModelType.IDValue = try req.getId() - return ModelType.find(id, on: db).unwrap(or: Abort(.notFound)) + return ModelType.find(id, on: req.db).unwrap(or: Abort(.notFound)) } func create(_ req: Request) throws -> EventLoopFuture { - return try req.content.decode(ModelType.self).save(on: db).map { + return try req.content.decode(ModelType.self).save(on: req.db).map { return } } @@ -34,7 +34,7 @@ public extension CrudControllerProtocol { let temp = model temp.id = id - return temp.update(on: db).map { + return temp.update(on: req.db).map { return } } @@ -42,10 +42,10 @@ public extension CrudControllerProtocol { func delete(_ req: Request) throws -> EventLoopFuture { let id: ModelType.IDValue = try req.getId() return ModelType - .find(id, on: db) + .find(id, on: req.db) .unwrap(or: Abort(.notFound)) .flatMap { model in - return model.delete(on: self.db).transform(to: HTTPStatus.ok) + return model.delete(on: req.db).transform(to: HTTPStatus.ok) } } } diff --git a/Sources/CrudRouter/ControllerProtocols/CrudParentControllerProtocol.swift b/Sources/CrudRouter/ControllerProtocols/CrudParentControllerProtocol.swift index 94d6cb4..3681997 100644 --- a/Sources/CrudRouter/ControllerProtocols/CrudParentControllerProtocol.swift +++ b/Sources/CrudRouter/ControllerProtocols/CrudParentControllerProtocol.swift @@ -5,7 +5,7 @@ public protocol CrudParentControllerProtocol { associatedtype ParentType: Model & Content where ParentType.IDValue: LosslessStringConvertible associatedtype ChildType: Model & Content where ChildType.IDValue: LosslessStringConvertible - var db: Database { get } +// var db: Database { get } var relation: KeyPath> { get } @@ -17,8 +17,8 @@ public extension CrudParentControllerProtocol { func index(_ req: Request) throws -> EventLoopFuture { let childId: ChildType.IDValue = try req.getId() - return ChildType.find(childId, on: db).unwrap(or: Abort(.notFound)).flatMap { child in - child[keyPath: self.relation].get(on: self.db) + return ChildType.find(childId, on: req.db).unwrap(or: Abort(.notFound)).flatMap { child in + child[keyPath: self.relation].get(on: req.db) } } @@ -26,10 +26,10 @@ public extension CrudParentControllerProtocol { let childId: ChildType.IDValue = try req.getId() return ChildType - .find(childId, on: db) + .find(childId, on: req.db) .unwrap(or: Abort(.notFound)) .flatMap { child in - return child[keyPath: self.relation].get(on: self.db) + return child[keyPath: self.relation].get(on: req.db) } } } diff --git a/Sources/CrudRouter/ControllerProtocols/CrudSiblingsControllerProtocol.swift b/Sources/CrudRouter/ControllerProtocols/CrudSiblingsControllerProtocol.swift index 6eab97a..d7cee98 100644 --- a/Sources/CrudRouter/ControllerProtocols/CrudSiblingsControllerProtocol.swift +++ b/Sources/CrudRouter/ControllerProtocols/CrudSiblingsControllerProtocol.swift @@ -1,12 +1,22 @@ import Vapor import Fluent +extension EventLoopFuture { + func throwingFlatMap(file: StaticString = #file, line: UInt = #line, _ callback: @escaping ((Value) throws -> EventLoopFuture)) rethrows -> EventLoopFuture { + return self.flatMap(file: file, line: line) { (value: Value) -> EventLoopFuture in + do { + return try callback(value) + } catch { + return self.eventLoop.makeFailedFuture(error) + } + } + } +} + public protocol CrudSiblingsControllerProtocol { associatedtype ParentType: Model & Content where ParentType.IDValue: LosslessStringConvertible associatedtype ChildType: Model & Content where ChildType.IDValue: LosslessStringConvertible associatedtype ThroughType: Model - - var db: Database { get } var siblings: KeyPath> { get } @@ -20,10 +30,10 @@ public extension CrudSiblingsControllerProtocol { let parentId: ParentType.IDValue = try req.getId() let childId: ChildType.IDValue = try req.getId() - return ParentType.find(parentId, on: db).unwrap(or: Abort(.notFound)).flatMap { parent -> EventLoopFuture in + return ParentType.find(parentId, on: req.db).unwrap(or: Abort(.notFound)).flatMap { parent -> EventLoopFuture in return try parent[keyPath: self.siblings] - .query(on: self.db) + .query(on: req.db) .filter(\ChildType.fluentID == childId) .first() .unwrap(or: Abort(.notFound)) @@ -33,7 +43,7 @@ public extension CrudSiblingsControllerProtocol { func indexAll(_ req: Request) throws -> EventLoopFuture<[ChildType]> { let parentId: ParentType.IDValue = try req.getId() - return ParentType.find(parentId, on: db).unwrap(or: Abort(.notFound)).flatMap { parent -> EventLoopFuture<[ChildType]> in + return ParentType.find(parentId, on: req.db).unwrap(or: Abort(.notFound)).flatMap { parent -> EventLoopFuture<[ChildType]> in let siblingsRelation = parent[keyPath: self.siblings] return try siblingsRelation .query(on: self.db) @@ -46,11 +56,11 @@ public extension CrudSiblingsControllerProtocol { let childId: ChildType.IDValue = try req.getId() return ParentType - .find(parentId, on: db) + .find(parentId, on: req.db) .unwrap(or: Abort(.notFound)) .flatMap { parent -> EventLoopFuture in return try parent[keyPath: self.siblings] - .query(on: self.db) + .query(on: req.db) .filter(\ChildType.fluentID == childId) .first() .unwrap(or: Abort(.notFound)) @@ -58,7 +68,7 @@ public extension CrudSiblingsControllerProtocol { return try req.content.decode(ChildType.self).flatMap { newChild in var temp = newChild temp.fluentID = oldChild.fluentID - return temp.update(on: self.db) + return temp.update(on: req.db) } } } @@ -69,12 +79,11 @@ ThroughType.Right == ChildType { func create(_ req: Request) throws -> EventLoopFuture { let parentId: ParentType.IDValue = try req.getId() - return ParentType.find(parentId, on: db).unwrap(or: Abort(.notFound)).flatMap { parent -> EventLoopFuture in - - return try req.content.decode(ChildType.self).flatMap { child in - let relation = parent[keyPath: self.siblings] - return relation.attach(child, on: self.db).transform(to: child) - } + return try ParentType.find(parentId, on: req.db).unwrap(or: Abort(.notFound)).throwingFlatMap { parent -> EventLoopFuture in + let child = try req.content.decode(ChildType.self) + + let relation = parent[keyPath: self.siblings] + return relation.attach(child, on: req.db).transform(to: child) } } @@ -82,18 +91,18 @@ ThroughType.Right == ChildType { let parentId: ParentType.IDValue = try req.getId() let childId: ChildType.IDValue = try req.getId() - return ParentType - .find(parentId, on: req) + return try ParentType + .find(parentId, on: req.db) .unwrap(or: Abort(.notFound)) - .flatMap { parent -> EventLoopFuture in + .throwingFlatMap { parent -> EventLoopFuture in let siblingsRelation = parent[keyPath: self.siblings] return try siblingsRelation - .query(on: req) + .query(on: req.db) .filter(\ChildType.fluentID == childId) .first() .unwrap(or: Abort(.notFound)) - .flatMap { siblingsRelation.detach($0, on: req).transform(to: $0) } - .delete(on: req) + .flatMap { siblingsRelation.detach($0, on: req.db).transform(to: $0) } + .delete(on: req.db) .transform(to: HTTPStatus.ok) } } @@ -104,13 +113,13 @@ ThroughType.Left == ChildType { func create(_ req: Request) throws -> EventLoopFuture { let parentId: ParentType.IDValue = try req.getId() - return ParentType.find(parentId, on: req).unwrap(or: Abort(.notFound)).flatMap { parent -> EventLoopFuture in + return ParentType.find(parentId, on: req.db).unwrap(or: Abort(.notFound)).flatMap { parent -> EventLoopFuture in return try req.content.decode(ChildType.self).flatMap { child in - return child.create(on: req) + return child.create(on: req.db) }.flatMap { child in let relation = parent[keyPath: self.siblings] - return relation.attach(child, on: req).transform(to: child) + return relation.attach(child, on: req.db).transform(to: child) } } } @@ -120,16 +129,16 @@ ThroughType.Left == ChildType { let childId: ChildType.IDValue = try req.getId() return ParentType - .find(parentId, on: req) + .find(parentId, on: req.db) .unwrap(or: Abort(.notFound)) .flatMap { parent -> EventLoopFuture in let siblingsRelation = parent[keyPath: self.siblings] return try siblingsRelation - .query(on: req) + .query(on: req.db) .filter(\ChildType.fluentID == childId) .first() .unwrap(or: Abort(.notFound)) - .delete(on: req) + .delete(on: req.db) .transform(to: HTTPStatus.ok) } } diff --git a/Sources/CrudRouter/ControllerProtocols/Crudable.swift b/Sources/CrudRouter/ControllerProtocols/Crudable.swift index 6184a26..92e81b1 100644 --- a/Sources/CrudRouter/ControllerProtocols/Crudable.swift +++ b/Sources/CrudRouter/ControllerProtocols/Crudable.swift @@ -56,12 +56,12 @@ extension Crudable { relationConfiguration: ((CrudParentController) -> Void)?=nil ) where ParentType: Model & Content, - ChildType.Database == ParentType.Database, +// ChildType.Database == ParentType.Database, ParentType.IDValue: LosslessStringConvertible { let baseIdPath = self.path.appending(.parameter("\(ChildType.schema)ID")) let adjustedPath = path.adjustedPath(for: ParentType.self) - let fullPath = baseIdPath.appending(adjustedPath) + let fullPath = baseIdPath + adjustedPath let allMethods: Set = Set([.read, .update]) @@ -91,7 +91,7 @@ extension Crudable { let baseIdPath = self.path.appending(.parameter("\(ChildType.schema)ID")) let adjustedPath = path.adjustedPath(for: ChildChildType.self) - let fullPath = baseIdPath.appending(adjustedPath) + let fullPath = baseIdPath + adjustedPath let allMethods: Set = Set([.read, .update]) let controller: CrudChildrenController @@ -122,7 +122,7 @@ extension Crudable { let baseIdPath = self.path.appending(.parameter("\(ChildType.schema)ID")) let adjustedPath = path.adjustedPath(for: ChildChildType.self) - let fullPath = baseIdPath.appending(adjustedPath) + let fullPath = baseIdPath + adjustedPath let allMethods: Set = Set([.read, .readAll, .create, .update, .delete]) let controller: CrudSiblingsController @@ -154,7 +154,7 @@ extension Crudable { let baseIdPath = self.path.appending(.parameter("\(ChildType.schema)ID")) let adjustedPath = path.adjustedPath(for: ChildChildType.self) - let fullPath = baseIdPath.appending(adjustedPath) + let fullPath = baseIdPath + adjustedPath let allMethods: Set = Set([.read, .readAll, .create, .update, .delete]) let controller: CrudSiblingsController diff --git a/Sources/CrudRouter/CrudChildrenController.swift b/Sources/CrudRouter/CrudChildrenController.swift index a57612e..28b9ecd 100644 --- a/Sources/CrudRouter/CrudChildrenController.swift +++ b/Sources/CrudRouter/CrudChildrenController.swift @@ -2,7 +2,7 @@ import Vapor import Fluent public struct CrudChildrenController: CrudChildrenControllerProtocol, Crudable where ChildT.IDValue: LosslessStringConvertible, ParentT.IDValue: LosslessStringConvertible { - public var db: Database +// public var db: Database public var router: RoutesBuilder public typealias ParentType = ParentT diff --git a/Sources/CrudRouter/CrudParentController.swift b/Sources/CrudRouter/CrudParentController.swift index b325dc1..867f1b7 100644 --- a/Sources/CrudRouter/CrudParentController.swift +++ b/Sources/CrudRouter/CrudParentController.swift @@ -2,7 +2,7 @@ import Vapor import Fluent public struct CrudParentController: CrudParentControllerProtocol, Crudable where ChildT.IDValue: LosslessStringConvertible, ParentT.IDValue: LosslessStringConvertible { - public var db: Database +// public var db: Database public typealias ParentType = ParentT public typealias ChildType = ChildT diff --git a/Sources/CrudRouter/CrudSiblingsController.swift b/Sources/CrudRouter/CrudSiblingsController.swift index c0b98be..a3d89a8 100644 --- a/Sources/CrudRouter/CrudSiblingsController.swift +++ b/Sources/CrudRouter/CrudSiblingsController.swift @@ -1,10 +1,15 @@ import Vapor import Fluent -public struct CrudSiblingsController: CrudSiblingsControllerProtocol, Crudable where +public struct CrudSiblingsController< + ChildT: Model & Content, + ParentT: Model & Content, + ThroughT: Model +>: CrudSiblingsControllerProtocol, Crudable where ChildT.IDValue: LosslessStringConvertible, - ParentT.IDValue: LosslessStringConvertible { - public var db: Database + ParentT.IDValue: LosslessStringConvertible +{ +// public var db: Database public typealias ThroughType = ThroughT public typealias ParentType = ParentT @@ -30,39 +35,43 @@ public struct CrudSiblingsController Date: Sun, 17 Nov 2019 11:51:10 -0800 Subject: [PATCH 05/38] Starting to deal with siblings --- .../CrudSiblingsControllerProtocol.swift | 24 +++++++++---------- .../ControllerProtocols/Crudable.swift | 6 ++--- Sources/CrudRouter/Extensions/Array.swift | 4 ++-- .../CrudRouter/Extensions/Router+CRUD.swift | 6 ++--- Sources/CrudRouter/Extensions/String.swift | 2 +- 5 files changed, 21 insertions(+), 21 deletions(-) diff --git a/Sources/CrudRouter/ControllerProtocols/CrudSiblingsControllerProtocol.swift b/Sources/CrudRouter/ControllerProtocols/CrudSiblingsControllerProtocol.swift index d7cee98..93f9b33 100644 --- a/Sources/CrudRouter/ControllerProtocols/CrudSiblingsControllerProtocol.swift +++ b/Sources/CrudRouter/ControllerProtocols/CrudSiblingsControllerProtocol.swift @@ -30,7 +30,7 @@ public extension CrudSiblingsControllerProtocol { let parentId: ParentType.IDValue = try req.getId() let childId: ChildType.IDValue = try req.getId() - return ParentType.find(parentId, on: req.db).unwrap(or: Abort(.notFound)).flatMap { parent -> EventLoopFuture in + return try ParentType.find(parentId, on: req.db).unwrap(or: Abort(.notFound)).throwingFlatMap { parent -> EventLoopFuture in return try parent[keyPath: self.siblings] .query(on: req.db) @@ -43,10 +43,10 @@ public extension CrudSiblingsControllerProtocol { func indexAll(_ req: Request) throws -> EventLoopFuture<[ChildType]> { let parentId: ParentType.IDValue = try req.getId() - return ParentType.find(parentId, on: req.db).unwrap(or: Abort(.notFound)).flatMap { parent -> EventLoopFuture<[ChildType]> in + return try ParentType.find(parentId, on: req.db).unwrap(or: Abort(.notFound)).throwingFlatMap { parent -> EventLoopFuture<[ChildType]> in let siblingsRelation = parent[keyPath: self.siblings] return try siblingsRelation - .query(on: self.db) + .query(on: req.db) .all() } } @@ -55,16 +55,16 @@ public extension CrudSiblingsControllerProtocol { let parentId: ParentType.IDValue = try req.getId() let childId: ChildType.IDValue = try req.getId() - return ParentType + return try ParentType .find(parentId, on: req.db) .unwrap(or: Abort(.notFound)) - .flatMap { parent -> EventLoopFuture in + .throwingFlatMap { parent -> EventLoopFuture in return try parent[keyPath: self.siblings] .query(on: req.db) .filter(\ChildType.fluentID == childId) .first() .unwrap(or: Abort(.notFound)) - }.flatMap { oldChild in + }.throwingFlatMap { oldChild in return try req.content.decode(ChildType.self).flatMap { newChild in var temp = newChild temp.fluentID = oldChild.fluentID @@ -113,13 +113,13 @@ ThroughType.Left == ChildType { func create(_ req: Request) throws -> EventLoopFuture { let parentId: ParentType.IDValue = try req.getId() - return ParentType.find(parentId, on: req.db).unwrap(or: Abort(.notFound)).flatMap { parent -> EventLoopFuture in + return try ParentType.find(parentId, on: req.db).unwrap(or: Abort(.notFound)).throwingFlatMap { parent -> EventLoopFuture in return try req.content.decode(ChildType.self).flatMap { child in return child.create(on: req.db) - }.flatMap { child in - let relation = parent[keyPath: self.siblings] - return relation.attach(child, on: req.db).transform(to: child) + }.flatMap { child in + let relation = parent[keyPath: self.siblings] + return relation.attach(child, on: req.db).transform(to: child) } } } @@ -128,10 +128,10 @@ ThroughType.Left == ChildType { let parentId: ParentType.IDValue = try req.getId() let childId: ChildType.IDValue = try req.getId() - return ParentType + return try ParentType .find(parentId, on: req.db) .unwrap(or: Abort(.notFound)) - .flatMap { parent -> EventLoopFuture in + .throwingFlatMap { parent -> EventLoopFuture in let siblingsRelation = parent[keyPath: self.siblings] return try siblingsRelation .query(on: req.db) diff --git a/Sources/CrudRouter/ControllerProtocols/Crudable.swift b/Sources/CrudRouter/ControllerProtocols/Crudable.swift index 92e81b1..66b5347 100644 --- a/Sources/CrudRouter/ControllerProtocols/Crudable.swift +++ b/Sources/CrudRouter/ControllerProtocols/Crudable.swift @@ -11,7 +11,7 @@ public protocol Crudable: ControllerProtocol { relationConfiguration: ((CrudParentController) -> Void)? ) where ParentType: Model & Content, - ChildType.Database == ParentType.Database, +// ChildType.Database == ParentType.Database, ParentType.IDValue: LosslessStringConvertible func crud( @@ -21,7 +21,7 @@ public protocol Crudable: ControllerProtocol { relationConfiguration: ((CrudChildrenController) -> Void)? ) where ChildChildType: Model & Content, - ChildType.Database == ChildChildType.Database +// ChildType.Database == ChildChildType.Database func crud( at path: PathComponent..., @@ -86,7 +86,7 @@ extension Crudable { relationConfiguration: ((CrudChildrenController) -> Void)?=nil ) where ChildChildType: Model & Content, - ChildType.Database == ChildChildType.Database, +// ChildType.Database == ChildChildType.Database, ChildChildType.IDValue: LosslessStringConvertible { let baseIdPath = self.path.appending(.parameter("\(ChildType.schema)ID")) let adjustedPath = path.adjustedPath(for: ChildChildType.self) diff --git a/Sources/CrudRouter/Extensions/Array.swift b/Sources/CrudRouter/Extensions/Array.swift index c91967c..d01ba9d 100644 --- a/Sources/CrudRouter/Extensions/Array.swift +++ b/Sources/CrudRouter/Extensions/Array.swift @@ -1,7 +1,7 @@ import Vapor public extension Array { - public func appending(_ element: Element) -> Array { + func appending(_ element: Element) -> Array { var temp = self temp.append(element) return temp @@ -11,7 +11,7 @@ public extension Array { extension Array where Element == PathComponent { func adjustedPath(for type: T.Type) -> [PathComponent] { return self.count == 0 - ? [String(describing: T.self).snakeCased()! as PathComponent] + ? [.constant(String(describing: T.self).snakeCased()!)] : self } } diff --git a/Sources/CrudRouter/Extensions/Router+CRUD.swift b/Sources/CrudRouter/Extensions/Router+CRUD.swift index 72741bc..e71630e 100644 --- a/Sources/CrudRouter/Extensions/Router+CRUD.swift +++ b/Sources/CrudRouter/Extensions/Router+CRUD.swift @@ -1,8 +1,8 @@ import Vapor import Fluent -public extension Router { - public func crud( +public extension RoutesBuilder { + func crud( _ path: PathComponent..., register type: ModelType.Type, _ either: OnlyExceptEither = .only([.read, .readAll, .create, .update, .delete]), @@ -18,7 +18,7 @@ public extension Router { controller = CrudController(path: path, router: self, activeMethods: allMethods.subtracting(Set(methods))) } - do { try controller.boot(router: self) } catch { fatalError("I have no reason to expect boot to throw") } + do { try controller.boot(routes: self) } catch { fatalError("I have no reason to expect boot to throw") } relationConfiguration?(controller) } diff --git a/Sources/CrudRouter/Extensions/String.swift b/Sources/CrudRouter/Extensions/String.swift index 01456d0..6aa3279 100644 --- a/Sources/CrudRouter/Extensions/String.swift +++ b/Sources/CrudRouter/Extensions/String.swift @@ -1,7 +1,7 @@ import Foundation public extension String { - public func snakeCased() -> String? { + func snakeCased() -> String? { let pattern = "([a-z0-9])([A-Z])" let regex = try? NSRegularExpression(pattern: pattern, options: []) From a1add9dcd2cea1f62cff32f8642813780a552c47 Mon Sep 17 00:00:00 2001 From: twof Date: Sun, 17 Nov 2019 19:58:26 -0800 Subject: [PATCH 06/38] Some tests passing --- .../CrudChildrenControllerProtocol.swift | 48 ++-- .../CrudControllerProtocol.swift | 10 +- .../CrudSiblingsControllerProtocol.swift | 100 ++++---- .../ControllerProtocols/Crudable.swift | 188 ++++++++-------- Sources/CrudRouter/CrudController.swift | 7 +- .../CrudRouter/CrudSiblingsController.swift | 94 ++++---- .../RouterMethods/SiblingRouterMethod.swift | 49 ++-- Tests/CrudRouterTests/CrudRouterTests.swift | 213 ++++++++---------- Tests/CrudRouterTests/TestModels/Galaxy.swift | 43 ++-- Tests/CrudRouterTests/TestModels/Planet.swift | 43 ++-- .../TestModels/PlanetTag.swift | 43 ++-- Tests/CrudRouterTests/TestModels/Tag.swift | 40 +++- 12 files changed, 460 insertions(+), 418 deletions(-) diff --git a/Sources/CrudRouter/ControllerProtocols/CrudChildrenControllerProtocol.swift b/Sources/CrudRouter/ControllerProtocols/CrudChildrenControllerProtocol.swift index 43e54c6..3cbc83f 100644 --- a/Sources/CrudRouter/ControllerProtocols/CrudChildrenControllerProtocol.swift +++ b/Sources/CrudRouter/ControllerProtocols/CrudChildrenControllerProtocol.swift @@ -3,6 +3,14 @@ import FluentKit import Fluent import NIOExtras +extension EventLoopFuture where Value: Model { + func delete(on db: Database) -> EventLoopFuture { + return self.map { model in + return model.delete(on: db) + } + } +} + public protocol CrudChildrenControllerProtocol { associatedtype ParentType: Model & Content where ParentType.IDValue: LosslessStringConvertible associatedtype ChildType: Model & Content where ChildType.IDValue: LosslessStringConvertible @@ -19,23 +27,16 @@ public protocol CrudChildrenControllerProtocol { public extension CrudChildrenControllerProtocol { func index(_ req: Request) throws -> EventLoopFuture { let parentId: ParentType.IDValue = try req.getId() - let childId: ChildType.IDValue = try req.getId() - let thing = ParentType - .query(on: req.db) - .join(ChildType.self, on: children == \ParentType.$id) - - return - -// .find(parentId, on: db) -// .unwrap(or: Abort(.notFound)) -// .flatMap { parent -> EventLoopFuture in -// return try! parent[keyPath: self.children] -// .query(on: self.db) -// .filter(\ChildType.id == childId) -// .first() -// .unwrap(or: Abort(.notFound)) -// } + return try ParentType + .find(parentId, on: req.db) + .unwrap(or: Abort(.notFound)) + .throwingFlatMap { parent -> EventLoopFuture in + return try parent[keyPath: self.children] + .query(on: req.db) + .first() + .unwrap(or: Abort(.notFound)) + } } func indexAll(_ req: Request) throws -> EventLoopFuture<[ChildType]> { @@ -58,13 +59,12 @@ public extension CrudChildrenControllerProtocol { .unwrap(or: Abort(.notFound)) .throwingFlatMap { parent -> EventLoopFuture in let child = try req.content.decode(ChildType.self) - return try parent[keyPath: self.children].query(on: req.db) + return child.save(on: req.db).transform(to: child) } } func update(_ req: Request) throws -> EventLoopFuture { let parentId: ParentType.IDValue = try req.getId() - let childId: ChildType.IDValue = try req.getId() return try ParentType .find(parentId, on: req.db) @@ -72,21 +72,18 @@ public extension CrudChildrenControllerProtocol { .throwingFlatMap { parent -> EventLoopFuture in return try parent[keyPath: self.children] .query(on: req.db) - .filter(\ChildType.fluentID == childId) .first() .unwrap(or: Abort(.notFound)) }.throwingFlatMap { oldChild in - return try req.content.decode(ChildType.self).flatMap { newChild in - var temp = newChild - temp.fluentID = oldChild.fluentID - return temp.update(on: req.db) - } + let newChild = try req.content.decode(ChildType.self) + let temp = newChild + temp.id = oldChild.id + return temp.update(on: req.db).transform(to: temp) } } func delete(_ req: Request) throws -> EventLoopFuture { let parentId: ParentType.IDValue = try req.getId() - let childId: ChildType.IDValue = try req.getId() return try ParentType .find(parentId, on: req.db) @@ -94,7 +91,6 @@ public extension CrudChildrenControllerProtocol { .throwingFlatMap { parent -> EventLoopFuture in return try parent[keyPath: self.children] .query(on: req.db) - .filter(\ChildType.fluentID == childId) .first() .unwrap(or: Abort(.notFound)) .delete(on: req.db) diff --git a/Sources/CrudRouter/ControllerProtocols/CrudControllerProtocol.swift b/Sources/CrudRouter/ControllerProtocols/CrudControllerProtocol.swift index c8dd69e..0dfc625 100644 --- a/Sources/CrudRouter/ControllerProtocols/CrudControllerProtocol.swift +++ b/Sources/CrudRouter/ControllerProtocols/CrudControllerProtocol.swift @@ -3,7 +3,6 @@ import Fluent public protocol CrudControllerProtocol { associatedtype ModelType: Model, Content where ModelType.IDValue: LosslessStringConvertible -// var db: Database { get } func indexAll(_ req: Request) throws -> EventLoopFuture<[ModelType]> func index(_ req: Request) throws -> EventLoopFuture @@ -23,9 +22,8 @@ public extension CrudControllerProtocol { } func create(_ req: Request) throws -> EventLoopFuture { - return try req.content.decode(ModelType.self).save(on: req.db).map { - return - } + let model = try req.content.decode(ModelType.self) + return model.save(on: req.db).transform(to: model) } func update(_ req: Request) throws -> EventLoopFuture { @@ -34,9 +32,7 @@ public extension CrudControllerProtocol { let temp = model temp.id = id - return temp.update(on: req.db).map { - return - } + return temp.update(on: req.db).transform(to: temp) } func delete(_ req: Request) throws -> EventLoopFuture { diff --git a/Sources/CrudRouter/ControllerProtocols/CrudSiblingsControllerProtocol.swift b/Sources/CrudRouter/ControllerProtocols/CrudSiblingsControllerProtocol.swift index 93f9b33..6725c25 100644 --- a/Sources/CrudRouter/ControllerProtocols/CrudSiblingsControllerProtocol.swift +++ b/Sources/CrudRouter/ControllerProtocols/CrudSiblingsControllerProtocol.swift @@ -1,5 +1,6 @@ import Vapor import Fluent +import FluentKit extension EventLoopFuture { func throwingFlatMap(file: StaticString = #file, line: UInt = #line, _ callback: @escaping ((Value) throws -> EventLoopFuture)) rethrows -> EventLoopFuture { @@ -28,13 +29,11 @@ public protocol CrudSiblingsControllerProtocol { public extension CrudSiblingsControllerProtocol { func index(_ req: Request) throws -> EventLoopFuture { let parentId: ParentType.IDValue = try req.getId() - let childId: ChildType.IDValue = try req.getId() return try ParentType.find(parentId, on: req.db).unwrap(or: Abort(.notFound)).throwingFlatMap { parent -> EventLoopFuture in return try parent[keyPath: self.siblings] .query(on: req.db) - .filter(\ChildType.fluentID == childId) .first() .unwrap(or: Abort(.notFound)) } @@ -53,29 +52,30 @@ public extension CrudSiblingsControllerProtocol { func update(_ req: Request) throws -> EventLoopFuture { let parentId: ParentType.IDValue = try req.getId() - let childId: ChildType.IDValue = try req.getId() return try ParentType .find(parentId, on: req.db) .unwrap(or: Abort(.notFound)) .throwingFlatMap { parent -> EventLoopFuture in - return try parent[keyPath: self.siblings] - .query(on: req.db) - .filter(\ChildType.fluentID == childId) + let siblings: Siblings = parent[keyPath: self.siblings] + let siblingsQuery = try siblings.query(on: req.db) + return siblingsQuery .first() .unwrap(or: Abort(.notFound)) }.throwingFlatMap { oldChild in - return try req.content.decode(ChildType.self).flatMap { newChild in - var temp = newChild - temp.fluentID = oldChild.fluentID - return temp.update(on: req.db) - } - } + let newChild = try req.content.decode(ChildType.self) + let temp = newChild + temp.id = oldChild.id + return temp.update(on: req.db).transform(to: temp) + } } } -public extension CrudSiblingsControllerProtocol where ThroughType.Left == ParentType, -ThroughType.Right == ChildType { +public extension CrudSiblingsControllerProtocol +// where +// ThroughType.Left == ParentType, +// ThroughType.Right == ChildType +{ func create(_ req: Request) throws -> EventLoopFuture { let parentId: ParentType.IDValue = try req.getId() @@ -89,7 +89,6 @@ ThroughType.Right == ChildType { func delete(_ req: Request) throws -> EventLoopFuture { let parentId: ParentType.IDValue = try req.getId() - let childId: ChildType.IDValue = try req.getId() return try ParentType .find(parentId, on: req.db) @@ -98,7 +97,6 @@ ThroughType.Right == ChildType { let siblingsRelation = parent[keyPath: self.siblings] return try siblingsRelation .query(on: req.db) - .filter(\ChildType.fluentID == childId) .first() .unwrap(or: Abort(.notFound)) .flatMap { siblingsRelation.detach($0, on: req.db).transform(to: $0) } @@ -108,38 +106,38 @@ ThroughType.Right == ChildType { } } -public extension CrudSiblingsControllerProtocol where ThroughType.Right == ParentType, -ThroughType.Left == ChildType { - func create(_ req: Request) throws -> EventLoopFuture { - let parentId: ParentType.IDValue = try req.getId() - - return try ParentType.find(parentId, on: req.db).unwrap(or: Abort(.notFound)).throwingFlatMap { parent -> EventLoopFuture in - - return try req.content.decode(ChildType.self).flatMap { child in - return child.create(on: req.db) - }.flatMap { child in - let relation = parent[keyPath: self.siblings] - return relation.attach(child, on: req.db).transform(to: child) - } - } - } - - func delete(_ req: Request) throws -> EventLoopFuture { - let parentId: ParentType.IDValue = try req.getId() - let childId: ChildType.IDValue = try req.getId() - - return try ParentType - .find(parentId, on: req.db) - .unwrap(or: Abort(.notFound)) - .throwingFlatMap { parent -> EventLoopFuture in - let siblingsRelation = parent[keyPath: self.siblings] - return try siblingsRelation - .query(on: req.db) - .filter(\ChildType.fluentID == childId) - .first() - .unwrap(or: Abort(.notFound)) - .delete(on: req.db) - .transform(to: HTTPStatus.ok) - } - } -} +//public extension CrudSiblingsControllerProtocol where ThroughType.Right == ParentType, +//ThroughType.Left == ChildType { +// func create(_ req: Request) throws -> EventLoopFuture { +// let parentId: ParentType.IDValue = try req.getId() +// +// return try ParentType.find(parentId, on: req.db).unwrap(or: Abort(.notFound)).throwingFlatMap { parent -> EventLoopFuture in +// +// return try req.content.decode(ChildType.self).flatMap { child in +// return child.create(on: req.db) +// }.flatMap { child in +// let relation = parent[keyPath: self.siblings] +// return relation.attach(child, on: req.db).transform(to: child) +// } +// } +// } +// +// func delete(_ req: Request) throws -> EventLoopFuture { +// let parentId: ParentType.IDValue = try req.getId() +// let childId: ChildType.IDValue = try req.getId() +// +// return try ParentType +// .find(parentId, on: req.db) +// .unwrap(or: Abort(.notFound)) +// .throwingFlatMap { parent -> EventLoopFuture in +// let siblingsRelation = parent[keyPath: self.siblings] +// return try siblingsRelation +// .query(on: req.db) +// .filter(\ChildType.fluentID == childId) +// .first() +// .unwrap(or: Abort(.notFound)) +// .delete(on: req.db) +// .transform(to: HTTPStatus.ok) +// } +// } +//} diff --git a/Sources/CrudRouter/ControllerProtocols/Crudable.swift b/Sources/CrudRouter/ControllerProtocols/Crudable.swift index 66b5347..8b3e60b 100644 --- a/Sources/CrudRouter/ControllerProtocols/Crudable.swift +++ b/Sources/CrudRouter/ControllerProtocols/Crudable.swift @@ -2,7 +2,7 @@ import Vapor import Fluent public protocol Crudable: ControllerProtocol { - associatedtype ChildType: Model, Content + associatedtype ChildType: Model, Content where ChildType.IDValue: LosslessStringConvertible func crud( at path: PathComponent..., @@ -20,7 +20,7 @@ public protocol Crudable: ControllerProtocol { _ either: OnlyExceptEither, relationConfiguration: ((CrudChildrenController) -> Void)? ) where - ChildChildType: Model & Content, + ChildChildType: Model & Content // ChildType.Database == ChildChildType.Database func crud( @@ -31,21 +31,21 @@ public protocol Crudable: ControllerProtocol { ) where ChildChildType: Content, ChildChildType.IDValue: LosslessStringConvertible, - ThroughType: Model, - ThroughType.Left == ChildType, - ThroughType.Right == ChildChildType - - func crud( - at path: PathComponent..., - siblings relation: KeyPath>, - _ either: OnlyExceptEither, - relationConfiguration: ((CrudSiblingsController) -> Void)? - ) where - ChildChildType: Content, - ChildChildType.IDValue: LosslessStringConvertible, - ThroughType: Model, - ThroughType.Right == ChildType, - ThroughType.Left == ChildChildType + ThroughType: Model +// ThroughType.Left == ChildType, +// ThroughType.Right == ChildChildType + +// func crud( +// at path: PathComponent..., +// siblings relation: KeyPath>, +// _ either: OnlyExceptEither, +// relationConfiguration: ((CrudSiblingsController) -> Void)? +// ) where +// ChildChildType: Content, +// ChildChildType.IDValue: LosslessStringConvertible, +// ThroughType: Model, +// ThroughType.Right == ChildType, +// ThroughType.Left == ChildChildType } extension Crudable { @@ -57,26 +57,27 @@ extension Crudable { ) where ParentType: Model & Content, // ChildType.Database == ParentType.Database, - ParentType.IDValue: LosslessStringConvertible { - let baseIdPath = self.path.appending(.parameter("\(ChildType.schema)ID")) - let adjustedPath = path.adjustedPath(for: ParentType.self) + ParentType.IDValue: LosslessStringConvertible + { + let baseIdPath = self.path.appending(.parameter("\(ChildType.schema)ID")) + let adjustedPath = path.adjustedPath(for: ParentType.self) - let fullPath = baseIdPath + adjustedPath + let fullPath = baseIdPath + adjustedPath - let allMethods: Set = Set([.read, .update]) - let controller: CrudParentController + let allMethods: Set = Set([.read, .update]) + let controller: CrudParentController - switch either { - case .only(let methods): - controller = CrudParentController(relation: relation, path: fullPath, router: self.router, activeMethods: Set(methods)) - case .except(let methods): - controller = CrudParentController(relation: relation, path: fullPath, router: self.router, activeMethods: allMethods.subtracting(Set(methods))) - } + switch either { + case .only(let methods): + controller = CrudParentController(relation: relation, path: fullPath, router: self.router, activeMethods: Set(methods)) + case .except(let methods): + controller = CrudParentController(relation: relation, path: fullPath, router: self.router, activeMethods: allMethods.subtracting(Set(methods))) + } - do { try controller.boot(routes: self.router) } catch { fatalError("I have no reason to expect boot to throw") } + do { try controller.boot(routes: self.router) } catch { fatalError("I have no reason to expect boot to throw") } - relationConfiguration?(controller) + relationConfiguration?(controller) } public func crud( @@ -87,25 +88,26 @@ extension Crudable { ) where ChildChildType: Model & Content, // ChildType.Database == ChildChildType.Database, - ChildChildType.IDValue: LosslessStringConvertible { - let baseIdPath = self.path.appending(.parameter("\(ChildType.schema)ID")) - let adjustedPath = path.adjustedPath(for: ChildChildType.self) + ChildChildType.IDValue: LosslessStringConvertible + { + let baseIdPath = self.path.appending(.parameter("\(ChildType.schema)ID")) + let adjustedPath = path.adjustedPath(for: ChildChildType.self) - let fullPath = baseIdPath + adjustedPath + let fullPath = baseIdPath + adjustedPath - let allMethods: Set = Set([.read, .update]) - let controller: CrudChildrenController + let allMethods: Set = Set([.read, .update]) + let controller: CrudChildrenController - switch either { - case .only(let methods): - controller = CrudChildrenController(childrenRelation: relation, path: fullPath, router: self.router, activeMethods: Set(methods)) - case .except(let methods): - controller = CrudChildrenController(childrenRelation: relation, path: fullPath, router: self.router, activeMethods: allMethods.subtracting(Set(methods))) - } + switch either { + case .only(let methods): + controller = CrudChildrenController(childrenRelation: relation, path: fullPath, router: self.router, activeMethods: Set(methods)) + case .except(let methods): + controller = CrudChildrenController(childrenRelation: relation, path: fullPath, router: self.router, activeMethods: allMethods.subtracting(Set(methods))) + } - do { try controller.boot(routes: self.router) } catch { fatalError("I have no reason to expect boot to throw") } + do { try controller.boot(routes: self.router) } catch { fatalError("I have no reason to expect boot to throw") } - relationConfiguration?(controller) + relationConfiguration?(controller) } public func crud( @@ -116,58 +118,60 @@ extension Crudable { ) where ChildChildType: Content, ChildChildType.IDValue: LosslessStringConvertible, - ThroughType: Model, - ThroughType.Left == ChildType, - ThroughType.Right == ChildChildType { - let baseIdPath = self.path.appending(.parameter("\(ChildType.schema)ID")) - let adjustedPath = path.adjustedPath(for: ChildChildType.self) + ThroughType: Model +// ThroughType.Left == ChildType, +// ThroughType.Right == ChildChildType + { + let baseIdPath = self.path.appending(.parameter("\(ChildType.schema)ID")) + let adjustedPath = path.adjustedPath(for: ChildChildType.self) - let fullPath = baseIdPath + adjustedPath + let fullPath = baseIdPath + adjustedPath - let allMethods: Set = Set([.read, .readAll, .create, .update, .delete]) - let controller: CrudSiblingsController + let allMethods: Set = Set([.read, .readAll, .create, .update, .delete]) + let controller: CrudSiblingsController - switch either { - case .only(let methods): - controller = CrudSiblingsController(siblingRelation: relation, path: fullPath, router: - self.router, activeMethods: Set(methods)) - case .except(let methods): - controller = CrudSiblingsController(siblingRelation: relation, path: fullPath, router: self.router, activeMethods: allMethods.subtracting(Set(methods))) - } + switch either { + case .only(let methods): + controller = CrudSiblingsController(siblingRelation: relation, path: fullPath, router: + self.router, activeMethods: Set(methods)) + case .except(let methods): + controller = CrudSiblingsController(siblingRelation: relation, path: fullPath, router: self.router, activeMethods: allMethods.subtracting(Set(methods))) + } - do { try controller.boot(routes: self.router) } catch { fatalError("I have no reason to expect boot to throw") } + do { try controller.boot(routes: self.router) } catch { fatalError("I have no reason to expect boot to throw") } - relationConfiguration?(controller) + relationConfiguration?(controller) } - public func crud( - at path: PathComponent..., - siblings relation: KeyPath>, - _ either: OnlyExceptEither = .only([.read, .readAll, .create, .update, .delete]), - relationConfiguration: ((CrudSiblingsController) -> Void)?=nil - ) where - ChildChildType: Content, - ChildChildType.IDValue: LosslessStringConvertible, - ThroughType: Model, - ThroughType.Right == ChildType, - ThroughType.Left == ChildChildType { - let baseIdPath = self.path.appending(.parameter("\(ChildType.schema)ID")) - let adjustedPath = path.adjustedPath(for: ChildChildType.self) - - let fullPath = baseIdPath + adjustedPath - - let allMethods: Set = Set([.read, .readAll, .create, .update, .delete]) - let controller: CrudSiblingsController - - switch either { - case .only(let methods): - controller = CrudSiblingsController(siblingRelation: relation, path: fullPath, router: self.router, activeMethods: Set(methods)) - case .except(let methods): - controller = CrudSiblingsController(siblingRelation: relation, path: fullPath, router: self.router, activeMethods: allMethods.subtracting(Set(methods))) - } - - do { try controller.boot(routes: self.router) } catch { fatalError("I have no reason to expect boot to throw") } - - relationConfiguration?(controller) - } +// public func crud( +// at path: PathComponent..., +// siblings relation: KeyPath>, +// _ either: OnlyExceptEither = .only([.read, .readAll, .create, .update, .delete]), +// relationConfiguration: ((CrudSiblingsController) -> Void)?=nil +// ) where +// ChildChildType: Content, +// ChildChildType.IDValue: LosslessStringConvertible, +// ThroughType: Model +//// ThroughType.Right == ChildType, +//// ThroughType.Left == ChildChildType +// { +// let baseIdPath = self.path.appending(.parameter("\(ChildType.schema)ID")) +// let adjustedPath = path.adjustedPath(for: ChildChildType.self) +// +// let fullPath = baseIdPath + adjustedPath +// +// let allMethods: Set = Set([.read, .readAll, .create, .update, .delete]) +// let controller: CrudSiblingsController +// +// switch either { +// case .only(let methods): +// controller = CrudSiblingsController(siblingRelation: relation, path: fullPath, router: self.router, activeMethods: Set(methods)) +// case .except(let methods): +// controller = CrudSiblingsController(siblingRelation: relation, path: fullPath, router: self.router, activeMethods: allMethods.subtracting(Set(methods))) +// } +// +// do { try controller.boot(routes: self.router) } catch { fatalError("I have no reason to expect boot to throw") } +// +// relationConfiguration?(controller) +// } } diff --git a/Sources/CrudRouter/CrudController.swift b/Sources/CrudRouter/CrudController.swift index d22b971..5e2c054 100644 --- a/Sources/CrudRouter/CrudController.swift +++ b/Sources/CrudRouter/CrudController.swift @@ -10,12 +10,15 @@ public struct CrudController: CrudControllerProtocol, C public typealias ChildType = ModelT public typealias ModelType = ModelT - public let db: Database public let path: [PathComponent] public let router: RoutesBuilder let activeMethods: Set - init(path: [PathComponent], router: RoutesBuilder, activeMethods: Set) { + init( + path: [PathComponent], + router: RoutesBuilder, + activeMethods: Set + ) { let adjustedPath = path.adjustedPath(for: ModelType.self) self.path = adjustedPath diff --git a/Sources/CrudRouter/CrudSiblingsController.swift b/Sources/CrudRouter/CrudSiblingsController.swift index a3d89a8..59dacfa 100644 --- a/Sources/CrudRouter/CrudSiblingsController.swift +++ b/Sources/CrudRouter/CrudSiblingsController.swift @@ -2,19 +2,17 @@ import Vapor import Fluent public struct CrudSiblingsController< - ChildT: Model & Content, - ParentT: Model & Content, - ThroughT: Model + ChildType: Model & Content, + ParentType: Model & Content, + ThroughType: Model >: CrudSiblingsControllerProtocol, Crudable where - ChildT.IDValue: LosslessStringConvertible, - ParentT.IDValue: LosslessStringConvertible + ChildType.IDValue: LosslessStringConvertible, + ParentType.IDValue: LosslessStringConvertible { -// public var db: Database + public typealias ParentType = ParentType + public typealias ChildType = ChildType + public typealias ThroughType = ThroughType - public typealias ThroughType = ThroughT - public typealias ParentType = ParentT - public typealias ChildType = ChildT - public var siblings: KeyPath> public let path: [PathComponent] public let router: RoutesBuilder @@ -35,49 +33,49 @@ public struct CrudSiblingsController< extension CrudSiblingsController: RouteCollection {} -public extension CrudSiblingsController where - ThroughType.Right == ParentType, - ThroughType.Left == ChildType -{ - func boot(routes router: RoutesBuilder) throws { - let parentPath = self.path - let parentIdPath = self.path.appending(.parameter("\(ParentType.schema)ID")) - - self.activeMethods.forEach { - $0.register( - router: router, - controller: self, - path: parentPath, - idPath: parentIdPath - ) - } - } -} - -public extension CrudSiblingsController where - ThroughType.Left == ParentType, - ThroughType.Right == ChildType -{ - func boot(routes router: RoutesBuilder) throws { - let parentPath = self.path - let parentIdPath = self.path.appending(.parameter("\(ParentType.schema)ID")) - - self.activeMethods.forEach { - $0.register( - router: router, - controller: self, - path: parentPath, - idPath: parentIdPath - ) - } - } -} +//public extension CrudSiblingsController where +// ThroughType.Right == ParentType, +// ThroughType.Left == ChildType +//{ +// func boot(routes router: RoutesBuilder) throws { +// let parentPath = self.path +// let parentIdPath = self.path.appending(.parameter("\(ParentType.schema)ID")) +// +// self.activeMethods.forEach { +// $0.register( +// router: router, +// controller: self, +// path: parentPath, +// idPath: parentIdPath +// ) +// } +// } +//} +// +//public extension CrudSiblingsController where +// ThroughType.Left == ParentType, +// ThroughType.Right == ChildType +//{ +// func boot(routes router: RoutesBuilder) throws { +// let parentPath = self.path +// let parentIdPath = self.path.appending(.parameter("\(ParentType.schema)ID")) +// +// self.activeMethods.forEach { +// $0.register( +// router: router, +// controller: self, +// path: parentPath, +// idPath: parentIdPath +// ) +// } +// } +//} public extension CrudSiblingsController { func boot(routes router: RoutesBuilder) throws { let parentPath = self.path let parentIdPath = self.path.appending(.parameter("\(ParentType.schema)ID")) - + router.get(parentIdPath, use: self.index) router.get(parentPath, use: self.indexAll) router.put(parentIdPath, use: self.update) diff --git a/Sources/CrudRouter/RouterMethods/SiblingRouterMethod.swift b/Sources/CrudRouter/RouterMethods/SiblingRouterMethod.swift index 9f2131c..f3b8808 100644 --- a/Sources/CrudRouter/RouterMethods/SiblingRouterMethod.swift +++ b/Sources/CrudRouter/RouterMethods/SiblingRouterMethod.swift @@ -8,34 +8,39 @@ public enum ModifiableSiblingRouterMethod { case update case delete - func register( - router: RoutesBuilder, - controller: CrudSiblingsController, - path: [PathComponent], - idPath: [PathComponent] - ) where ThroughType.Left == ParentType, - ThroughType.Right == ChildType { - switch self { - case .read: - router.get(idPath, use: controller.index) - case .readAll: - router.get(path, use: controller.indexAll) - case .create: - router.post(path, use: controller.create) - case .update: - router.put(idPath, use: controller.update) - case .delete: - router.delete(idPath, use: controller.delete) - } - } +// func register( +// router: RoutesBuilder, +// controller: CrudSiblingsController, +// path: [PathComponent], +// idPath: [PathComponent] +// ) where +// ThroughType.Left == ParentType, +// ThroughType.Right == ChildType +// { +// switch self { +// case .read: +// router.get(idPath, use: controller.index) +// case .readAll: +// router.get(path, use: controller.indexAll) +// case .create: +// router.post(path, use: controller.create) +// case .update: +// router.put(idPath, use: controller.update) +// case .delete: +// router.delete(idPath, use: controller.delete) +// } +// } func register( router: RoutesBuilder, controller: CrudSiblingsController, path: [PathComponent], idPath: [PathComponent] - ) where ThroughType.Left == ChildType, - ThroughType.Right == ParentType { + ) +// where +// ThroughType.Left == ChildType, +// ThroughType.Right == ParentType + { switch self { case .read: router.get(idPath, use: controller.index) diff --git a/Tests/CrudRouterTests/CrudRouterTests.swift b/Tests/CrudRouterTests/CrudRouterTests.swift index 4a064ec..bde3a6d 100644 --- a/Tests/CrudRouterTests/CrudRouterTests.swift +++ b/Tests/CrudRouterTests/CrudRouterTests.swift @@ -1,103 +1,102 @@ import XCTest @testable import CrudRouter import Vapor -import FluentSQLite +import FluentSQLiteDriver +import Fluent extension PathComponent { var stringComponent: String { switch self { - case .constant(let val), .parameter(let val): + case .constant(let val): return val + case .parameter(let val): + return ":\(val)" default: return "all" } } } -extension Array where Element: Model, Element.Database: TransactionSupporting { - func save(on conn: Element.Database.Connection) -> EventLoopFuture<[Element]> { - let databaseIdentifier = Element.defaultDatabase! - - return conn.transaction(on: databaseIdentifier) { (dbConn) -> EventLoopEventLoopFuture<[Element]> in - return self.map { $0.save(on: dbConn) }.flatten(on: dbConn) - } - } - - func delete(on conn: Element.Database.Connection) -> EventLoopFuture<[Element]> { - let databaseIdentifier = Element.defaultDatabase! - - return conn.transaction(on: databaseIdentifier) { (dbConn) -> EventLoopEventLoopFuture<[Element]> in - return self.map { element in - return element.delete(on: dbConn).transform(to: element) - }.flatten(on: dbConn) - } - } - - func create(on conn: Element.Database.Connection) -> EventLoopFuture<[Element]> { - let databaseIdentifier = Element.defaultDatabase! - - return conn.transaction(on: databaseIdentifier) { (dbConn) -> EventLoopEventLoopFuture<[Element]> in - return self.map { $0.create(on: dbConn) }.flatten(on: dbConn) - } - } -} - - -struct TestSeeding: SQLiteMigration { +//extension Array where Element: Model { +// func save(on conn: Database) -> EventLoopFuture<[Element]> { +// +// return conn.transaction(on: conn) { (dbConn) -> EventLoopFuture<[Element]> in +// return self.map { $0.save(on: dbConn) }.flatten(on: dbConn) +// } +// } +// +// func delete(on conn: Element.Database.Connection) -> EventLoopFuture<[Element]> { +// let databaseIdentifier = Element.defaultDatabase! +// +// return conn.transaction(on: databaseIdentifier) { (dbConn) -> EventLoopFuture<[Element]> in +// return self.map { element in +// return element.delete(on: dbConn).transform(to: element) +// }.flatten(on: dbConn) +// } +// } +// +// func create(on conn: Element.Database.Connection) -> EventLoopFuture<[Element]> { +// let databaseIdentifier = Element.defaultDatabase! +// +// return conn.transaction(on: databaseIdentifier) { (dbConn) -> EventLoopEventLoopFuture<[Element]> in +// return self.map { $0.create(on: dbConn) }.flatten(on: dbConn) +// } +// } +//} + + +struct TestSeeding: Migration { + func prepare(on database: Database) -> EventLoopFuture { + return TestSeeding.galaxies.create(on: database).transform(to: ()) + } + + func revert(on database: Database) -> EventLoopFuture { + return TestSeeding.galaxies.map { + $0.delete(on: database).transform(to: ()) + }.flatten(on: database.eventLoop) + } + static let galaxies = [Galaxy(name: "Milky Way")] - - static func prepare(on conn: SQLiteConnection) -> EventLoopEventLoopFuture { - return TestSeeding.galaxies.create(on: conn).transform(to: ()) - } - - static func revert(on conn: SQLiteConnection) -> EventLoopEventLoopFuture { - return TestSeeding.galaxies.delete(on: conn).transform(to: ()) - } } -func configure(_ config: inout Config, _ env: inout Environment, _ services: inout Services) throws { - /// Register providers first - try services.register(FluentSQLiteProvider()) - - /// Register routes to the router - let router = EngineRouter.default() - try routes(router) - services.register(router, as: Router.self) - - /// Register middleware - var middlewares = MiddlewareConfig() // Create _empty_ middleware config - middlewares.use(ErrorMiddleware.self) // Catches errors and converts to HTTP response - services.register(middlewares) - - // Configure a SQLite database - let sqlite = try SQLiteDatabase(storage: .memory) - - /// Register the configured SQLite database to the database config. - var databases = DatabasesConfig() - databases.add(database: sqlite, as: .sqlite) - services.register(databases) - - /// Configure migrations - var migrations = MigrationConfig() - migrations.add(model: Galaxy.self, database: .sqlite) - migrations.add(model: Planet.self, database: .sqlite) - migrations.add(model: PlanetTag.self, database: .sqlite) - migrations.add(model: Tag.self, database: .sqlite) - migrations.add(migration: TestSeeding.self, database: .sqlite) - migrations.prepareCache(for: .sqlite) - services.register(migrations) +func configure(_ app: Application) throws { + app.provider(FluentProvider()) + + // Register middleware + app.register(extension: MiddlewareConfiguration.self) { middlewares, app in + // Serves files from `Public/` directory + // middlewares.use(app.make(FileMiddleware.self)) + } + + app.databases.sqlite( + configuration: .init(storage: .connection(.file(path: "db.sqlite"))), + threadPool: app.make(), + poolConfiguration: app.make(), + logger: app.make(), + on: app.make() + ) + + app.register(Migrations.self) { c in + var migrations = Migrations() + migrations.add(GalaxyMigration(), to: .sqlite) + migrations.add(PlanetMigration(), to: .sqlite) + migrations.add(PlanetTagMigration(), to: .sqlite) + migrations.add(TagMigration(), to: .sqlite) + migrations.add(TestSeeding(), to: .sqlite) + return migrations + } } -func routes(_ router: Router) throws { +func routes(_ router: RoutesBuilder) throws { router.crud(register: Galaxy.self) { controller in - controller.crud(children: \.planets) + controller.crud(children: \.$planets) } router.crud(register: Planet.self) { controller in - controller.crud(parent: \.galaxy) - controller.crud(siblings: \.tags) + controller.crud(parent: \.$galaxy) + controller.crud(siblings: \.$tags) } router.crud(register: Tag.self) { controller in - controller.crud(siblings: \.planets) + controller.crud(siblings: \.$planets) } } @@ -105,30 +104,17 @@ func boot(_ app: Application) throws { } extension Application { static func testable(envArgs: [String]? = nil) throws -> Application { - var config = Config.default() - var services = Services.default() - var env = Environment.testing - - if let environmentArgs = envArgs { - env.arguments = environmentArgs - } + let app = Application() + try configure(app) - try configure(&config, &env, &services) - let app = try Application(config: config, environment: env, services: services) - - try boot(app) + try boot(app)() return app } func sendRequest(to path: String, method: HTTPMethod, headers: HTTPHeaders = .init(), body: T? = nil) throws -> Response where T: Content { - let headers = headers - let responder = try self.make(Responder.self) - let request = HTTPRequest(method: method, url: URL(string: path)!, headers: headers) - let wrappedRequest = Request(http: request, using: self) - if let body = body { - try wrappedRequest.content.encode(body) - } - return try responder.respond(to: wrappedRequest).wait() + let responder = self.make(Responder.self) + let request = Request(application: self, method: method, url: URI(path: path), on: self.make()) + return try responder.respond(to: request).wait() } func sendRequest(to path: String, method: HTTPMethod, headers: HTTPHeaders = .init()) throws -> Response { @@ -138,7 +124,7 @@ extension Application { func getResponse(to path: String, method: HTTPMethod = .GET, headers: HTTPHeaders = .init(), data: C? = nil, decodeTo type: T.Type) throws -> T where C: Content, T: Decodable { let response = try self.sendRequest(to: path, method: method, headers: headers, body: data) - return try response.content.decode(type).wait() + return try response.content.decode(type) } func getResponse(to path: String, method: HTTPMethod = .GET, headers: HTTPHeaders = .init(), decodeTo type: T.Type) throws -> T where T: Content { @@ -163,42 +149,41 @@ final class CrudRouterTests: XCTestCase { } func testBaseCrudRegistrationWithRouteName() throws { - let router = EngineRouter.default() - router.crud("planets", register: Planet.self) + app.crud("planets", register: Planet.self) - XCTAssert(router.routes.isEmpty == false) - XCTAssert(router.routes.count == 5) - let paths = router.routes.map { $0.path.map { $0.stringComponent } } + XCTAssert(app.routes.all.isEmpty == false) + XCTAssert(app.routes.all.count == 5) + let paths = app.routes.all.map { [$0.method.rawValue] + $0.path.map { $0.stringComponent } } XCTAssert(paths.contains { $0 == ["GET", "planets"] }) - XCTAssert(paths.contains { $0 == ["GET", "planets", "int"] }) + XCTAssert(paths.contains { $0 == ["GET", "planets", ":planetsID"] }) XCTAssert(paths.contains { $0 == ["POST", "planets"] }) - XCTAssert(paths.contains { $0 == ["PUT", "planets", "int"] }) - XCTAssert(paths.contains { $0 == ["DELETE", "planets", "int"] }) + XCTAssert(paths.contains { $0 == ["PUT", "planets", ":planetsID"] }) + XCTAssert(paths.contains { $0 == ["DELETE", "planets", ":planetsID"] }) } func testBaseCrudRegistrationWithDefaultRoute() throws { - let router = EngineRouter.default() - router.crud(register: Planet.self) + app.crud(register: Planet.self) - XCTAssert(router.routes.isEmpty == false) - XCTAssert(router.routes.count == 5) - let paths = router.routes.map { $0.path.map { $0.stringComponent } } + XCTAssert(app.routes.all.isEmpty == false) + XCTAssert(app.routes.all.count == 5) + let paths = app.routes.all.map { [$0.method.rawValue] + $0.path.map { $0.stringComponent } } XCTAssert(paths.contains { $0 == ["GET", "planet"] }) - XCTAssert(paths.contains { $0 == ["GET", "planet", "int"] }) + XCTAssert(paths.contains { $0 == ["GET", "planet", ":planetsID"] }) XCTAssert(paths.contains { $0 == ["POST", "planet"] }) - XCTAssert(paths.contains { $0 == ["PUT", "planet", "int"] }) - XCTAssert(paths.contains { $0 == ["DELETE", "planet", "int"] }) + XCTAssert(paths.contains { $0 == ["PUT", "planet", ":planetsID"] }) + XCTAssert(paths.contains { $0 == ["DELETE", "planet", ":planetsID"] }) } func testPublicable() throws { do { // let resp = try app.getResponse(to: "/galaxy", method: .GET, decodeTo: [Galaxy.PublicGalaxy].self) let resp = try app.sendRequest(to: "/galaxy", method: .GET) - XCTAssert(try resp.content.syncDecode([Galaxy].self).count == 1) + let decoded = try resp.content.decode([Galaxy].self) + XCTAssert(decoded.count == 1) // XCTAssert(resp.count == 1) // XCTAssert(resp[0].nameAndId == "Milky Way 0") } catch { @@ -210,5 +195,5 @@ final class CrudRouterTests: XCTestCase { ("testBaseCrudRegistrationWithRouteName", testBaseCrudRegistrationWithRouteName), ("testBaseCrudRegistrationWithDefaultRoute", testBaseCrudRegistrationWithDefaultRoute), ("testPublicable", testPublicable), - ] + ] } diff --git a/Tests/CrudRouterTests/TestModels/Galaxy.swift b/Tests/CrudRouterTests/TestModels/Galaxy.swift index ef7b289..fafadb1 100644 --- a/Tests/CrudRouterTests/TestModels/Galaxy.swift +++ b/Tests/CrudRouterTests/TestModels/Galaxy.swift @@ -1,24 +1,41 @@ import Vapor -import FluentSQLite +import FluentSQLiteDriver import CrudRouter -struct Galaxy: SQLiteModel { - var id: Int? - var name: String +public final class Galaxy: Model, Content { + public static let schema = "galaxies" - public init(id: Int?=nil, name: String) { + public static var migration: Migration { + return GalaxyMigration() + } + + @ID(key: "id") + public var id: Int? + + @Field(key: "name") + public var name: String + + @Children(for: \.$galaxy) + public var planets: [Planet] + + public init() { } + + public init(id: Int? = nil, name: String) { self.id = id self.name = name } } -extension Galaxy { - // this galaxy's related planets - var planets: Children { - return children(\.galaxyID) +struct GalaxyMigration: Migration { + init() {} + func prepare(on database: Database) -> EventLoopFuture { + return database.schema("galaxies") + .field("id", .int, .identifier(auto: true)) + .field("name", .string, .required) + .create() } -} -extension Galaxy: Content { } -extension Galaxy: Migration { } -extension Galaxy: Parameter { } + func revert(on database: Database) -> EventLoopFuture { + return database.schema("galaxies").delete() + } +} diff --git a/Tests/CrudRouterTests/TestModels/Planet.swift b/Tests/CrudRouterTests/TestModels/Planet.swift index bab46c4..0a9eb19 100644 --- a/Tests/CrudRouterTests/TestModels/Planet.swift +++ b/Tests/CrudRouterTests/TestModels/Planet.swift @@ -1,31 +1,40 @@ -import FluentSQLite +import FluentSQLiteDriver import Vapor -public struct Planet: SQLiteModel { +public final class Planet: Model, Content { + public static let schema = "planets" + + @ID(key: "id") public var id: Int? + + @Field(key: "name") public var name: String - public var galaxyID: Int - public init(id: Int?=nil, name: String, galaxyID: Int) { + @Parent(key: "galaxy_id") + public var galaxy: Galaxy + + @Siblings(through: PlanetTag.self, from: \.$planet, to: \.$tag) + public var tags: [Tag] + + public init() { } + + public init(id: Int? = nil, name: String, galaxyID: Galaxy.IDValue) { self.id = id self.name = name - self.galaxyID = galaxyID + self.$galaxy.id = galaxyID } } -extension Planet { - // this planet's related galaxy - var galaxy: Parent { - return parent(\.galaxyID) +public struct PlanetMigration: Migration { + public func prepare(on database: Database) -> EventLoopFuture { + return database.schema("planets") + .field("id", .int, .identifier(auto: true)) + .field("name", .string, .required) + .field("galaxy_id", .int, .required) + .create() } -} -extension Planet { - // this planet's related tags - var tags: Siblings { - return siblings() + public func revert(on database: Database) -> EventLoopFuture { + return database.schema("planets").delete() } } - -extension Planet: Content { } -extension Planet: Migration { } diff --git a/Tests/CrudRouterTests/TestModels/PlanetTag.swift b/Tests/CrudRouterTests/TestModels/PlanetTag.swift index dbbca78..bc38597 100644 --- a/Tests/CrudRouterTests/TestModels/PlanetTag.swift +++ b/Tests/CrudRouterTests/TestModels/PlanetTag.swift @@ -1,22 +1,35 @@ -import FluentSQLite +import FluentSQLiteDriver -struct PlanetTag: SQLitePivot { - typealias Left = Planet - typealias Right = Tag +final class PlanetTag: Model { + static let schema = "planet+tag" + + @ID(key: "id") + var id: Int? - static var leftIDKey: LeftIDKey = \.planetID - static var rightIDKey: RightIDKey = \.tagID + @Parent(key: "planet_id") + var planet: Planet - var id: Int? - var planetID: Int - var tagID: Int -} + @Parent(key: "tag_id") + var tag: Tag + + init() { } -extension PlanetTag: ModifiablePivot { - init(_ planet: Planet, _ tag: Tag) throws { - planetID = try planet.requireID() - tagID = try tag.requireID() + init(planetID: Int, tagID: Int) { + self.$planet.id = planetID + self.$tag.id = tagID } } -extension PlanetTag: Migration { } +struct PlanetTagMigration: Migration { + func prepare(on database: Database) -> EventLoopFuture { + return database.schema("planet+tag") + .field("id", .int, .identifier(auto: true)) + .field("planet_id", .int, .required) + .field("tag_id", .int, .required) + .create() + } + + func revert(on database: Database) -> EventLoopFuture { + return database.schema("planet+tag").delete() + } +} diff --git a/Tests/CrudRouterTests/TestModels/Tag.swift b/Tests/CrudRouterTests/TestModels/Tag.swift index cd44da0..b01826d 100644 --- a/Tests/CrudRouterTests/TestModels/Tag.swift +++ b/Tests/CrudRouterTests/TestModels/Tag.swift @@ -1,17 +1,35 @@ -import FluentSQLite +import FluentSQLiteDriver import Vapor -struct Tag: SQLiteModel { - var id: Int? - var name: String -} +public final class Tag: Model, Content { + public static let schema = "tags" + + @ID(key: "id") + public var id: Int? + + @Field(key: "name") + public var name: String + + @Siblings(through: PlanetTag.self, from: \.$tag, to: \.$planet) + public var planets: [Planet] + + public init() { } -extension Tag { - // all planets that have this tag - var planets: Siblings { - return siblings() + public init(id: Int? = nil, name: String) { + self.id = id + self.name = name } } -extension Tag: Content { } -extension Tag: Migration { } +public struct TagMigration: Migration { + public func prepare(on database: Database) -> EventLoopFuture { + return database.schema("tags") + .field("id", .int, .identifier(auto: true)) + .field("name", .string, .required) + .create() + } + + public func revert(on database: Database) -> EventLoopFuture { + return database.schema("tags").delete() + } +} From 49d6a33a03398133da3e85f4f5b2de04915813dd Mon Sep 17 00:00:00 2001 From: Simon Edelmann Date: Sat, 21 Dec 2019 02:05:11 +0100 Subject: [PATCH 07/38] Update to Vapor-beta.3 / Fluent-beta.2 --- Package.resolved | 79 +++++++++++-------- Package.swift | 6 +- .../CrudRouter/CrudSiblingsController.swift | 4 +- .../RouterMethods/ChildrenRouterMethod.swift | 4 +- .../RouterMethods/ParentRouterMethod.swift | 2 +- .../RouterMethods/RouterMethod.swift | 4 +- .../RouterMethods/SiblingRouterMethod.swift | 4 +- Tests/CrudRouterTests/CrudRouterTests.swift | 42 ++++------ 8 files changed, 70 insertions(+), 75 deletions(-) diff --git a/Package.resolved b/Package.resolved index 17ad7da..1a08d7d 100644 --- a/Package.resolved +++ b/Package.resolved @@ -6,8 +6,8 @@ "repositoryURL": "https://github.com/swift-server/async-http-client.git", "state": { "branch": null, - "revision": "51dc885a30ca704b02fa803099b0a9b5b38067b6", - "version": "1.0.0" + "revision": "48e284d1ea6d0e8baac1af1c4ad8bd298670caf6", + "version": "1.0.1" } }, { @@ -15,8 +15,8 @@ "repositoryURL": "https://github.com/vapor/async-kit.git", "state": { "branch": null, - "revision": "d9fd2be441af6d1428b62ab694848396e7072a14", - "version": "1.0.0-beta.1" + "revision": "cb8e6ee62dc6684a95132f8d46511956e78ee0f1", + "version": "1.0.0-beta.2" } }, { @@ -24,8 +24,8 @@ "repositoryURL": "https://github.com/vapor/console-kit.git", "state": { "branch": null, - "revision": "5b91c2dc93781e4b36cb4c667972670eac90e6e7", - "version": "4.0.0-beta.1" + "revision": "535874e4654b17cebb422b9e17353e85d421a118", + "version": "4.0.0-beta.2" } }, { @@ -33,8 +33,8 @@ "repositoryURL": "https://github.com/vapor/fluent.git", "state": { "branch": null, - "revision": "96f2ab8599b0507ba593da80ba2c56101932679b", - "version": "4.0.0-beta.1" + "revision": "5adacb3bd3e073fb2142b7713acce8992e3aadae", + "version": "4.0.0-beta.2" } }, { @@ -42,8 +42,8 @@ "repositoryURL": "https://github.com/vapor/fluent-kit.git", "state": { "branch": null, - "revision": "2907bfd11743909c63ccde89cb2c66b1c8d1d645", - "version": "1.0.0-beta.1" + "revision": "c232981184891cb762f041d0c70f96b254d97a78", + "version": "1.0.0-beta.2.5" } }, { @@ -51,8 +51,8 @@ "repositoryURL": "https://github.com/vapor/fluent-sqlite-driver.git", "state": { "branch": null, - "revision": "987900d8ea4b77f00eb9df5aac1b11f0cdb8ae87", - "version": "4.0.0-beta.1" + "revision": "d2bb65b333a046e8ed64053312a6d6bdeb3a3590", + "version": "4.0.0-beta.2" } }, { @@ -60,8 +60,8 @@ "repositoryURL": "https://github.com/vapor/multipart-kit.git", "state": { "branch": null, - "revision": "a941d7a1d685c83df09077f6190808ff2a7f4dce", - "version": "4.0.0-beta.1" + "revision": "b41a49b5756ac3fbf8f2b07228a7e75f9b60731a", + "version": "4.0.0-beta.2" } }, { @@ -69,8 +69,8 @@ "repositoryURL": "https://github.com/vapor/open-crypto.git", "state": { "branch": null, - "revision": "06d26edb8e28295bb7103b4f950d5ea58d634c1b", - "version": "4.0.0-alpha.2" + "revision": "90c49bc68ee6d992fa13cf84ca8fc54b97eaf4cc", + "version": "4.0.0-beta.2" } }, { @@ -78,8 +78,8 @@ "repositoryURL": "https://github.com/vapor/routing-kit.git", "state": { "branch": null, - "revision": "6c7f4b471f9662d05045d82e64e22d5572a16a82", - "version": "4.0.0-alpha.1" + "revision": "6a8a1636ad26494b03f3c72d74a420fc3a44949c", + "version": "4.0.0-beta.3" } }, { @@ -87,8 +87,8 @@ "repositoryURL": "https://github.com/vapor/sql-kit.git", "state": { "branch": null, - "revision": "2a05245bd3b1a27acc83597dca25ac52358073e5", - "version": "3.0.0-beta.1" + "revision": "d09b5527fb0c341f6993e4f5233fadbb7dee1c76", + "version": "3.0.0-beta.3" } }, { @@ -96,8 +96,8 @@ "repositoryURL": "https://github.com/vapor/sqlite-kit.git", "state": { "branch": null, - "revision": "f4058e695ecb09fff24d061826d324ec0fa68713", - "version": "4.0.0-beta.1" + "revision": "9f3a7fb91c983b943edd239351ed9cd0cb75eda1", + "version": "4.0.0-beta.3" } }, { @@ -105,8 +105,8 @@ "repositoryURL": "https://github.com/vapor/sqlite-nio.git", "state": { "branch": null, - "revision": "e3a36bc2b1c958da8d38de20b1396aefdea78478", - "version": "1.0.0-alpha.1.3" + "revision": "1bf9748cc62cd96d440a712a06cb301b66a27550", + "version": "1.0.0-beta.2.1" } }, { @@ -118,13 +118,22 @@ "version": "1.2.0" } }, + { + "package": "swift-metrics", + "repositoryURL": "https://github.com/apple/swift-metrics.git", + "state": { + "branch": null, + "revision": "3fefedaaef285830cc98ae80231140122076a7e0", + "version": "1.2.0" + } + }, { "package": "swift-nio", "repositoryURL": "https://github.com/apple/swift-nio.git", "state": { "branch": null, - "revision": "ff01888051cd7efceb1bf8319c1dd3986c4bf6fc", - "version": "2.10.1" + "revision": "f6487a11d80bfb9a0a0a752b7442847c7e3a8253", + "version": "2.12.0" } }, { @@ -141,8 +150,8 @@ "repositoryURL": "https://github.com/apple/swift-nio-http2.git", "state": { "branch": null, - "revision": "7cc32a9a41e348230a4a452b25b7308cae1f3652", - "version": "1.7.1" + "revision": "c1bfb7ce3f201e41ff60ef38fa63e67e0eb66a24", + "version": "1.9.0" } }, { @@ -150,17 +159,17 @@ "repositoryURL": "https://github.com/apple/swift-nio-ssl.git", "state": { "branch": null, - "revision": "ccf96bbe65ecc7c1558ab0dba7ffabdea5c1d31f", - "version": "2.4.4" + "revision": "b75ffaba05b2cffdb1420d558f1a90b4e6c46dcc", + "version": "2.5.0" } }, { "package": "vapor", "repositoryURL": "https://github.com/vapor/vapor.git", "state": { - "branch": "ArrayRouting", - "revision": "7df058aa79117f1cf1b83f202267e4171d4cf8f6", - "version": null + "branch": null, + "revision": "88bc369b95036fc02394f045efe0a36af3bfccdb", + "version": "4.0.0-beta.3" } }, { @@ -168,8 +177,8 @@ "repositoryURL": "https://github.com/vapor/websocket-kit.git", "state": { "branch": null, - "revision": "66c0ea58398f055b5a0d92b0d5f4c32ef0c02eeb", - "version": "2.0.0-beta.1" + "revision": "4c9da4fe7d8186243418254031288ab5ee0202e4", + "version": "2.0.0-beta.2.1" } } ] diff --git a/Package.swift b/Package.swift index 80d6624..fc0baa0 100644 --- a/Package.swift +++ b/Package.swift @@ -10,9 +10,9 @@ let package = Package( .library(name: "CrudRouter", targets: ["CrudRouter"]), ], dependencies: [ - .package(url: "https://github.com/vapor/vapor.git", .branch("ArrayRouting")), - .package(url: "https://github.com/vapor/fluent.git", from: "4.0.0-alpha.2.1"), - .package(url: "https://github.com/vapor/fluent-sqlite-driver.git", from: "4.0.0-alpha.3"), + .package(url: "https://github.com/vapor/vapor.git", from: "4.0.0-beta.3"), + .package(url: "https://github.com/vapor/fluent.git", from: "4.0.0-beta.2"), + .package(url: "https://github.com/vapor/fluent-sqlite-driver.git", from: "4.0.0-beta.2"), ], targets: [ .target(name: "CrudRouter", dependencies: ["Fluent", "FluentSQLiteDriver", "Vapor"]), diff --git a/Sources/CrudRouter/CrudSiblingsController.swift b/Sources/CrudRouter/CrudSiblingsController.swift index 59dacfa..9f449c0 100644 --- a/Sources/CrudRouter/CrudSiblingsController.swift +++ b/Sources/CrudRouter/CrudSiblingsController.swift @@ -76,8 +76,8 @@ public extension CrudSiblingsController { let parentPath = self.path let parentIdPath = self.path.appending(.parameter("\(ParentType.schema)ID")) - router.get(parentIdPath, use: self.index) - router.get(parentPath, use: self.indexAll) + router.on(.GET, parentIdPath, use: self.index) + router.on(.GET, parentPath, use: self.indexAll) router.put(parentIdPath, use: self.update) } } diff --git a/Sources/CrudRouter/RouterMethods/ChildrenRouterMethod.swift b/Sources/CrudRouter/RouterMethods/ChildrenRouterMethod.swift index b954be3..5ad3e25 100644 --- a/Sources/CrudRouter/RouterMethods/ChildrenRouterMethod.swift +++ b/Sources/CrudRouter/RouterMethods/ChildrenRouterMethod.swift @@ -15,9 +15,9 @@ public enum ChildrenRouterMethod { ) { switch self { case .read: - router.get(idPath, use: controller.index) + router.on(.GET, idPath, use: controller.index) case .readAll: - router.get(path, use: controller.indexAll) + router.on(.GET, path, use: controller.indexAll) case .create: router.post(path, use: controller.create) case .update: diff --git a/Sources/CrudRouter/RouterMethods/ParentRouterMethod.swift b/Sources/CrudRouter/RouterMethods/ParentRouterMethod.swift index bf67211..c882f6c 100644 --- a/Sources/CrudRouter/RouterMethods/ParentRouterMethod.swift +++ b/Sources/CrudRouter/RouterMethods/ParentRouterMethod.swift @@ -11,7 +11,7 @@ public enum ParentRouterMethod { ) { switch self { case .read: - router.get(path, use: controller.index) + router.on(.GET, path, use: controller.index) case .update: router.put(path, use: controller.update) } diff --git a/Sources/CrudRouter/RouterMethods/RouterMethod.swift b/Sources/CrudRouter/RouterMethods/RouterMethod.swift index ade5fd4..9c8e4ac 100644 --- a/Sources/CrudRouter/RouterMethods/RouterMethod.swift +++ b/Sources/CrudRouter/RouterMethods/RouterMethod.swift @@ -15,9 +15,9 @@ public enum RouterMethod { ) { switch self { case .read: - router.get(idPath, use: controller.index) + router.on(.GET, idPath, use: controller.index) case .readAll: - router.get(path, use: controller.indexAll) + router.on(.GET, path, use: controller.indexAll) case .create: router.post(path, use: controller.create) case .update: diff --git a/Sources/CrudRouter/RouterMethods/SiblingRouterMethod.swift b/Sources/CrudRouter/RouterMethods/SiblingRouterMethod.swift index f3b8808..0f19e2a 100644 --- a/Sources/CrudRouter/RouterMethods/SiblingRouterMethod.swift +++ b/Sources/CrudRouter/RouterMethods/SiblingRouterMethod.swift @@ -43,9 +43,9 @@ public enum ModifiableSiblingRouterMethod { { switch self { case .read: - router.get(idPath, use: controller.index) + router.on(.GET, idPath, use: controller.index) case .readAll: - router.get(path, use: controller.indexAll) + router.on(.GET, path, use: controller.indexAll) case .create: router.post(path, use: controller.create) case .update: diff --git a/Tests/CrudRouterTests/CrudRouterTests.swift b/Tests/CrudRouterTests/CrudRouterTests.swift index bde3a6d..929ac41 100644 --- a/Tests/CrudRouterTests/CrudRouterTests.swift +++ b/Tests/CrudRouterTests/CrudRouterTests.swift @@ -60,31 +60,18 @@ struct TestSeeding: Migration { } func configure(_ app: Application) throws { - app.provider(FluentProvider()) - - // Register middleware - app.register(extension: MiddlewareConfiguration.self) { middlewares, app in - // Serves files from `Public/` directory - // middlewares.use(app.make(FileMiddleware.self)) - } - - app.databases.sqlite( - configuration: .init(storage: .connection(.file(path: "db.sqlite"))), - threadPool: app.make(), - poolConfiguration: app.make(), - logger: app.make(), - on: app.make() - ) - - app.register(Migrations.self) { c in - var migrations = Migrations() - migrations.add(GalaxyMigration(), to: .sqlite) - migrations.add(PlanetMigration(), to: .sqlite) - migrations.add(PlanetTagMigration(), to: .sqlite) - migrations.add(TagMigration(), to: .sqlite) - migrations.add(TestSeeding(), to: .sqlite) - return migrations - } + // Serves files from `Public/` directory + // app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory)) + + // Configure SQLite database + app.databases.use(.sqlite(file: "db.sqlite"), as: .sqlite) + + // Configure migrations + app.migrations.add(GalaxyMigration()) + app.migrations.add(PlanetMigration()) + app.migrations.add(PlanetTagMigration()) + app.migrations.add(TagMigration()) + app.migrations.add(TestSeeding()) } func routes(_ router: RoutesBuilder) throws { @@ -112,9 +99,8 @@ extension Application { } func sendRequest(to path: String, method: HTTPMethod, headers: HTTPHeaders = .init(), body: T? = nil) throws -> Response where T: Content { - let responder = self.make(Responder.self) - let request = Request(application: self, method: method, url: URI(path: path), on: self.make()) - return try responder.respond(to: request).wait() + let request = Request(application: self, method: method, url: URI(path: path), on: self.eventLoopGroup.next()) + return try self.responder.respond(to: request).wait() } func sendRequest(to path: String, method: HTTPMethod, headers: HTTPHeaders = .init()) throws -> Response { From caa7f26365f4f7a2295268103c9f1e164a9f6e3c Mon Sep 17 00:00:00 2001 From: Simon Edelmann Date: Wed, 25 Dec 2019 13:58:16 +0100 Subject: [PATCH 08/38] Third test passing - Add manual migrate to test setup, as Vapor 4 does not automatically migrate. - Register missing routes --- Tests/CrudRouterTests/CrudRouterTests.swift | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Tests/CrudRouterTests/CrudRouterTests.swift b/Tests/CrudRouterTests/CrudRouterTests.swift index 929ac41..666b8dc 100644 --- a/Tests/CrudRouterTests/CrudRouterTests.swift +++ b/Tests/CrudRouterTests/CrudRouterTests.swift @@ -95,6 +95,11 @@ extension Application { try configure(app) try boot(app)() + + // Prepare migrator and run migrations + try app.migrator.setupIfNeeded().wait() + try app.migrator.prepareBatch().wait() + return app } @@ -166,12 +171,13 @@ final class CrudRouterTests: XCTestCase { func testPublicable() throws { do { - // let resp = try app.getResponse(to: "/galaxy", method: .GET, decodeTo: [Galaxy.PublicGalaxy].self) + app.crud(register: Galaxy.self) + let resp = try app.sendRequest(to: "/galaxy", method: .GET) let decoded = try resp.content.decode([Galaxy].self) XCTAssert(decoded.count == 1) - // XCTAssert(resp.count == 1) - // XCTAssert(resp[0].nameAndId == "Milky Way 0") + XCTAssert(decoded[0].name == "Milky Way") + XCTAssert(decoded[0].id == 1) } catch { XCTFail("Probably couldn't decode to public galaxy: \(error.localizedDescription)") } From d1799ab1b6689c47cd9dd42373078ed1cf4aa3e3 Mon Sep 17 00:00:00 2001 From: twof Date: Sat, 28 Dec 2019 19:36:21 -0800 Subject: [PATCH 09/38] stashing testing changes --- Package.swift | 2 +- .../CrudParentControllerProtocol.swift | 2 - Tests/CrudRouterTests/CrudRouterTests.swift | 44 +++++++++---------- 3 files changed, 22 insertions(+), 26 deletions(-) diff --git a/Package.swift b/Package.swift index 80d6624..0fa6364 100644 --- a/Package.swift +++ b/Package.swift @@ -16,6 +16,6 @@ let package = Package( ], targets: [ .target(name: "CrudRouter", dependencies: ["Fluent", "FluentSQLiteDriver", "Vapor"]), - .testTarget(name: "CrudRouterTests", dependencies: ["CrudRouter", "FluentSQLiteDriver"]), + .testTarget(name: "CrudRouterTests", dependencies: ["CrudRouter", "FluentSQLiteDriver", "XCTVapor"]), ] ) diff --git a/Sources/CrudRouter/ControllerProtocols/CrudParentControllerProtocol.swift b/Sources/CrudRouter/ControllerProtocols/CrudParentControllerProtocol.swift index 3681997..9f497dc 100644 --- a/Sources/CrudRouter/ControllerProtocols/CrudParentControllerProtocol.swift +++ b/Sources/CrudRouter/ControllerProtocols/CrudParentControllerProtocol.swift @@ -4,8 +4,6 @@ import Fluent public protocol CrudParentControllerProtocol { associatedtype ParentType: Model & Content where ParentType.IDValue: LosslessStringConvertible associatedtype ChildType: Model & Content where ChildType.IDValue: LosslessStringConvertible - -// var db: Database { get } var relation: KeyPath> { get } diff --git a/Tests/CrudRouterTests/CrudRouterTests.swift b/Tests/CrudRouterTests/CrudRouterTests.swift index bde3a6d..b0d78db 100644 --- a/Tests/CrudRouterTests/CrudRouterTests.swift +++ b/Tests/CrudRouterTests/CrudRouterTests.swift @@ -3,6 +3,7 @@ import XCTest import Vapor import FluentSQLiteDriver import Fluent +import XCTVapor extension PathComponent { var stringComponent: String { @@ -47,7 +48,9 @@ extension PathComponent { struct TestSeeding: Migration { func prepare(on database: Database) -> EventLoopFuture { - return TestSeeding.galaxies.create(on: database).transform(to: ()) + return TestSeeding.galaxies.map { + $0.save(on: database).transform(to: ()) + }.flatten(on: database.eventLoop) } func revert(on database: Database) -> EventLoopFuture { @@ -59,7 +62,7 @@ struct TestSeeding: Migration { static let galaxies = [Galaxy(name: "Milky Way")] } -func configure(_ app: Application) throws { +func configure(_ app: inout Application) throws { app.provider(FluentProvider()) // Register middleware @@ -69,7 +72,7 @@ func configure(_ app: Application) throws { } app.databases.sqlite( - configuration: .init(storage: .connection(.file(path: "db.sqlite"))), + configuration: .init(storage: .connection(.memory)), threadPool: app.make(), poolConfiguration: app.make(), logger: app.make(), @@ -79,12 +82,16 @@ func configure(_ app: Application) throws { app.register(Migrations.self) { c in var migrations = Migrations() migrations.add(GalaxyMigration(), to: .sqlite) + migrations.add(TestSeeding(), to: .sqlite) migrations.add(PlanetMigration(), to: .sqlite) migrations.add(PlanetTagMigration(), to: .sqlite) migrations.add(TagMigration(), to: .sqlite) - migrations.add(TestSeeding(), to: .sqlite) return migrations } + + let migrator = app.make(Migrator.self) + try migrator.setupIfNeeded().wait() + try migrator.prepareBatch().wait() } func routes(_ router: RoutesBuilder) throws { @@ -100,16 +107,14 @@ func routes(_ router: RoutesBuilder) throws { } } -func boot(_ app: Application) throws { } - extension Application { - static func testable(envArgs: [String]? = nil) throws -> Application { - let app = Application() - try configure(app) - - try boot(app)() - return app - } +// static func testable(envArgs: [String]? = nil) throws -> Application { +// let environment = Environment(name: "testing") +// var app = Application(environment: environment) +// try configure(&app) +// +// return app +// } func sendRequest(to path: String, method: HTTPMethod, headers: HTTPHeaders = .init(), body: T? = nil) throws -> Response where T: Content { let responder = self.make(Responder.self) @@ -141,15 +146,9 @@ struct EmptyContent: Content {} final class CrudRouterTests: XCTestCase { - var app: Application! - - override func setUp() { - // try! Application.reset() - app = try! Application.testable() - } - func testBaseCrudRegistrationWithRouteName() throws { - + Application. + app.crud("planets", register: Planet.self) XCTAssert(app.routes.all.isEmpty == false) @@ -164,7 +163,6 @@ final class CrudRouterTests: XCTestCase { } func testBaseCrudRegistrationWithDefaultRoute() throws { - app.crud(register: Planet.self) XCTAssert(app.routes.all.isEmpty == false) @@ -179,8 +177,8 @@ final class CrudRouterTests: XCTestCase { } func testPublicable() throws { + app.crud(register: Galaxy.self) do { - // let resp = try app.getResponse(to: "/galaxy", method: .GET, decodeTo: [Galaxy.PublicGalaxy].self) let resp = try app.sendRequest(to: "/galaxy", method: .GET) let decoded = try resp.content.decode([Galaxy].self) XCTAssert(decoded.count == 1) From 52c91d62db0938f6fcde274ae0e42808193a525d Mon Sep 17 00:00:00 2001 From: twof Date: Sat, 28 Dec 2019 19:52:33 -0800 Subject: [PATCH 10/38] add package resolved to ignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 02c0875..7fb530a 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ /.build /Packages /*.xcodeproj +Package.resolved From 5239565b2ed95a8da43aed15d9a6c88d31dd04f2 Mon Sep 17 00:00:00 2001 From: twof Date: Sat, 28 Dec 2019 19:54:09 -0800 Subject: [PATCH 11/38] remote swiftpm folder --- .gitignore | 1 + Package.resolved | 178 ----------------------------------------------- 2 files changed, 1 insertion(+), 178 deletions(-) delete mode 100644 Package.resolved diff --git a/.gitignore b/.gitignore index 7fb530a..426cef3 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ /Packages /*.xcodeproj Package.resolved +.swiftpm/ diff --git a/Package.resolved b/Package.resolved deleted file mode 100644 index 17ad7da..0000000 --- a/Package.resolved +++ /dev/null @@ -1,178 +0,0 @@ -{ - "object": { - "pins": [ - { - "package": "async-http-client", - "repositoryURL": "https://github.com/swift-server/async-http-client.git", - "state": { - "branch": null, - "revision": "51dc885a30ca704b02fa803099b0a9b5b38067b6", - "version": "1.0.0" - } - }, - { - "package": "async-kit", - "repositoryURL": "https://github.com/vapor/async-kit.git", - "state": { - "branch": null, - "revision": "d9fd2be441af6d1428b62ab694848396e7072a14", - "version": "1.0.0-beta.1" - } - }, - { - "package": "console-kit", - "repositoryURL": "https://github.com/vapor/console-kit.git", - "state": { - "branch": null, - "revision": "5b91c2dc93781e4b36cb4c667972670eac90e6e7", - "version": "4.0.0-beta.1" - } - }, - { - "package": "fluent", - "repositoryURL": "https://github.com/vapor/fluent.git", - "state": { - "branch": null, - "revision": "96f2ab8599b0507ba593da80ba2c56101932679b", - "version": "4.0.0-beta.1" - } - }, - { - "package": "fluent-kit", - "repositoryURL": "https://github.com/vapor/fluent-kit.git", - "state": { - "branch": null, - "revision": "2907bfd11743909c63ccde89cb2c66b1c8d1d645", - "version": "1.0.0-beta.1" - } - }, - { - "package": "fluent-sqlite-driver", - "repositoryURL": "https://github.com/vapor/fluent-sqlite-driver.git", - "state": { - "branch": null, - "revision": "987900d8ea4b77f00eb9df5aac1b11f0cdb8ae87", - "version": "4.0.0-beta.1" - } - }, - { - "package": "multipart-kit", - "repositoryURL": "https://github.com/vapor/multipart-kit.git", - "state": { - "branch": null, - "revision": "a941d7a1d685c83df09077f6190808ff2a7f4dce", - "version": "4.0.0-beta.1" - } - }, - { - "package": "open-crypto", - "repositoryURL": "https://github.com/vapor/open-crypto.git", - "state": { - "branch": null, - "revision": "06d26edb8e28295bb7103b4f950d5ea58d634c1b", - "version": "4.0.0-alpha.2" - } - }, - { - "package": "routing-kit", - "repositoryURL": "https://github.com/vapor/routing-kit.git", - "state": { - "branch": null, - "revision": "6c7f4b471f9662d05045d82e64e22d5572a16a82", - "version": "4.0.0-alpha.1" - } - }, - { - "package": "sql-kit", - "repositoryURL": "https://github.com/vapor/sql-kit.git", - "state": { - "branch": null, - "revision": "2a05245bd3b1a27acc83597dca25ac52358073e5", - "version": "3.0.0-beta.1" - } - }, - { - "package": "sqlite-kit", - "repositoryURL": "https://github.com/vapor/sqlite-kit.git", - "state": { - "branch": null, - "revision": "f4058e695ecb09fff24d061826d324ec0fa68713", - "version": "4.0.0-beta.1" - } - }, - { - "package": "sqlite-nio", - "repositoryURL": "https://github.com/vapor/sqlite-nio.git", - "state": { - "branch": null, - "revision": "e3a36bc2b1c958da8d38de20b1396aefdea78478", - "version": "1.0.0-alpha.1.3" - } - }, - { - "package": "swift-log", - "repositoryURL": "https://github.com/apple/swift-log.git", - "state": { - "branch": null, - "revision": "74d7b91ceebc85daf387ebb206003f78813f71aa", - "version": "1.2.0" - } - }, - { - "package": "swift-nio", - "repositoryURL": "https://github.com/apple/swift-nio.git", - "state": { - "branch": null, - "revision": "ff01888051cd7efceb1bf8319c1dd3986c4bf6fc", - "version": "2.10.1" - } - }, - { - "package": "swift-nio-extras", - "repositoryURL": "https://github.com/apple/swift-nio-extras.git", - "state": { - "branch": null, - "revision": "53808818c2015c45247cad74dc05c7a032c96a2f", - "version": "1.3.2" - } - }, - { - "package": "swift-nio-http2", - "repositoryURL": "https://github.com/apple/swift-nio-http2.git", - "state": { - "branch": null, - "revision": "7cc32a9a41e348230a4a452b25b7308cae1f3652", - "version": "1.7.1" - } - }, - { - "package": "swift-nio-ssl", - "repositoryURL": "https://github.com/apple/swift-nio-ssl.git", - "state": { - "branch": null, - "revision": "ccf96bbe65ecc7c1558ab0dba7ffabdea5c1d31f", - "version": "2.4.4" - } - }, - { - "package": "vapor", - "repositoryURL": "https://github.com/vapor/vapor.git", - "state": { - "branch": "ArrayRouting", - "revision": "7df058aa79117f1cf1b83f202267e4171d4cf8f6", - "version": null - } - }, - { - "package": "websocket-kit", - "repositoryURL": "https://github.com/vapor/websocket-kit.git", - "state": { - "branch": null, - "revision": "66c0ea58398f055b5a0d92b0d5f4c32ef0c02eeb", - "version": "2.0.0-beta.1" - } - } - ] - }, - "version": 1 -} From 3e44c8d9b86e0e1685bf85b452054eb3ee690319 Mon Sep 17 00:00:00 2001 From: twof Date: Sat, 28 Dec 2019 19:55:00 -0800 Subject: [PATCH 12/38] remove swiftpm folder --- .../contents.xcworkspacedata | 7 ----- .../IDEFindNavigatorScopes.plist | 5 ---- .../UserInterfaceState.xcuserstate | Bin 47716 -> 0 bytes .../xcschemes/xcschememanagement.plist | 27 ------------------ 4 files changed, 39 deletions(-) delete mode 100644 .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata delete mode 100644 .swiftpm/xcode/package.xcworkspace/xcuserdata/fnord.xcuserdatad/IDEFindNavigatorScopes.plist delete mode 100644 .swiftpm/xcode/package.xcworkspace/xcuserdata/fnord.xcuserdatad/UserInterfaceState.xcuserstate delete mode 100644 .swiftpm/xcode/xcuserdata/fnord.xcuserdatad/xcschemes/xcschememanagement.plist diff --git a/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 919434a..0000000 --- a/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/.swiftpm/xcode/package.xcworkspace/xcuserdata/fnord.xcuserdatad/IDEFindNavigatorScopes.plist b/.swiftpm/xcode/package.xcworkspace/xcuserdata/fnord.xcuserdatad/IDEFindNavigatorScopes.plist deleted file mode 100644 index 5dd5da8..0000000 --- a/.swiftpm/xcode/package.xcworkspace/xcuserdata/fnord.xcuserdatad/IDEFindNavigatorScopes.plist +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/.swiftpm/xcode/package.xcworkspace/xcuserdata/fnord.xcuserdatad/UserInterfaceState.xcuserstate b/.swiftpm/xcode/package.xcworkspace/xcuserdata/fnord.xcuserdatad/UserInterfaceState.xcuserstate deleted file mode 100644 index cdb32f6d83352d77993a0a1697728a7c42c9d06b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 47716 zcmeFa2Y6IP_dkAT?(W^&mTX7}oospt$)-UFosa^dlR}3Ol1*4hvSD{aNAA#+q6kV8 zQ4*RF0UJ#P0Z|YUrHY7(1qDUKf~Y9`&)l1v1OvP;@9%xS-|zonKNh3 z%$zg2w8-K1#KfFr5Q7<(F)&7kV@!-VICz5H<#sqrh6Lxj^2RveS5~mcSvok_IeM%; z&*QGopt);uEM9#xd)X)3^OLF~9bdk)6NQ}%hWEwGznP?`4iDlxL zc&0VehDl)BGKow(CYecLQkgWSH`}geNjJTM+K-5 zjX@4H7L7wis2G(X7n+Qwps8pYnvUk6x#(duA1y$SqQ}rOv>dHKThLbYB6}L9D`$V9FE7WaU0wYcg0D#8}5mF;odk4XXA0W2p8iL?8K#bJa%C>_TWi) z239b^^YDDU06&4B#LweR_yxQfZ^2vfi})q{GJXZ`z9XYhroU!g{k|Y&hGJjbK}`k!%!eW24y^wl&*=yQAb{G2^yPtiFJ-{Ae-)FyKzh*D6->~1Z7uiefckK7E`h%nd;Z48|ZoefhqBMmu*QHET@XhWVM z-(WWs7zz#J3@(G);4zdLrW+nI%rHD`c*O9G;aS5PL%E^Cu-357u->r2u+gx|@Ur1G z!|R4O3>KQ(?~ylA{?{K@#U@lWFo&YP>x`Eb5m1I~|Y$Ti{`b4|Dw zTnHD+*|=yfhD+qyas9ad+yE|v%jB}SY;GVoh#Sle=kmCG&dwEcC7hEhIEleek9$;aetYGm>^g_*)lElsheI8zT(x~ZqB zm#Md@kEySzpQ*oTfGOKF+%&>8(o|q7G>tKpn#P+RHa%jRXPR$XU|MK;)byBXk?C>M zlcrUsr%mfj>rER>TTEL`Z=2pR9W)&>y=yvbde3yk^uFn+>A2}*(^=D(rmsw2n=YGv z;91_l8+nd5@jNf^A}{eWZ{ZvA{=AhB;9K$$d{;h+@5U$dDSRrQ#&_p?@acRXK8w%h z2l9jX9DWp^%a7*A^5b~I&*W$Ev-vsvT>fGH5q=&&pMQ*B%CF|1;h*I<^3U;G_^tel z{5$+X{t*8zf0%!dKf=GyALT#bKje?`$N3ZdN&Xc7Ie(u2g8!2Lj{lXv#{b4&=YQw_ z;Qtg1f>GcEi{LHP7kq@~LJPrP2ol1CwnC!NPG~Q55V{E6gk&L2=q~gU1_;^0V8Jdp zgt5XjVY={;FhfuTBFq$K3A2SKg{8tWVY#qESSdUu>=bqhuL-XUZwR}EJ;Iy9USXfG zUwBJ6AiOQSBODRl7mf?(ginRfgwKWZ!WY8#!e!wH;fnCH@SE_bXb_E}m)Jn`6Pt)F zM1L_z3>L%1mSU7>6XV2qv8|XWb{Bh#{lzSCq?jX)5_84TVu3hToFXdXBjQ5wQE{=j zL|i5=7gvc-i)+MkalKe6J}>SS_lf((x5NYD+u}RoLGh6IuK2!qQamSqDqax35x*5L zikHQ!;$Pz5;!W|E#7MGamg-4fl11{98cNNiP$^6bm*S*&skPKz>L4Xa-K7CihLkCd zkVZ;5l0zCRjguxzQ>3ZVG-XQlJfH_~P4XXzK|59x2&D08w&_K|(%2C|>rP;MkQmP6!7 zIYw?RC(7;Q_Hv4xDyPZa^4Br7tJ zXUenW+43XuQ}QbLX?eB$jQp&;MlP4v$?N5}hu>ZJd3wdno*0B?}A;&MV4wyDJ!ProMs|wu13x z8Yl*W4=S!4i*I^L>L90UoVzqP&z|DUD=W5_cycVkDc#cA#w8`SiAsozNr-BloRAQe zl$?+fm6#gSHZ?URA+=p%s-ANbCYZ5RGEJFgOmn6MJEOOIxEjr#nK#B>Y=;E2pBZH( zDR#HVQIhKcsVT=2(>vGgNp=>O7TF=e{#2N9d!XVPS+`s_$V@alhi7tzz0~P~7V**> zXON>L-#O6@rO=*|T&~=bLddPelVTrTR+wH=;DqRz_9BqP?D^Hv8)ew@AdXgMJuzBj zL2t+QOlQWnmg&HBWI8Dp#apSrmg&NDWs(#hrLkhA4H7vx&zWzxL8tX{cx<<)WlM%v z8P1|2J@Fh%*uA3nb{5vejJj9M{`SILcbPlWS?0>K(_HitcW2TWTRGE1@hxY1Dh-uJ zCzw6}#lB2Grav=)f^jsovP@Y7(tV6#a5Rq1#7iyP+SV z3rd`>{Aj&k?&yKJ(Ei$Yaz=JahO^9LcSUPOaYrY+%JQp%lAR?UnvmVq-{tf;^PEN6 z>$>4K_e4j5r;FQ@>+GXt4HL$$G1l3Q#atUcP@*?lvimn-1);K6+}`>6uD-hM-wVT^6f z8l{nz!bm13$KumBvrm~PcXW|G#Q_*}mgKr7uVqFuqnN?N`)1aJ>1m8+@@XIBDNV{5 zyVC6TJ{YUEchek8oZj9=&cgQ5(ebHq-C~mxGZWo&XBRe)Rs#{WWxA^3? zaWQdi+Ugk;GftX8iPF5BDOFlf7&g|?t@l%JXP!E=+{aa3%vxq0v!2<&Y-FBe zDw*e*P0S0-W@Zbsm3fhQiFuiMg?W|P#%yPHC?k}K%6w(D@}lyVa!R?V{6$bbf~*9^ z5tK~OAcDpaG?k!*1g#a2COKf~VZxac`&{7nozo zDR30op~wDThezrpU~qKJ=#I{?k1qpms11YKBX*yfX>g9^r8{NX+cDY&Sd31wPp}s` zOJNkMJOh5aU|{E`?o0jG=_=kQby^fWaHm|k=rU>HT(cPq*f>Q>_&wHl#0mXbSF zLx=2ruojKO^s#%!IP=}nnU2v#j*`OKfpyF9o?3?R9LwfAmEnJ`DP7Q@P}tkrl9n98 zXk+%zbpfvej_R&kg%8y#MC4c&|L;~|f;t0K!K7|6PpQR>%&~O3Q!)QlZ)c(C693w<-6=9HSl{I@c`%cMxBm%7=1 zt!5vWV|n#X+5gXhR_k%S9W@D{3bfJcuyc1SgT}PGT4`glZcQ$!HEEq=dEtMzCjTxc zt6PIBY7G){EL;8$*8n6fmCaJBvVQsKs@Wtft&8$1>wTN%=m|QkN6o6)(tJEmX??`aJ$`v&1?G zM1E=&(sC@7cWT%FIj;UML=ly-yXqFdsapIVIhN=C?-##r>8)z%d*)as-l_EeMHW~y zX73K>5VbJ9b1a4brNU@^rVGZkf7NY?2sQJ*IhGgil==S|81~QgjG>w#X9>(Gb!!o= z)}nuorT9*@_z(KCcB8qeJPz5`4y#*))@l(laxA(3r6SM?_MbCo-E7;b*=FTfw%#e* z|GEDAmz)q(HdOTnOWnGa=&V*^V2-8WPL=o%y0718n8>ISQ4>sE)xYcJovh|PILG4r zFXdeuCZOx8T3^%-ty_p5Y9WUHuZ#HqhG3lKD5gq-;?g@VR!|?cCd2=4*QAz!tP59q zpQu`U)E^B6-8mY7GEgSULfL2_8iWR;A<9T4M;WE$Dx;M=C10^C1(j$R8m|83piw9n zp7NAJWt{p~q!cS|c=Ax?ToX+cqIz0QL2PrYx@cAN-K_4*G0us793_t8+|nFNz#U)H zq>`O*$^i5crUU{+_7b{d;ta^Zp`^Uv~ii_Wsr8+Kd zP+a_l{-z&gnX+8{_*-O4)2dFx25eGsfjPj%N?-w40Os;kU0iUV>m>ECw~7mF0bD$N zcM?5*LmU9Oz>RQY+ypnp&2V$v0{dgD@{IDVvPLOaDwMU#I%U1Gp%Mq`xWHi)7dS%M zSdEKKDlWD>z{P#z0w(}2a9icMDqP_9fD7CKccdSsQh8qe_*;yY#(JkE<1`Hysep?Y zVDRG}z%Mq}#f2$IC+UOxYq;nKxY&Ak5kBW{6?9^b^JU1 zC|@XFsvm!gWT~DRZn0R!1&aU|UjqkV4Zt)m)WwBySXDo>0xPSyU?srCw|6JeoY3ENb;qbFG8h+aXm+j0Z zY4GR@@VHLcdtG=0a+Vc(ho`f>HGuR2fZVt{ksd#T9Zms~$!4+H>_B!9JD459 z4rPZae=9eYTLdu#A%ZYLEI|f>jFs$&DwyQ5qhVb&oV63gX*eN>CrE%r);Iz?N)OZ_ z_q%>&$5W`d2yFCe`VbZbk#(?4*+W0DOqs2I{DZps-j0;%tfC=hhEhV1NPB&zQc93i z7b{*p^qzm1ov$Hfo>E4T`5r_%#pCQ#6f29_CF~RIlk8G<8M~Za!LB68iy#X@-UQVr z$cG?bf*KIyM^M8`c9lNcuxsdS!>%Q$Q59P$BB&{yZwOLPwmndu-Iun5-Ad8&B0-IH zw7fz&%d6;b`XQ)^vRwTF-oooW_&U2s1Ilg+lx84TuzQsj1U0VYs*<lp7Q%HrnSmDNv&80)_vnipankIF+Lqj1(xb_aM?KWP=|7#b7qnGk6&+25&=s zgO9=20L&wvpw-)Q+I`1a+u1G}M7&Xhwlz@F%Ea4Ny93KuLN4l=}vX zp%nnd5J^y{DxesmRiL2V_&VAR^6M=1C1lo*^Sm!N(G z^(SZmG_b$L*J8Ca(7SPhVX}snNfa%aAa59^QXwVl4rtl06DfvS8d_#jv<$olkxnts z@B~H6e8U36Lc^nm#|(=Mj~f;nmJl?UpdkbeC1@By0EZC-jU*_Cpiz~EC#%u2Qbo(t z1Z4ASwB%`MDR_XE`$x-j6fKnmjn>if0%a+nM^klaRF9_W()8YY#jsri$u^I zBn9ELm>?*GlOQO-_)5dEYLJ{(LGm#{t{RX`&_FWz0Z8s2Bo`=1z99&BhPp#*xI|gX zcZTojhae9@(696xcnWK2t@q;3hHDyFex+cUNc;Ud1v#f~FBPouG#Znn9345FuzLL9+;&U1_xFU@kY)yrfd38bJ*Fq<1XY8ng#Mps?WWha%bc$}q-V`Ls#uQ_!G0oWB*u$7^>}dpH z|k5Ui@7B4{;T9Y6B` zCif4MTndxX1TEKLVh5NQ3(#NmL(mFknfe7BB`m0G#BKT%V~KIRhM7`|nN`3ejBd(U zp00}-uOLe`Y9QSittV&$K^qBrj-bj)3EDx>PJ(t3^jf9ya5Y9gP%(0x zpx0|KvRlK*-Uk@De~f%aG4eSaHN4@fD>Z1qd2M#&}PWep|YQZ)WGBe4JOAQz~sJR!Zih$aLovMzX~Rt zKfr{u8Z+sKprbIgYu_*diB)~bg>exoRJfKDDj!0xbCDD($Ld1Gs2^40V!75TRJeEw zl@s?M(z9vLr2tg84qQjB6W5vR!gb}6xNclBL8k~hO%Uj5&kzL5o3jLcLeM#aKCR?Z zHK=guTu-hSK&1~spXss+s1Uy-=mM2h9&8;u?;k8fC|E!-@wpC`5fm&V*eQlbc1*C4#;q=zD@L6Z8W?R|xu%psNJ^RLRY$W-;?s76an2 zzO4*P>}wi}`TYTlxqsBGq^N;4g1)oNt){5y#XUoF zztLWQo`U6iU9jL&I?-0{WeqAXQK(j`Y*L|u#RpKif2e#!p>l>`qt0MH zp-?%8{-PfOhg_GbUjP)W_uyCDHyThb08lVb`}`sR1) zC{RoS!G1MBX`}(A=>wqLH&9Fs04OFuf*V!=#nc#pf~UjL$LU-zP}Vw>-E^YYg6%C^2<2CBuD^;ifc#!>S|}96@j^ zsLVKmZ4Y2_|1im*Fv%o1T!+a(fC<`T8caX10>Uk|FMmt3pst#ilEy0Ndx2rTwuVySWRmL)h;Py3)rIW^3x;{Y6{Uc^EMa&X{JLrg6 zsxlVSay;F%9GD6mxL3dYExJuM(`wTi4Jgl2pme5vUO|Dv!I7fGadk~yXa8H7JRhnL} z2FhL)C}1_OcMVYbX+X(%0F?U&$_ErE9}?V02g(TzD5n4@rzl4HYG3}A=6Vl)V){%2 z%BK`4{b`?{r$8A{7bs|nPISR^Q3J}i6ex5%`ffxz#TC=<6evHMu9|)_{cQTh^sDKb z={M7Lg0l%8Nbn$n2NOJm;GqN$BX~H$BPvaQR0HKE1qu(s?#LRTjM9LT_W&sO4HVuC zK;i2VxEP~t8uH!%6w`9#F)inMQGVipuiC;2--vIj;)HKPagqyN&Nrtx8C@4A`0uJ- z=L7i=6(@W!#R=V!z8jHF(TZ;aIN>AtDBi|L^D%rZAIHb@FxwOmTuAU3f}v<*2?pt- zh~Q#^ODg#U4JUkiz60M8aMGDzr-l=P-2{6Go=ByYNe_^6|48Xckphq?)sfN{kiz#v zx%>b)v4Y1d(7-MjiT;)tOKecr;M>$I{9t~VhMJ)iHD$0D$d7;xM7SSRXCY+XXo=8K zkjEEjxUo~*fc?e0lIavh`~-@dV!niT@}>NE-o?9l4-eEJv`iy-I>8SSJcD3`U_$Ur zf@f9o6LtQ=PgD5|yq;adU*>B3W!?kS+&^j-P}D3Wc#e*mMRX#%ZYZW7dSJ4j_NA|d z_+|V`4Ja!pP#&h7VijdCkJJT->uwP4kaf(J~Cl(`_?sz5E>wA^Ax(c~OUgW3tGmMW?3ggS))V?B8;uo^O~ zI5*Gfo)jBni*1vwKId2h^Cx)n)xt&>A(1Ye=iol5d12n(xu9>MOAH% z3s%NU2oM6P5$Aj`?b^@V`{ZcdAqm2)!N{5Zb4Mh+kmYhZJ>Z4J5+un!zAgN%5v`&- zbnMhQq)T$I-hKM^%N{ax*vPzs(g{-@nmMP+(z&^wm&Ln2SW&L(2F=7WSVd0q0Mi)_ zgTxIM&I1*8U|KF%&CNg2z^`7zCe80)6uqKRW5u8tE1EU~6Xng61_S$MrhySZhzZt% zoUT&=fkDCbLPEpBTf&=6#}qqcT98(hTj-`XnC)N=+EEI4ftQr!1$7P8y1@Gp3z$1M zDNTpkGSm8IMs)*I9mnfMMu92OCfVsUhZ@>iWp1Jfw5Ty{+OLa^i*K!4OJA4JHnAPG zk4`OXD~(mt&@>HNo@(kGixmo;MZ?qVxs)|!+C9{~tNTt-veUEOV8o>#*m7|>@@uEk zNKd6IN;UOyzj|G}l_!Bgaz9$glEb)Si!0j z3_%)*YPuLw$hTKPt0F(^RN)xM*m~oNic6eOyJ{n2)IxYh_ikzsU@wze;)m%4VwAZ( z&SDjiYGC(RYYhCw_OmpPv9(KV)2?-5d|c}|8fc4&OK8_7rcFZIb_t9HDb1CD6XRX= z+@7+mzM1JMkVATMvc&{9eyztR)|)iBW>m{P7)I^^md%|Q_((C>P=~Tu2M-x)H5~=7 zLjrglie;GGJa=iI%(N^D^YqkYs~aL|kk`L|I}Kin;NOX;9{sIWJ@nuF-#f3=bWHQ5Ob*5BIecHBGXyk<0cHng(&IZk2{qLSHt7F~!f%dHy~{^K7(1tN;DW2-Pv~{!lAF8_ln}?W|R3+!OcFVAir2q*)N4BcG<9xFgvvN}rc*2DiHS*<>p6gO9|p!=HgSsJh>J3j@Jj1H%mR|5n32Ji~kS`?fDI76F6E?I^U;;N*-fYhIaa z0)2)tLBEWQ2<{(z!JSwOCJ4NIL@{yTNUQ_Xl}TlKf>Vera3MC5$z#Sa#o$0}A{hQB zVCa7V81`SvJOwuR*MZIbEnsDT2lEEAk9miAkNJ=}1&+kdGZ(;t*j45?<_1E@1TMpT zP-EneLcmE_97;r;!7W&CFrhXCETa{I8?Z8P05%I8e=Pw^Smme^9DeOW`_Q}S7+661 z0$l>PUVnlsFB$uSy_-;Q;gyJ!z+qPw*f=Q!CtXwUZ2TD5x2OP@T-)(J{2mxII1i4u zuCWX&!3FjJaIBTcCbRwEj&cF(Vjp7X!!6VbaE|pFdk`)fo(C6Le;7EpMP@Zb89IP_ zt88#H{l3(Gh-`bdpM^%*jQ+sXq;g6Vf~kmL_4pEQ0bEahjyy6|~^4>&IRmcIdxi9)~$Q9mJHm@F(3)`3Hz4}=TC z4bfW+6FY(HpRpnlSBP7{mCrfx7fF%=rFP(o#~~@;V&_Hako1N0hg@Hdkdx)%;Qr<@ zd852n{#gD6T-1b`yPAiZJ?6*EmFBn1pP7HJ=TpyCFTGxVy&3gZ)!Sb0c)crLf>(%F zlGjMDDPGIGUh(?C>#{|#gj$j`*_EC_wz3HUg-V2_q*N~>znF_ z)=#aUUw?M}we{bs|CJBx6XcWZljk$bXPwX6J{NpB-!R{F-?6?6d^h_Z^}X7leuLHx z1~!<~U{!+`_L#*A`=2JlSGTi%b4K{+<0D{!jY9>HnRz zfi=lmWL;rBV7(g9ETBg~SwMNf(SRF)VS(9!GXu8gb9ibP)8il2YO$pl+b}rl;-X(l|ct!ZhmR!rk zmg8EkZuwyZj%X7xCSp~@2d!|cgjSALt6Lq5G)A_IEQzd${3uF}N{X5gwJGWgo1d+> zZKiF9?MifT^w8+V(Fdb%#=4yqT!-g7T z*{M&ZewG%PR+zRa?P~Wn-KTcn+r!wSSC7R#K1y$%o|j&kezj-Yp3{3C=q2~c?DbTy zFMCJycJ+R(5AM^m&l7#l^$qD;(sxHcreAu$C;ENbKdk@w{;v<<2J|1WYQQ%cu^H1c z4rcmf=43vf`CC@EtVLO$WQS*$W$z#8HE{UA%7NDhr3_j!==|X5!P5u7H>AlB$B>;v zO+yC`-7xgpu+(8ohg}%nX87FUr$>a1m^9+ukxfPxjoh8%m6MzE$|&QgL8G1@^;d4c z+=|?5qtiz}J^E^1a^CX1@AJFnKbe2Y-pRhi{%t|0f+YnP3p*7)QFv)gmodx6Ty`Wo zo^t#&w#V3K$6g=TZ`{Ulw~7W8Z7t@DM-}fXsb4a#__*I9#Ry*9h~ zY-RRWbGpyjG`HSd*W8aD?(p!lj~E_tJaTki{JfR(ZqCo2e|SO6f@KT-T9~)+@T0Mh zu6Xp;V+D^LU6iosna8=uOCCSHxXa?_mUu6jvgE5L`aH4yN$ZpIpZs-c&eC_6wO;n@ za&dXt^7AWtuh_XVc;(|O|9Wc7Q>RvSTebD+W>3$1`ughp)yJRd`plMRn?JkY*+17f z)|@HtUcS8|v|`y>ZtaA%->e(B?%?{w^_3f%Y*?`2?~NrJ&p(&(+=0rrm6gvodw$U- zys2!{r5A?3@Zsi^%{#Y5Z7JXCw{^jb%!_3&e)rO-mrlRj>*f8gB)+oc)sR=8-sZDy z{&uu|^7gAc#_Tx1bI8tPyL#^0|5}IFw!Ln9ed8MeZ>-wgVE3Xu;-1-iZoN6>&1-v0 z_g>ytxbLg|Ir~3(Yw%kq4-7bP^zB}6A9^S4owp7q9endpr$cYN+y32MhZ7I)crW3- z?MK=i+4g>$_qQEQIJ*6Vwjb>Lu-%8R9qV{(&+#tD_nk;N@y^Nglkc7Cd+ONf?9*pH z8vfDwGx=vOeq8kNPiH5by#Y3LjOXTmYWZ~8XU#sV_`K!kThAw)-~C0(7e~I#{PL5p z^1k~1YxmcGU6}oi`I}|m`hQz_G4A3Um(nhM_}#GYE_`45{f*0WfAId{=_}z^Uj4D_ zk4LT!zIx#&*H5>8UieGnUpD^Q`q%x}`d|C(x8mP!T%Z4Y$cwz zW7cea4PG&euE6F#tS*JBSEKpb(!b&nSc1V4>7U^Y4{pw=KeCppEI2E@1TIQ%fMZf`a7G${f>9_6 zM-kwHv^|(*9Re0uM}vIf0C%Is$O-2;CZI{+Zgd)$0euo2iJk>Vp?`uoznhrBCM<${ z0p|Fy4>${L2(Cf<;Q=@k?5X8}L(s`^@o)|}|6B>Sv(|&7&kw+8(s$t0^A~XBDYDJM zWoH|3*x7~c3ARv1fRoM{?8D%Qvl5(eZf0Kv2b^!R`@jL`+w5WXxTOW$Zw;};TiRMW z!z|k0l4%)bv0I8QrIs?we9NO$ECZoUm*(bazx8hmXal#YCP;-~A>v zP}07=i*N3`0-I)UXf4%MNYWa`ULka42IpA3vz+<4ldZWPYivyW)@`i)`)CnUgw%g3 zDufgedk0&-Q?;T(5B^d)c;#bj6re(Hp)d8e1%c&4KZ5sD-(X;sR>-J{ss#+>FM(cn zkM;u&?>-N5^i06`JZ1&B(|Uw?To}y|aDWvJ&aXNt!@x~e2L<+@-UdHeuv<{=rcD^5 zfJ-)l-}(QgVRqGsvM^3?Gq#OFkx(p@2u`6?7%#X8K1A@l1Ro~&J%W!A{64`)Hwqr1 z3=J113dM9^f#44a{tz}3*v}v?-BwT&d zT0&}Ed{ja#xKeAKkU;&ZwWjV8(_&k9Ym*k6n3|+%sx?8SmP_BERW-57*!!srm z7KTN3c#~rZubq=dkprqBFoNxLQ5)E*`DDsT!FIBad;49AW@%hqk1VINXmqYCUC&ih z;@2)w?N?K$#>FH?TBBm7M_Q+XHZm^K3O?edPuIu)906{NZ4l-P4-1b7^Mv`r0)meb ze4OAD1fL}M6v3xA2yi_NmKKl0_Y#6XqAlE;u!HFTpD2y!PLXDrItko6g5C^%XM&a8 zzPS?|g_;^Zl9v9CjhMMvIkR;x1Jclr)xJ zr&U5FW7{k|Evy!v5uO#+2<1YBuohNE>xB)%Mgg`Q;Mgi`rJN=B6N1kX3~GBo%;yB3 zC-@73za;o8g1_F3a)nL83&Lh$3q_ak68wHecvaYjhSS~M3j}{d@XrM7v#A?caO|Am z-v}!awk<^-IO*$VFL1i-eR3x`ia|@MYKL47x3h%qB;`>5h*iMRT2BqNSgU@3KTHSs zvs7CJc0cImP4C>v;OYzblj@kV&`f*&%d1tG`ITes{U4q zp)Y-)NL8vGwIEt4!tdFD4tI4KKzOKXoZj{VPnI)->gjYi92DTPTe$%IMvr2+%B-L(kU=Q+L4#{#Bdnei42JKcCeE6~Z-UaP0uSoYw_7z_bQjUY-(e2!FxE z_P20TxFs?o5;1oUa?qVH=-aO&Ynq3aM2%6L>nN!S27i=lzWoYx+M0KS0AHoOnc}2r+NyaDh?!;TMz1=*iX2n6Q8bCXD2SpciLwX>!~Y=o zFM@9pmLV)Atbwo`%&ekC^k%%o`e?W4JG7=8*=h+1{*%f=H-%o8+zPj}L*n$+Q{5F1lrc;3I1EnEyvP=?#Am5B5Ecu-BXNC zW!0!|RZ#`so2tr@K~;rAP%h*MRwRL`-GK2~Hy5{_O@tPeRioD4vaCufnmb36GD3er zT7xua0@c*5HEOttp=R{YP1mzxJF&gkLF_1Yf^O(6b`iUZNk}%W5|h!R=ut66OlJCt z$zs|t-6}tLXN8@AH|*TgZ8O+zDJw4NTUI=pZe7&}7qE2#z4OMvb}=~jb!AXT1u!-M zlyq~Rs&-RLz`+2JK#)cYTPt=gxgk`XgnWv@(L#Mazna%D;{K7aGf(Me2_USXFg~}c z{%L6GA*RE=8nA~f*lf^IuaB6uVo$Lbh05I`K=%OYKgqO|`-tG8he{&14UZa7-p-&a z#C|lRe>=W5ng)O@D`pT@u3?vAwm1a%mpD)yBn~F5nXvT;>$O%KYWPkZPFM>yy}^2e zUj;f?d%^JOWuJW8B<7>>#U9|u*-4Krz_vb26JC1s9PPE1e;&<0pRo0*{45p%PR581 z5Q{*YP2KO@mJ&^Xus(!MI3tb|i^O8F1mZizQgOWK65XOlEE6Y)^_c}CoHb~yW9qiX zqg%w_%z}D-q0$>&-q_sgDVf_F6N(F{@YM4A3p=YNs!&wLrT!V;`?j|cMVt6U!j!ESXNt4L z+2R~=E@Ax$+la7D2-}RXEovCyJe8-g4gV9K@|ZT%8~+0V2 zK%?BMTkWd(QQRbMgHuxC3*u(bz;6{_6kifw7GDuxC2TNZLkJs6*f7F|6SgH`BM94y zu#uJGcI^P3ae(+bwc#P|A#7CD!A-E?!A5H~JlI$&06eg8d0!ST#lvs}PkfKCwyGm| z;!!w)cPASjdJmow&!|W6#E;+z9vcIv>F#E@L#Oym{6al{C!UA%cWm4}C~_?K=cql$ z@^4Mz8kjGM-@!x(tiKKetPsDaXujh+jrIre3JB}}q*d8)h4>>SzVl1H34RiPV{Gfh zpT%FqU&U*LO(bkP!nP-Dhjrq0@poV%e-gGMVUvK5usxMVgQ?uEE&gc2)slJjVqmBy z-_teH5LLRTk*ji(kOX!L)=5}mC4*!nY$w8YCTthNcBM{na+y*l1cr|rj!V>|vVT+c zfFST-5Y6oq>Eg$okvmb9e_@Vka{F7=*OEOyGY?#&!r`3TUaI6ySFKY(K<{^36g71^ zMd)SqmV96}BGo5sw{i(Mb@Dw{BT^$+jYy3p;KC_{h3g{vxjL!26mW;-ffUGuNWfQ8 zVKu_0DX!{P4eC1U#oEgYsig$h^Yl5@;i}rPW9v zY_D>#Yv)#L0YV~mpO)q*0%3}Fm0szNQse4Qi6#isUKncQT;=I$^)s!L7SpMSkP8M~}aQY!6wTS`Tei?OYjilq|CDV0j& z2@68f5W)^6EEH$>ddV$$q%vuOG?B0)2s@py&lC0)!otZuz@X}3)J=_$e%mP=0)c8tEn*O*h1S|`1+*GS+T ztU@Z6DhTT!?AQuvowS~?(10ZrCQULYIto12Dwo5c&?$ul44hv9-C1pcicxj0((}@0 zn%E}k1;Q2)wwOL{QXh+yM*Xu@FO7P}uSz>;+-)?16XH}zJLzL7JSvT<*LPJ3@0Rvz z3BRePHl8NDUww2bjqXs8cO=l(&`5{WNbcJT04v6n z(c|8f5pAzqGN>&cBJ7L`8OtnT6~eBi`8FP3W_L{ncc8GC0HzFph9iO0qo!*_fy$;q z2?OCJRmsV`Y=)^+7GzPDWSOvpurmn@ll1I$!e-e^w#eR8>B-I^ER5`jX%>D}ia{8R z>MWT&#_3eYO0%L|w9(j-H;dze}+SmLuT0S8!XZ;_ z<*kVr-Euv7V<_9AD`S;9s{nqTmYc{;C3)Z~5Xv${ zO)|^wDjrfy6G_f>+lRs!ubRt;s;||e5cW~RJ|Ykn_|QVaR-S|;L*+0zTyDwqLyyX> zpeXi9pli)@cv2kh(xTj|bM%9u<%;R#o<$D{5_W+CoiOh>kVnZjIeG|eIcW<+!p_g` z@0j59sM3`jE5`vRsoq!x#tr3N3&HWs;OgVhTGTc&_>89_QFTLTjQ|-}$Zd5&tnSNy z5Y@C^33$TQJG+D21tv7Pqufabd1(=0A1CbMwQ^TEiGjhqgs@M5$mGiMKn;hhEf?jc zVh{*9%_r*GeDziX?Zha6K|bv}`o5VOF$%hZLP5Rr;4CZ2*XY$%yPOV!xZIPlPgdXj zm;1;AXnFd|{p9|HT}s$xgk8Q?&X6Z%GkM}~O_IO}Ri##L5iA6lJ^J2SVaNGnLCGH_G-0V@b zeWf;O63Huz#UZVl8QKg_FRbAUxEh}>@t)uBg5L{}->hUCLF8G4R;vWBWT@?6j&)}D3~UTRL`qajHav$S`sSXuY_LCp_^uRh}HuRspq=##z2gqJIAQj3Tx?5 z3E>XusI0+Wl~#&&ieY^YSM71$nc)McyjED8D4XEQ5py)7W;x?jY<=!tNsMYlMBBux}7{H(~b> z7G|=&gx$AU-X?FCcgQVbuY zne!~|A#bQnwP*wa4juCJzK|yC{G)l)1zlBpXdOzIUjImha#09tSgV=n0-~tjXgZ$Z z4ZW$9>T)?6! zrj@oTAfpWKLQoxG{n{C;BBM%IOKR16`Ivls4PoEX#@qKtax zciP?75~l}FoR`3kZn4$lw3fP@9%r7jNTZ@_kTAVjZ<*OUG^^QQHkvuYo*?W=!hS@D z(cN3jEJBN!CBmM%ON*K7nZ3+jT8r^nOg}!$T%WL~Ev?iR^X9haSV{^22>*p9tL=f@ z9bM)3x^|%6ij9+sM>`73oMmonncHr47Ffez1cTTarsB}-2lzENB<#m3e!XgXMa3`h z$eN{*x zgQ|v*05{;wp+aCkZ$rU9Z@z^N4u&gKZjfSz1k! zM0=qf2(i0{!aF!nLeB(1v~)M*1ILoG)6-KxBut0BZU^0$f;&kLm)iqGrEvSe?t(p8 zb)xDEf(5*;PE}Kvx#6{X460AAdmJpD^gOF)!1NR?+wQJXH6JKJdj2Fh<)oEO+vrk| z!YUjuC}60IQ4EU7;-wfBZwuocnzz+bA8yq}w1P`P%47Uf$n0)wu4C7#m;K{|tnAPgbSmAC$KK%ATY|si&7F}Iity29p*;p5y zYPi9P=$P2J)(P~a*f{%SH?)kcs`m$KR};(Q6~4TUA}f{?iS1(BcjySy5;QQ}{k4~9 z4W6Byo}!BdaGtUDD|*dO5$I(=SPiDZ>7ulRxY#H-R+JPK(>f_8Dkd=|CMr2CDIqzo zO(INaDZ{4Hrqa&q9PgUcE%{FO>Y%G?XHwCPqD7lDPo#B^?UCLS)_{6MfDO;O_DqzJM>YJR8AAvNkxy*^A9%$FLLGDR6>wAxm%1 zz`yFVnDk~^t$*w@a26A)(wGTkBH_|qd!`E6@Xl`%rXzmP`O1ha-%xUHx=AP!>=Dz0s<_vR|d7ybPTs9eQ9%)`}e%4%WUTa=& z-Ut^@Hkmh@x0+uzZ!_;Szi!@R-e*2wK4?B{e&76|`Gonj`D61r^XKL-%@@oU&EK1^ zn6H}uHs7j;>aq3g^<4El^(NGt>FU$X z=O>?=zKk#SHTZ7x-S2zA_Z{Cu4SF=lZZN39kOspV>}v2%gF_7tH#p*F@oVnq?-$?~ zFOW^`bR`Y?)2R9!Imsmb*e!Tg~=BHcqXp!AwP>UfghWXd` z_xBI*5AqN3Kj(kR|1bZWR>q301}kUft)f-7*0Tm!gRCLeFl$R|E4Ww}ZH=|YTiaMO ztmCaqtvjrrT5kme1#}C@4Hz3x9N-KXA220gR=}KqhXdvXEC_fsU{S#0fF%K|0-g)l z9I!RurGV`LI|E(|cq8CYz()b+1HK6OD&RuEm4IIYt_9*iQ=kwi1zG~@2l@v31vU$8 z6_^;3nQ)nAP~ecjVSyt8O9Nejp1=u#lLDs%P78b}P=RYUvjfWm_Xd6) z#05nL^$l_bEe?7yXjjl1L3@Js2E7|}Jm_T5>7X+~XM@fKeHL^+=!c-6gMJPAE$Bwj z-$A#6QLrW0C)gSs5*!xXGT0Uz6C4-ZIygDFe{f!~J-9H~5j-xqIM^9HKG+>x7W_!? z{NRPbj|D#-yd?O^;AO!pf}aY0I(S#`nc%-dT7;y86okwVsSG(3axCOz$mx(XAzy@i z7jik|O32lapF@5P`7Px4P-CbVDu>n!^$Bed+Ay?nXn1I=(8SPAp7g@1N$9N5IdC~>Ug(0*M?=?yR)nq#-4Oa*=<}g3gl-9aG4$ooS3{45 zUJdgNO9&et_E1 z9u*!P9vj{@JUP67cy{=p@FC$N!$*aW4$lvFhb!Svgf9(W9=Z|_#5GS!uN*n4?ht8PWYkl!{JB5FSX=bMz-wR($jKT%hy_d-17Gb6k&+qBKQdJ zh$a!uB3eXPBLX9WBSIs>;rdehh>j7RBa$OhBf3YVM+}Y_1{a!&Bb*WABPK*likK2H zEn-2$iik}SnlM$yQ&P1GzI2Z9*#QBIXBff5h zT3K7Av?^$|u+^4U$6NgrX^Ctc*(|a}q&2c-WL#wH$b`tm$o7#PBU2+YA_qkdi5wO= zDspsWeq=#pS>&Y1Ig#@t7e+o7`9$Q>$mNkMBcF@h7I`T0aO9E5qmds*9*;a3c{=h; zM)U2q=sQ03NwAHgk*%EB+Y#nT! zY-zTBwgI+GTefYGZHR4{ZG>&C&1oBNbK54_rr4(09>? z;%>qvv?lRU@iFo7@d@$m;ycE7iSHJl8s8&+Sp3NNQSqbW^WzKR9r5Ghi{nebKFaOXM9(DKl~v45PT|rBz`nL4L=S~ zz_alAcsX8$FTm^YGx2lq^YKOa#duc-g7e`Kd<7oEhw)K-3}20}#qYvj!ha+TBd`cF z2|mIm!U@7T!UaMr;WFV4;R)ei!ZX4P!fV1?!aHI&Vh>_3Vjto_;vixQF_k!hIEk1+ zq!AfJkeE&65(UH@Vga#`Xd^m^ZelqRA)>?(F-oi=)({(rjl?G6cH$1=F5+I|e&Rvm zVd7EZo%BxWW765_h3RN|Q~LSz=jrd$Kc#;qbtd&74Im|x29r`qsia|~38X2cX(Swp zL;^@u5}hO@<&p|W2GT6jTv8Ef5osxDImtx|k=B#yNcE%!QX^>_X$NU{2k1LUY9U=D zT_Rl}T_fEf-6q{7wUHi@9%m$Gq-A7h%+CmAY|FTm@fJu1h6AI3F~C@03Xl!}02N>W zEFcre0MA=E%P1#2|L^(n^PB}@r zL}{b^NBKbcOzlKXpe9ngQ+rYSQ2SHUsN<;Lp{qW{C_$C$y8GKv|MjMa>_jP;C-j4g~^j6IBfj02267>5~W8Gkcc z8CMuL8Mhht821^k8UHc6GP^T-GLx7Cn90l`%%RL5m;`1vlgAV?bD0vRjHzU5m|CWR zS;8!3u3%c3cBYf*VfvT|6J-XO4a`58&sqIh=`01y%38(R$2!J3$vVSoX0@_zvF@_k zSPxl`S^u!wS#LYo=C@!Eus7Hr90(2uQ^4`ykKiOQ1Ehfr5CpS9E+_zVzyh!kw1E!L z4VHrlh=L(73RZzNU<23)Hi6s09pEl-FSs8(2p$HHf_E}IWsb>YXBK9nnN6AJGoNR^ z&-|46mED=$gFS$q%pS~6VW+Z(u_v&nu&1$cY!Vw_Q`vO3kS$_s*n0L%_8j&C_Cod& z_A<7U9b~U#Z)E?%{*Aqb-NgQ#y^Fnyxd*syyu=Plo6VcUTg+R=E8&@V4j#foc_Chu zSH-L5)$-Q!w(@rHcJcP|{@@+v9pjzgweqg;uJIo6+Ii1-FM0p*-t#{3zVLhVhw>-! zr}Ag;349Wt%%}00{7gQ?SMoJ{n6Kj-__O$P`SbY;_>1^U_!xgPzlHx=FjPPmzyh0K zwV+N=FK7^K5$qBi5*!tr5S$h?3(gA83vLST3GNFX3EBnE1TO`zg$cq$;Q-+f;ZWf) z;b`F);W*(B!gOJ#P$pCgHA1b>Ae=3nCtM(0BwQ-=2z^3CSRurOAz@S)6UK$L!d1dO z!YjhhIU{m1b7totIrTZGa$0gO=3LIXnsYDb-<)STFLGYzyv=!+^Fj2DsFx^7)K4@> zG(?mt8YY@3nj#{L7$TO4E#iv!9l$tOq!ks3%0w=aSL7E}h=QVsC?={FtrTq$HHo&1 zc8GR~_K5b24vG$mj*5S?oGczJP7$Yy zhl@vwCyA$vablvFET)PXVwN~pED`I(v&D18h2q8HrQ%|-QS22*#dRI1xk210-X`85 z-YwoIJ}5pcz9_ydzAC;hzA3&VZWBKcKN9~VZqMtH_d^~(Z(&|#-p;&hc^@PxlF^d1 z4hTI#GDAX^P$dirOTv~w5{^VHfh98~vnBH+3nhys%Ou4Tx1?NBDXEdHl&q1gmu!^$ zBKcLaM{+`PRdPdeTXIkGK=N4fujHBJrQ}WixA{Hudw0<4e)$9P2jvgRADTZbe?&ex zUzu;qUz>j-|FN{UbfT0i&67%{3aLsuQ(7clBwZ?9E;UL^r7NU9DJl(0Bht9EM!HJ6 zM!H$LReC^rM0!kmQrav%D{YbfExjjwE=!Ol%DT&X$@<9p%aUb7WT~>@vS~7$j37&w zWyr`fnv5Z1$=EVTHdE%3ZIYdqJ(u^F6XX*4GPy-=mpkQdIV!J`SIbw*SIgJR*U2}? zo8&v?yXE`jhvY}($K@yGm*iLF59R;LpUPjz-^$;~KgvHVdMQ#A6BSbw(-n9{x&ly8 z6$}Na$Wka2YDIxUtI#WED&{EWDGC(}6^j+9qCs(9@k*JZ1e66zt8$fcqw*K!Z_3Te zoytFyN0i5uCzWTE&C09FTgp4iHsxdGKgy@d=gP0DE~-ANfvQ2O6xDFmNY(eMG!;(8 zQ01wlDuqg|f>nCeEY)09p=zPZp>nIdDxV5bp{kH7s*0(qRkf;Js!OVm>S1b@dZyZ^ z-lRUEKBvB*ZdG4a-%&qNx2vD2U#efL->ToKKWGv(eKpCN!5z4Lgl3dxjApC`uSwTr zXtFdsjX)#PO47qv$|gmX4$2>+*C`okFM973jjc2HkPpS>09L4c%?sJ>3J{W8J^H_xeuy z1bw2uyS}e}fPRoZMW3n%^-J`t^>zBq`akr|`oHy;^jGya^lkcw`X~B!{WJXw{eOn8 zhMtBbLqEenL#ko8VU%HvVXR?_VY-20;21=PJcHDrFlY@1!)(JmL!n{0VTHkMs3@LU zJg0bZv8~usTv1$8TvuFQ+)&(Dysdag@$TZ|#ixqT6#rFxzW74%#o|lFpGroRa7&h# zSW7%5SV_2~w&drMx{|#mM@uf0TrIg?a=+wJ$&-@yk`E=HOTHSr7`qw2G4?eMFeV#^ z7)KdL8`F%FjVxoPG0T{34nnP(#xd}O5dBun*p=HtTh*zedd4}Gl$KU=D4}myxP3hyxv@A zt~WQB8_j3T&E|9F3+7hy74tRo4fAdDUGshOBlADzcJs3pV^(CXP_CG@!ndMoMg5BA z71u3YEJH0*ElSHGi^XEMI4vHF&w^OiSTz_%Pq@Y zOPl4T<*T*7^*d{tb-I;gC0l7$rZwBjvkI&ttJpfzT4Y^hwOB*e_0~FTqjjJ4khR%* z)q2f(-Fnmd%9dzLwvD%ewk#XR#<%6z#5T7LwXL%KWLsz3X#2(Xn{A7&$+q2g$o8kL z#dgtl$#&KD(Du9|4mZ#~!%nxe?3s4RKG$AsH`&d0t9^sL$^N^2r+tt8oc)IVw*9XC zzWurVb6Ka37oEhiq_Whq;bkMszAwXdW?*oh!Rq_RNvs=;rvw(ZkWp zk?a`mNO#a2a>qi4(Xr04$#KDP-Eqrt*KyzR((&H$$??_M#o682%h|`--#O4Z()pv4 z;S@Vn&H|^-In!C>^f?1g%-QJt)A`)h#Wm7}bJ1NIm)2!)&34UmEpRPzEpaV(`CS#R zpey2vx#F(1uHRf+TurXuUAtU+UHe_9UCpkut`^sOccQzyyQe$JJ;puFjdK&+Bsb_5 zx%1pox5BM+FLW<)FLRf;eeRe$?yhyOa&L6+b02bFa<{ubczS#KdIop~c~U&zc}93f zd(u4PJ%ESmp?jDf(39oic=#TnC)bnbS?mdVwtHGVpS;7pOt02!_xij6FXjz-YrN~c z8@-#nzj_sXYbeYE*%k!0p+andF4yX z{pDEs#_}!YC(8dUZ!3RM{-OM{uahsqm+0%`>+ehU4fYN7edkN_QGAdu*H_@1?7!x};lJg7 zg>*s^kVK?A(hEsK1|!3fQOFo%95MkRB2fz-h8z^K5Oz_`GK06vf& z00PtiBLD{2fw=*1pf0dCa4qmC@L%9V;B!T%iiC>9itZJ?DpD(kSB$Lqz9Ow+T*ZWn zNflEns1@ppWgQujBNZ3Xu4pPc9{mxWj7~#wC;^2~393NVD2&cT=b-b^1*jjbM1Mx> z(0a51Z9;!XccFXGz33tIFZ2T1ie5o)qIb|X^a1)5{fKqK60k(9Czgcu!vC2FAkV*eq-=R){UcmSD@UGR%c}F+Wy;Vc1G+4fZp(0o#PtV>_`u*naE} z>AU>EL1cHC#gQBU?;n7jivC$u*6Qh%(=}|Chj=H00bW3!5 z^kVdG^hNY_^uOqb=;z8#l?j!JmC2QZD^n^{D~DB%tQ=jLRynRxP`RiwR=KD0Qsu{3 zQfzQ+NGvr5#GsfyHY+waRv23tTM}CyGses@Ypg8hig{!H*oN4q*srn8v8}P~u^qA9 zvAwbVv4gQgv7@mQu~V@dvA0!8RimrORnn>@RcKX1)xN58Rrjl2SADMf67L-E5$_vM zj!%vg-yYu?-yJ^?Z;9WG zzlndT?p)oqIH6D{iga|^~dTjHQj2yt?5~lRMWR+T+P&) z88w8O^croAp=MUi+?x3{MK!l-KGt@v?O=_yy=zC*POqhPWN|WUp;~S&zjkqLX|1`| bTI*f&;(zz0E?xdNdyoHr_oj|@&FlXIT - - - - SchemeUserState - - CrudRouter.xcscheme_^#shared#^_ - - orderHint - 0 - - - SuppressBuildableAutocreation - - CrudRouter - - primary - - - CrudRouterTests - - primary - - - - - From 5049f91ae5bce2dbfcb219f8bdd9bda16ba10fb5 Mon Sep 17 00:00:00 2001 From: twof Date: Sun, 29 Dec 2019 16:20:52 -0800 Subject: [PATCH 13/38] base test working with XCTVapor --- .../CrudRouteCreationTests.swift | 67 +++++++ .../CrudRouteResponseTests.swift | 75 +++++++ Tests/CrudRouterTests/CrudRouterTests.swift | 184 ------------------ 3 files changed, 142 insertions(+), 184 deletions(-) create mode 100644 Tests/CrudRouterTests/CrudRouteCreationTests.swift create mode 100644 Tests/CrudRouterTests/CrudRouteResponseTests.swift delete mode 100644 Tests/CrudRouterTests/CrudRouterTests.swift diff --git a/Tests/CrudRouterTests/CrudRouteCreationTests.swift b/Tests/CrudRouterTests/CrudRouteCreationTests.swift new file mode 100644 index 0000000..e34f7e9 --- /dev/null +++ b/Tests/CrudRouterTests/CrudRouteCreationTests.swift @@ -0,0 +1,67 @@ +import XCTest +@testable import CrudRouter +import Vapor +import XCTVapor + +extension PathComponent { + var stringComponent: String { + switch self { + case .constant(let val): + return val + case .parameter(let val): + return ":\(val)" + default: + return "all" + } + } +} + +//func routes(_ router: RoutesBuilder) throws { +// router.crud(register: Galaxy.self) { controller in +// controller.crud(children: \.$planets) +// } +// router.crud(register: Planet.self) { controller in +// controller.crud(parent: \.$galaxy) +// controller.crud(siblings: \.$tags) +// } +// router.crud(register: Tag.self) { controller in +// controller.crud(siblings: \.$planets) +// } +//} + +final class CrudRouteCreationTests: XCTestCase { + func testBaseCrudRegistrationWithRouteName() throws { + let app = Application() + app.crud("planets", register: Planet.self) + + XCTAssert(app.routes.all.isEmpty == false) + XCTAssert(app.routes.all.count == 5) + let paths = app.routes.all.map { [$0.method.rawValue] + $0.path.map { $0.stringComponent } } + + XCTAssert(paths.contains { $0 == ["GET", "planets"] }) + XCTAssert(paths.contains { $0 == ["GET", "planets", ":planetsID"] }) + XCTAssert(paths.contains { $0 == ["POST", "planets"] }) + XCTAssert(paths.contains { $0 == ["PUT", "planets", ":planetsID"] }) + XCTAssert(paths.contains { $0 == ["DELETE", "planets", ":planetsID"] }) + } + + func testBaseCrudRegistrationWithDefaultRoute() throws { + let app = Application() + app.crud(register: Planet.self) + + XCTAssert(app.routes.all.isEmpty == false) + XCTAssert(app.routes.all.count == 5) + let paths = app.routes.all.map { [$0.method.rawValue] + $0.path.map { $0.stringComponent } } + + XCTAssert(paths.contains { $0 == ["GET", "planet"] }) + XCTAssert(paths.contains { $0 == ["GET", "planet", ":planetsID"] }) + XCTAssert(paths.contains { $0 == ["POST", "planet"] }) + XCTAssert(paths.contains { $0 == ["PUT", "planet", ":planetsID"] }) + XCTAssert(paths.contains { $0 == ["DELETE", "planet", ":planetsID"] }) + } + + static var allTests = [ + ("testBaseCrudRegistrationWithRouteName", testBaseCrudRegistrationWithRouteName), + ("testBaseCrudRegistrationWithDefaultRoute", testBaseCrudRegistrationWithDefaultRoute), + ] +} diff --git a/Tests/CrudRouterTests/CrudRouteResponseTests.swift b/Tests/CrudRouterTests/CrudRouteResponseTests.swift new file mode 100644 index 0000000..ab08b20 --- /dev/null +++ b/Tests/CrudRouterTests/CrudRouteResponseTests.swift @@ -0,0 +1,75 @@ +// +// File.swift +// +// +// Created by fnord on 12/29/19. +// + +import FluentSQLiteDriver +import Fluent +import XCTVapor + +struct TestSeeding: Migration { + func prepare(on database: Database) -> EventLoopFuture { + return TestSeeding.galaxies.map { + $0.save(on: database).transform(to: ()) + }.flatten(on: database.eventLoop) + } + + func revert(on database: Database) -> EventLoopFuture { + return TestSeeding.galaxies.map { + $0.delete(on: database).transform(to: ()) + }.flatten(on: database.eventLoop) + } + + static let galaxies = [Galaxy(name: "Milky Way")] +} + +func configure(_ app: Application) throws { + // Serves files from `Public/` directory + // app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory)) + + // Configure SQLite database + app.databases.use(.sqlite(file: "db.sqlite"), as: .sqlite) + + // Configure migrations + app.migrations.add(GalaxyMigration()) + app.migrations.add(PlanetMigration()) + app.migrations.add(PlanetTagMigration()) + app.migrations.add(TagMigration()) + app.migrations.add(TestSeeding()) +} + +final class CrudRouteResponseTests: XCTestCase { + var app: Application! + + override func setUp() { + super.setUp() + + app = Application() + try! configure(app) + } + + func testBase() throws { + app.crud(register: Galaxy.self) + + do { + try app.testable().test(.GET, "/galaxy", closure: { (resp) in + XCTAssert(resp.status == .ok) + guard let bodyBuffer = resp.body.buffer else { XCTFail(); return } + + let decoded = try JSONDecoder().decode([Galaxy].self, from: bodyBuffer) + + XCTAssert(decoded.count == 1) + XCTAssert(decoded[0].name == "Milky Way") + XCTAssert(decoded[0].id == 1) + }) + } catch { + XCTFail("Probably couldn't decode to public galaxy: \(error.localizedDescription)") + } + } + + static var allTests = [ + ("testPublicable", testPublicable), + ] +} diff --git a/Tests/CrudRouterTests/CrudRouterTests.swift b/Tests/CrudRouterTests/CrudRouterTests.swift deleted file mode 100644 index ce7da6d..0000000 --- a/Tests/CrudRouterTests/CrudRouterTests.swift +++ /dev/null @@ -1,184 +0,0 @@ -import XCTest -@testable import CrudRouter -import Vapor -import FluentSQLiteDriver -import Fluent -import XCTVapor - -extension PathComponent { - var stringComponent: String { - switch self { - case .constant(let val): - return val - case .parameter(let val): - return ":\(val)" - default: - return "all" - } - } -} - -//extension Array where Element: Model { -// func save(on conn: Database) -> EventLoopFuture<[Element]> { -// -// return conn.transaction(on: conn) { (dbConn) -> EventLoopFuture<[Element]> in -// return self.map { $0.save(on: dbConn) }.flatten(on: dbConn) -// } -// } -// -// func delete(on conn: Element.Database.Connection) -> EventLoopFuture<[Element]> { -// let databaseIdentifier = Element.defaultDatabase! -// -// return conn.transaction(on: databaseIdentifier) { (dbConn) -> EventLoopFuture<[Element]> in -// return self.map { element in -// return element.delete(on: dbConn).transform(to: element) -// }.flatten(on: dbConn) -// } -// } -// -// func create(on conn: Element.Database.Connection) -> EventLoopFuture<[Element]> { -// let databaseIdentifier = Element.defaultDatabase! -// -// return conn.transaction(on: databaseIdentifier) { (dbConn) -> EventLoopEventLoopFuture<[Element]> in -// return self.map { $0.create(on: dbConn) }.flatten(on: dbConn) -// } -// } -//} - - -struct TestSeeding: Migration { - func prepare(on database: Database) -> EventLoopFuture { - return TestSeeding.galaxies.map { - $0.save(on: database).transform(to: ()) - }.flatten(on: database.eventLoop) - } - - func revert(on database: Database) -> EventLoopFuture { - return TestSeeding.galaxies.map { - $0.delete(on: database).transform(to: ()) - }.flatten(on: database.eventLoop) - } - - static let galaxies = [Galaxy(name: "Milky Way")] -} - -func configure(_ app: Application) throws { - // Serves files from `Public/` directory - // app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory)) - - // Configure SQLite database - app.databases.use(.sqlite(file: "db.sqlite"), as: .sqlite) - - // Configure migrations - app.migrations.add(GalaxyMigration()) - app.migrations.add(PlanetMigration()) - app.migrations.add(PlanetTagMigration()) - app.migrations.add(TagMigration()) - app.migrations.add(TestSeeding()) -} - -func routes(_ router: RoutesBuilder) throws { - router.crud(register: Galaxy.self) { controller in - controller.crud(children: \.$planets) - } - router.crud(register: Planet.self) { controller in - controller.crud(parent: \.$galaxy) - controller.crud(siblings: \.$tags) - } - router.crud(register: Tag.self) { controller in - controller.crud(siblings: \.$planets) - } -} - -extension Application { - static func testable(envArgs: [String]? = nil) throws -> Application { - let app = Application() - try configure(app) - - try boot(app)() - - // Prepare migrator and run migrations - try app.migrator.setupIfNeeded().wait() - try app.migrator.prepareBatch().wait() - - return app - } - - func sendRequest(to path: String, method: HTTPMethod, headers: HTTPHeaders = .init(), body: T? = nil) throws -> Response where T: Content { - let request = Request(application: self, method: method, url: URI(path: path), on: self.eventLoopGroup.next()) - return try self.responder.respond(to: request).wait() - } - - func sendRequest(to path: String, method: HTTPMethod, headers: HTTPHeaders = .init()) throws -> Response { - let emptyContent: EmptyContent? = nil - return try sendRequest(to: path, method: method, headers: headers, body: emptyContent) - } - - func getResponse(to path: String, method: HTTPMethod = .GET, headers: HTTPHeaders = .init(), data: C? = nil, decodeTo type: T.Type) throws -> T where C: Content, T: Decodable { - let response = try self.sendRequest(to: path, method: method, headers: headers, body: data) - return try response.content.decode(type) - } - - func getResponse(to path: String, method: HTTPMethod = .GET, headers: HTTPHeaders = .init(), decodeTo type: T.Type) throws -> T where T: Content { - let emptyContent: EmptyContent? = nil - return try self.getResponse(to: path, method: method, headers: headers, data: emptyContent, decodeTo: type) - } - - func sendRequest(to path: String, method: HTTPMethod, headers: HTTPHeaders, data: T) throws where T: Content { - _ = try self.sendRequest(to: path, method: method, headers: headers, body: data) - } -} - -struct EmptyContent: Content {} - -final class CrudRouterTests: XCTestCase { - - func testBaseCrudRegistrationWithRouteName() throws { - Application. - - app.crud("planets", register: Planet.self) - - XCTAssert(app.routes.all.isEmpty == false) - XCTAssert(app.routes.all.count == 5) - let paths = app.routes.all.map { [$0.method.rawValue] + $0.path.map { $0.stringComponent } } - - XCTAssert(paths.contains { $0 == ["GET", "planets"] }) - XCTAssert(paths.contains { $0 == ["GET", "planets", ":planetsID"] }) - XCTAssert(paths.contains { $0 == ["POST", "planets"] }) - XCTAssert(paths.contains { $0 == ["PUT", "planets", ":planetsID"] }) - XCTAssert(paths.contains { $0 == ["DELETE", "planets", ":planetsID"] }) - } - - func testBaseCrudRegistrationWithDefaultRoute() throws { - app.crud(register: Planet.self) - - XCTAssert(app.routes.all.isEmpty == false) - XCTAssert(app.routes.all.count == 5) - let paths = app.routes.all.map { [$0.method.rawValue] + $0.path.map { $0.stringComponent } } - - XCTAssert(paths.contains { $0 == ["GET", "planet"] }) - XCTAssert(paths.contains { $0 == ["GET", "planet", ":planetsID"] }) - XCTAssert(paths.contains { $0 == ["POST", "planet"] }) - XCTAssert(paths.contains { $0 == ["PUT", "planet", ":planetsID"] }) - XCTAssert(paths.contains { $0 == ["DELETE", "planet", ":planetsID"] }) - } - - func testPublicable() throws { - app.crud(register: Galaxy.self) - do { - let resp = try app.sendRequest(to: "/galaxy", method: .GET) - let decoded = try resp.content.decode([Galaxy].self) - XCTAssert(decoded.count == 1) - XCTAssert(decoded[0].name == "Milky Way") - XCTAssert(decoded[0].id == 1) - } catch { - XCTFail("Probably couldn't decode to public galaxy: \(error.localizedDescription)") - } - } - - static var allTests = [ - ("testBaseCrudRegistrationWithRouteName", testBaseCrudRegistrationWithRouteName), - ("testBaseCrudRegistrationWithDefaultRoute", testBaseCrudRegistrationWithDefaultRoute), - ("testPublicable", testPublicable), - ] -} From 7b071f3e744258f0716a8839daaeeea34e5a8b4c Mon Sep 17 00:00:00 2001 From: twof Date: Sun, 29 Dec 2019 18:31:29 -0800 Subject: [PATCH 14/38] child test passing. Suite is failing due to setup being run more than once --- .../CrudChildrenControllerProtocol.swift | 10 +-- .../CrudControllerProtocol.swift | 6 +- .../CrudParentControllerProtocol.swift | 4 +- .../CrudSiblingsControllerProtocol.swift | 10 +-- Sources/CrudRouter/Extensions/Request.swift | 4 +- .../CrudRouteResponseTests.swift | 73 ++++++++++++++----- .../TestMigrations/BaseGalaxySeeding.swift | 24 ++++++ .../TestMigrations/ChildSeeding.swift | 26 +++++++ 8 files changed, 121 insertions(+), 36 deletions(-) create mode 100644 Tests/CrudRouterTests/TestMigrations/BaseGalaxySeeding.swift create mode 100644 Tests/CrudRouterTests/TestMigrations/ChildSeeding.swift diff --git a/Sources/CrudRouter/ControllerProtocols/CrudChildrenControllerProtocol.swift b/Sources/CrudRouter/ControllerProtocols/CrudChildrenControllerProtocol.swift index 3cbc83f..3a4d3f5 100644 --- a/Sources/CrudRouter/ControllerProtocols/CrudChildrenControllerProtocol.swift +++ b/Sources/CrudRouter/ControllerProtocols/CrudChildrenControllerProtocol.swift @@ -26,7 +26,7 @@ public protocol CrudChildrenControllerProtocol { public extension CrudChildrenControllerProtocol { func index(_ req: Request) throws -> EventLoopFuture { - let parentId: ParentType.IDValue = try req.getId() + let parentId = try req.getId(modelType: ParentType.self) return try ParentType .find(parentId, on: req.db) @@ -40,7 +40,7 @@ public extension CrudChildrenControllerProtocol { } func indexAll(_ req: Request) throws -> EventLoopFuture<[ChildType]> { - let parentId: ParentType.IDValue = try req.getId() + let parentId = try req.getId(modelType: ParentType.self) return try ParentType .find(parentId, on: req.db) .unwrap(or: Abort(.notFound)) @@ -52,7 +52,7 @@ public extension CrudChildrenControllerProtocol { } func create(_ req: Request) throws -> EventLoopFuture { - let parentId: ParentType.IDValue = try req.getId() + let parentId = try req.getId(modelType: ParentType.self) return try ParentType .find(parentId, on: req.db) @@ -64,7 +64,7 @@ public extension CrudChildrenControllerProtocol { } func update(_ req: Request) throws -> EventLoopFuture { - let parentId: ParentType.IDValue = try req.getId() + let parentId = try req.getId(modelType: ParentType.self) return try ParentType .find(parentId, on: req.db) @@ -83,7 +83,7 @@ public extension CrudChildrenControllerProtocol { } func delete(_ req: Request) throws -> EventLoopFuture { - let parentId: ParentType.IDValue = try req.getId() + let parentId = try req.getId(modelType: ParentType.self) return try ParentType .find(parentId, on: req.db) diff --git a/Sources/CrudRouter/ControllerProtocols/CrudControllerProtocol.swift b/Sources/CrudRouter/ControllerProtocols/CrudControllerProtocol.swift index 0dfc625..7978e16 100644 --- a/Sources/CrudRouter/ControllerProtocols/CrudControllerProtocol.swift +++ b/Sources/CrudRouter/ControllerProtocols/CrudControllerProtocol.swift @@ -17,7 +17,7 @@ public extension CrudControllerProtocol { } func index(_ req: Request) throws -> EventLoopFuture { - let id: ModelType.IDValue = try req.getId() + let id = try req.getId(modelType: ModelType.self) return ModelType.find(id, on: req.db).unwrap(or: Abort(.notFound)) } @@ -27,7 +27,7 @@ public extension CrudControllerProtocol { } func update(_ req: Request) throws -> EventLoopFuture { - let id: ModelType.IDValue = try req.getId() + let id = try req.getId(modelType: ModelType.self) let model = try req.content.decode(ModelType.self) let temp = model @@ -36,7 +36,7 @@ public extension CrudControllerProtocol { } func delete(_ req: Request) throws -> EventLoopFuture { - let id: ModelType.IDValue = try req.getId() + let id = try req.getId(modelType: ModelType.self) return ModelType .find(id, on: req.db) .unwrap(or: Abort(.notFound)) diff --git a/Sources/CrudRouter/ControllerProtocols/CrudParentControllerProtocol.swift b/Sources/CrudRouter/ControllerProtocols/CrudParentControllerProtocol.swift index 9f497dc..4511a63 100644 --- a/Sources/CrudRouter/ControllerProtocols/CrudParentControllerProtocol.swift +++ b/Sources/CrudRouter/ControllerProtocols/CrudParentControllerProtocol.swift @@ -13,7 +13,7 @@ public protocol CrudParentControllerProtocol { public extension CrudParentControllerProtocol { func index(_ req: Request) throws -> EventLoopFuture { - let childId: ChildType.IDValue = try req.getId() + let childId = try req.getId(modelType: ChildType.self) return ChildType.find(childId, on: req.db).unwrap(or: Abort(.notFound)).flatMap { child in child[keyPath: self.relation].get(on: req.db) @@ -21,7 +21,7 @@ public extension CrudParentControllerProtocol { } func update(_ req: Request) throws -> EventLoopFuture { - let childId: ChildType.IDValue = try req.getId() + let childId = try req.getId(modelType: ChildType.self) return ChildType .find(childId, on: req.db) diff --git a/Sources/CrudRouter/ControllerProtocols/CrudSiblingsControllerProtocol.swift b/Sources/CrudRouter/ControllerProtocols/CrudSiblingsControllerProtocol.swift index 6725c25..e9634d1 100644 --- a/Sources/CrudRouter/ControllerProtocols/CrudSiblingsControllerProtocol.swift +++ b/Sources/CrudRouter/ControllerProtocols/CrudSiblingsControllerProtocol.swift @@ -28,7 +28,7 @@ public protocol CrudSiblingsControllerProtocol { public extension CrudSiblingsControllerProtocol { func index(_ req: Request) throws -> EventLoopFuture { - let parentId: ParentType.IDValue = try req.getId() + let parentId = try req.getId(modelType: ParentType.self) return try ParentType.find(parentId, on: req.db).unwrap(or: Abort(.notFound)).throwingFlatMap { parent -> EventLoopFuture in @@ -40,7 +40,7 @@ public extension CrudSiblingsControllerProtocol { } func indexAll(_ req: Request) throws -> EventLoopFuture<[ChildType]> { - let parentId: ParentType.IDValue = try req.getId() + let parentId = try req.getId(modelType: ParentType.self) return try ParentType.find(parentId, on: req.db).unwrap(or: Abort(.notFound)).throwingFlatMap { parent -> EventLoopFuture<[ChildType]> in let siblingsRelation = parent[keyPath: self.siblings] @@ -51,7 +51,7 @@ public extension CrudSiblingsControllerProtocol { } func update(_ req: Request) throws -> EventLoopFuture { - let parentId: ParentType.IDValue = try req.getId() + let parentId = try req.getId(modelType: ParentType.self) return try ParentType .find(parentId, on: req.db) @@ -77,7 +77,7 @@ public extension CrudSiblingsControllerProtocol // ThroughType.Right == ChildType { func create(_ req: Request) throws -> EventLoopFuture { - let parentId: ParentType.IDValue = try req.getId() + let parentId = try req.getId(modelType: ParentType.self) return try ParentType.find(parentId, on: req.db).unwrap(or: Abort(.notFound)).throwingFlatMap { parent -> EventLoopFuture in let child = try req.content.decode(ChildType.self) @@ -88,7 +88,7 @@ public extension CrudSiblingsControllerProtocol } func delete(_ req: Request) throws -> EventLoopFuture { - let parentId: ParentType.IDValue = try req.getId() + let parentId = try req.getId(modelType: ParentType.self) return try ParentType .find(parentId, on: req.db) diff --git a/Sources/CrudRouter/Extensions/Request.swift b/Sources/CrudRouter/Extensions/Request.swift index 1de28c7..0907663 100644 --- a/Sources/CrudRouter/Extensions/Request.swift +++ b/Sources/CrudRouter/Extensions/Request.swift @@ -2,8 +2,8 @@ import Vapor import Fluent extension Request { - func getId() throws -> T where T: LosslessStringConvertible { - guard let id: T = self.parameters.get("id") else { fatalError() } + func getId(modelType: M.Type) throws -> M.IDValue where M.IDValue: LosslessStringConvertible { + guard let id: M.IDValue = self.parameters.get("\(modelType.schema)ID") else { fatalError() } return id } diff --git a/Tests/CrudRouterTests/CrudRouteResponseTests.swift b/Tests/CrudRouterTests/CrudRouteResponseTests.swift index ab08b20..012eb9f 100644 --- a/Tests/CrudRouterTests/CrudRouteResponseTests.swift +++ b/Tests/CrudRouterTests/CrudRouteResponseTests.swift @@ -9,35 +9,21 @@ import FluentSQLiteDriver import Fluent import XCTVapor -struct TestSeeding: Migration { - func prepare(on database: Database) -> EventLoopFuture { - return TestSeeding.galaxies.map { - $0.save(on: database).transform(to: ()) - }.flatten(on: database.eventLoop) - } - - func revert(on database: Database) -> EventLoopFuture { - return TestSeeding.galaxies.map { - $0.delete(on: database).transform(to: ()) - }.flatten(on: database.eventLoop) - } - - static let galaxies = [Galaxy(name: "Milky Way")] -} - func configure(_ app: Application) throws { // Serves files from `Public/` directory // app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory)) // Configure SQLite database - app.databases.use(.sqlite(file: "db.sqlite"), as: .sqlite) + app.databases.use(.sqlite(), as: .sqlite) // Configure migrations app.migrations.add(GalaxyMigration()) app.migrations.add(PlanetMigration()) app.migrations.add(PlanetTagMigration()) app.migrations.add(TagMigration()) - app.migrations.add(TestSeeding()) + + app.migrations.add(BaseGalaxySeeding()) + app.migrations.add(ChildSeeding()) } final class CrudRouteResponseTests: XCTestCase { @@ -48,11 +34,41 @@ final class CrudRouteResponseTests: XCTestCase { app = Application() try! configure(app) + + try! app.migrator.setupIfNeeded().wait() + try! app.migrator.prepareBatch().wait() + } + + override func tearDown() { + super.tearDown() + + try! app.migrator.revertAllBatches().wait() } func testBase() throws { app.crud(register: Galaxy.self) + do { + try app.testable().test(.GET, "/galaxy", closure: { (resp) in + XCTAssert(resp.status == .ok) + guard let bodyBuffer = resp.body.buffer else { XCTFail(); return } + + let decoded = try JSONDecoder().decode([Galaxy].self, from: bodyBuffer) + print(decoded) + XCTAssert(decoded.count == 1) + XCTAssert(decoded[0].name == "Milky Way") + XCTAssert(decoded[0].id == 1) + }) + } catch { + XCTFail("Probably couldn't decode to public galaxy: \(error.localizedDescription)") + } + } + + func testChildren() throws { + app.crud(register: Galaxy.self) { (controller) in + controller.crud(children: \.$planets) + } + do { try app.testable().test(.GET, "/galaxy", closure: { (resp) in XCTAssert(resp.status == .ok) @@ -64,12 +80,31 @@ final class CrudRouteResponseTests: XCTestCase { XCTAssert(decoded[0].name == "Milky Way") XCTAssert(decoded[0].id == 1) }) + + try app.testable().test(.GET, "/galaxy/1/planet", closure: { (resp) in + XCTAssert(resp.status == .ok) + guard let bodyBuffer = resp.body.buffer else { XCTFail(); return } + + let decoded = try JSONDecoder().decode([Planet].self, from: bodyBuffer) + + XCTAssert(decoded.count == 1) + XCTAssert(decoded[0].name == "Earth") + XCTAssert(decoded[0].id == 1) + }) } catch { XCTFail("Probably couldn't decode to public galaxy: \(error.localizedDescription)") } } + + func testParents() throws { + + } + + func testSiblings() throws { + + } static var allTests = [ - ("testPublicable", testPublicable), + ("testPublicable", testBase), ] } diff --git a/Tests/CrudRouterTests/TestMigrations/BaseGalaxySeeding.swift b/Tests/CrudRouterTests/TestMigrations/BaseGalaxySeeding.swift new file mode 100644 index 0000000..9746579 --- /dev/null +++ b/Tests/CrudRouterTests/TestMigrations/BaseGalaxySeeding.swift @@ -0,0 +1,24 @@ +// +// File.swift +// +// +// Created by fnord on 12/29/19. +// + +import FluentKit + +struct BaseGalaxySeeding: Migration { + func prepare(on database: Database) -> EventLoopFuture { + return BaseGalaxySeeding.galaxies.map { + $0.save(on: database).transform(to: ()) + }.flatten(on: database.eventLoop) + } + + func revert(on database: Database) -> EventLoopFuture { + return BaseGalaxySeeding.galaxies.map { + $0.delete(on: database).transform(to: ()) + }.flatten(on: database.eventLoop) + } + + static let galaxies = [Galaxy(name: "Milky Way")] +} diff --git a/Tests/CrudRouterTests/TestMigrations/ChildSeeding.swift b/Tests/CrudRouterTests/TestMigrations/ChildSeeding.swift new file mode 100644 index 0000000..f287b09 --- /dev/null +++ b/Tests/CrudRouterTests/TestMigrations/ChildSeeding.swift @@ -0,0 +1,26 @@ +// +// File.swift +// +// +// Created by fnord on 12/29/19. +// + +import FluentKit + +struct ChildSeeding: Migration { + func prepare(on database: Database) -> EventLoopFuture { + return ChildSeeding.planets.map { planet in + planet + .save(on: database) + .transform(to: ()) + }.flatten(on: database.eventLoop) + } + + func revert(on database: Database) -> EventLoopFuture { + return ChildSeeding.planets.map { + $0.delete(on: database).transform(to: ()) + }.flatten(on: database.eventLoop) + } + + static let planets = [Planet(id: 1, name: "Earth", galaxyID: 1)] +} From c31560afda8737e7ebd9c500ab873e2724a3deb2 Mon Sep 17 00:00:00 2001 From: twof Date: Sun, 29 Dec 2019 19:07:51 -0800 Subject: [PATCH 15/38] get siblings test succeeding --- .../CrudRouteResponseTests.swift | 92 ++++++++++++++++++- .../TestMigrations/SiblingSeeding.swift | 31 +++++++ 2 files changed, 118 insertions(+), 5 deletions(-) create mode 100644 Tests/CrudRouterTests/TestMigrations/SiblingSeeding.swift diff --git a/Tests/CrudRouterTests/CrudRouteResponseTests.swift b/Tests/CrudRouterTests/CrudRouteResponseTests.swift index 012eb9f..ed56228 100644 --- a/Tests/CrudRouterTests/CrudRouteResponseTests.swift +++ b/Tests/CrudRouterTests/CrudRouteResponseTests.swift @@ -24,6 +24,7 @@ func configure(_ app: Application) throws { app.migrations.add(BaseGalaxySeeding()) app.migrations.add(ChildSeeding()) + app.migrations.add(SiblingSeeding()) } final class CrudRouteResponseTests: XCTestCase { @@ -45,7 +46,7 @@ final class CrudRouteResponseTests: XCTestCase { try! app.migrator.revertAllBatches().wait() } - func testBase() throws { + func testGetBase() throws { app.crud(register: Galaxy.self) do { @@ -64,7 +65,7 @@ final class CrudRouteResponseTests: XCTestCase { } } - func testChildren() throws { + func testGetChildren() throws { app.crud(register: Galaxy.self) { (controller) in controller.crud(children: \.$planets) } @@ -96,15 +97,96 @@ final class CrudRouteResponseTests: XCTestCase { } } - func testParents() throws { + func testGetParents() throws { + app.crud(register: Planet.self) { (controller) in + controller.crud(parent: \.$galaxy) + } + do { + try app.testable().test(.GET, "/planet", closure: { (resp) in + XCTAssert(resp.status == .ok) + guard let bodyBuffer = resp.body.buffer else { XCTFail(); return } + + let decoded = try JSONDecoder().decode([Planet].self, from: bodyBuffer) + + XCTAssert(decoded.count == 1) + XCTAssert(decoded[0].name == "Earth") + XCTAssert(decoded[0].id == 1) + }) + + try app.testable().test(.GET, "/planet/1/galaxy", closure: { (resp) in + XCTAssert(resp.status == .ok) + guard let bodyBuffer = resp.body.buffer else { XCTFail(); return } + + let decoded = try JSONDecoder().decode(Galaxy.self, from: bodyBuffer) + + XCTAssert(decoded.name == "Milky Way") + XCTAssert(decoded.id == 1) + }) + } catch { + XCTFail("Probably couldn't decode to public galaxy: \(error.localizedDescription)") + } } - func testSiblings() throws { + func testGetSiblings() throws { + app.crud(register: Planet.self) { (controller) in + controller.crud(siblings: \.$tags) + } + + app.crud(register: Tag.self) { controller in + controller.crud(siblings: \.$planets) + } + do { + try app.testable().test(.GET, "/planet", closure: { (resp) in + XCTAssert(resp.status == .ok) + guard let bodyBuffer = resp.body.buffer else { XCTFail(); return } + + let decoded = try JSONDecoder().decode([Planet].self, from: bodyBuffer) + + XCTAssert(decoded.count == 1) + XCTAssert(decoded[0].name == "Earth") + XCTAssert(decoded[0].id == 1) + }) + + try app.testable().test(.GET, "/tag", closure: { (resp) in + XCTAssert(resp.status == .ok) + guard let bodyBuffer = resp.body.buffer else { XCTFail(); return } + + let decoded = try JSONDecoder().decode([Tag].self, from: bodyBuffer) + + XCTAssert(decoded.count == 1) + XCTAssert(decoded[0].name == "Life-Supporting") + XCTAssert(decoded[0].id == 1) + }) + + try app.testable().test(.GET, "/planet/1/tag", closure: { (resp) in + XCTAssert(resp.status == .ok) + guard let bodyBuffer = resp.body.buffer else { XCTFail(); return } + + let decoded = try JSONDecoder().decode([Tag].self, from: bodyBuffer) + + XCTAssert(decoded.count == 1) + XCTAssert(decoded[0].name == "Life-Supporting") + XCTAssert(decoded[0].id == 1) + }) + + try app.testable().test(.GET, "/tag/1/planet", closure: { (resp) in + XCTAssert(resp.status == .ok) + guard let bodyBuffer = resp.body.buffer else { XCTFail(); return } + + let decoded = try JSONDecoder().decode([Planet].self, from: bodyBuffer) + + XCTAssert(decoded.count == 1) + XCTAssert(decoded[0].name == "Earth") + XCTAssert(decoded[0].id == 1) + }) + } catch { + XCTFail("Probably couldn't decode to public galaxy: \(error.localizedDescription)") + } } static var allTests = [ - ("testPublicable", testBase), + ("testPublicable", testGetBase), ] } diff --git a/Tests/CrudRouterTests/TestMigrations/SiblingSeeding.swift b/Tests/CrudRouterTests/TestMigrations/SiblingSeeding.swift new file mode 100644 index 0000000..77c87df --- /dev/null +++ b/Tests/CrudRouterTests/TestMigrations/SiblingSeeding.swift @@ -0,0 +1,31 @@ +// +// SiblingSeeding.swift +// AsyncHTTPClient +// +// Created by fnord on 12/29/19. +// + +import FluentKit + +struct SiblingSeeding: Migration { + func prepare(on database: Database) -> EventLoopFuture { + return SiblingSeeding.tags.map { tag in + return tag + .save(on: database) + .transform(to: ()) + .map { tag.$planets.attach(ChildSeeding.planets[0], on: database) } + }.flatten(on: database.eventLoop) + } + + func revert(on database: Database) -> EventLoopFuture { + return SiblingSeeding.tags.map { tag in + [ + tag.$planets.detach(ChildSeeding.planets[0], on: database), + tag.delete(on: database).transform(to: ()) + ].flatten(on: database.eventLoop) + }.flatten(on: database.eventLoop) + } + + static var tags = [Tag(id: 1, name: "Life-Supporting")] +} + From a278b2cdab5e10ed59cfaef97079251cb54198df Mon Sep 17 00:00:00 2001 From: twof Date: Sun, 29 Dec 2019 22:29:26 -0800 Subject: [PATCH 16/38] renamed --- .../{CrudRouteResponseTests.swift => CrudRouteGetResponseTests} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename Tests/CrudRouterTests/{CrudRouteResponseTests.swift => CrudRouteGetResponseTests} (99%) diff --git a/Tests/CrudRouterTests/CrudRouteResponseTests.swift b/Tests/CrudRouterTests/CrudRouteGetResponseTests similarity index 99% rename from Tests/CrudRouterTests/CrudRouteResponseTests.swift rename to Tests/CrudRouterTests/CrudRouteGetResponseTests index ed56228..4a40ce4 100644 --- a/Tests/CrudRouterTests/CrudRouteResponseTests.swift +++ b/Tests/CrudRouterTests/CrudRouteGetResponseTests @@ -27,7 +27,7 @@ func configure(_ app: Application) throws { app.migrations.add(SiblingSeeding()) } -final class CrudRouteResponseTests: XCTestCase { +final class CrudRouteGetResponseTests: XCTestCase { var app: Application! override func setUp() { From 0c8e0b49368718e11afc287109e53d842aed34aa Mon Sep 17 00:00:00 2001 From: twof Date: Sat, 4 Jan 2020 17:50:47 -0800 Subject: [PATCH 17/38] add post tests file. Tests not passing --- ...eTests => CrudRouteGetResponseTests.swift} | 36 ++-- .../CrudRoutePostResponseTests.swift | 193 ++++++++++++++++++ 2 files changed, 211 insertions(+), 18 deletions(-) rename Tests/CrudRouterTests/{CrudRouteGetResponseTests => CrudRouteGetResponseTests.swift} (90%) create mode 100644 Tests/CrudRouterTests/CrudRoutePostResponseTests.swift diff --git a/Tests/CrudRouterTests/CrudRouteGetResponseTests b/Tests/CrudRouterTests/CrudRouteGetResponseTests.swift similarity index 90% rename from Tests/CrudRouterTests/CrudRouteGetResponseTests rename to Tests/CrudRouterTests/CrudRouteGetResponseTests.swift index 4a40ce4..7ac3ce4 100644 --- a/Tests/CrudRouterTests/CrudRouteGetResponseTests +++ b/Tests/CrudRouterTests/CrudRouteGetResponseTests.swift @@ -9,24 +9,6 @@ import FluentSQLiteDriver import Fluent import XCTVapor -func configure(_ app: Application) throws { - // Serves files from `Public/` directory - // app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory)) - - // Configure SQLite database - app.databases.use(.sqlite(), as: .sqlite) - - // Configure migrations - app.migrations.add(GalaxyMigration()) - app.migrations.add(PlanetMigration()) - app.migrations.add(PlanetTagMigration()) - app.migrations.add(TagMigration()) - - app.migrations.add(BaseGalaxySeeding()) - app.migrations.add(ChildSeeding()) - app.migrations.add(SiblingSeeding()) -} - final class CrudRouteGetResponseTests: XCTestCase { var app: Application! @@ -185,6 +167,24 @@ final class CrudRouteGetResponseTests: XCTestCase { XCTFail("Probably couldn't decode to public galaxy: \(error.localizedDescription)") } } + + private func configure(_ app: Application) throws { + // Serves files from `Public/` directory + // app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory)) + + // Configure SQLite database + app.databases.use(.sqlite(), as: .sqlite) + + // Configure migrations + app.migrations.add(GalaxyMigration()) + app.migrations.add(PlanetMigration()) + app.migrations.add(PlanetTagMigration()) + app.migrations.add(TagMigration()) + + app.migrations.add(BaseGalaxySeeding()) + app.migrations.add(ChildSeeding()) + app.migrations.add(SiblingSeeding()) + } static var allTests = [ ("testPublicable", testGetBase), diff --git a/Tests/CrudRouterTests/CrudRoutePostResponseTests.swift b/Tests/CrudRouterTests/CrudRoutePostResponseTests.swift new file mode 100644 index 0000000..8a7fee6 --- /dev/null +++ b/Tests/CrudRouterTests/CrudRoutePostResponseTests.swift @@ -0,0 +1,193 @@ +// +// CrudRoutePostResponseTests.swift +// AsyncHTTPClient +// +// Created by fnord on 1/4/20. +// + +import FluentSQLiteDriver +import Fluent +import XCTVapor + +final class CrudRoutePostResponseTests: XCTestCase { + var app: Application! + + override func setUp() { + super.setUp() + + app = Application() + try! configure(app) + + try! app.migrator.setupIfNeeded().wait() + try! app.migrator.prepareBatch().wait() + } + + override func tearDown() { + super.tearDown() + + try! app.migrator.revertAllBatches().wait() + } + + func testPostBase() throws { + app.crud(register: Galaxy.self) + + do { + try app.testable().test(.POST, "/galaxy", closure: { (resp) in + XCTAssert(resp.status == .ok) + guard let bodyBuffer = resp.body.buffer else { XCTFail(); return } + + let decoded = try JSONDecoder().decode([Galaxy].self, from: bodyBuffer) + print(decoded) + XCTAssert(decoded.count == 1) + XCTAssert(decoded[0].name == "Milky Way") + XCTAssert(decoded[0].id == 1) + }) + } catch { + XCTFail("Probably couldn't decode to public galaxy: \(error.localizedDescription)") + } + } + + func testPostChildren() throws { + app.crud(register: Galaxy.self) { (controller) in + controller.crud(children: \.$planets) + } + + do { + try app.testable().test(.POST, "/galaxy", closure: { (resp) in + XCTAssert(resp.status == .ok) + guard let bodyBuffer = resp.body.buffer else { XCTFail(); return } + + let decoded = try JSONDecoder().decode([Galaxy].self, from: bodyBuffer) + + XCTAssert(decoded.count == 1) + XCTAssert(decoded[0].name == "Milky Way") + XCTAssert(decoded[0].id == 1) + }) + + try app.testable().test(.POST, "/galaxy/1/planet", closure: { (resp) in + XCTAssert(resp.status == .ok) + guard let bodyBuffer = resp.body.buffer else { XCTFail(); return } + + let decoded = try JSONDecoder().decode([Planet].self, from: bodyBuffer) + + XCTAssert(decoded.count == 1) + XCTAssert(decoded[0].name == "Earth") + XCTAssert(decoded[0].id == 1) + }) + } catch { + XCTFail("Probably couldn't decode to public galaxy: \(error.localizedDescription)") + } + } + + func testPostParents() throws { + app.crud(register: Planet.self) { (controller) in + controller.crud(parent: \.$galaxy) + } + + do { + try app.testable().test(.POST, "/planet", closure: { (resp) in + XCTAssert(resp.status == .ok) + guard let bodyBuffer = resp.body.buffer else { XCTFail(); return } + + let decoded = try JSONDecoder().decode([Planet].self, from: bodyBuffer) + + XCTAssert(decoded.count == 1) + XCTAssert(decoded[0].name == "Earth") + XCTAssert(decoded[0].id == 1) + }) + + try app.testable().test(.POST, "/planet/1/galaxy", closure: { (resp) in + XCTAssert(resp.status == .ok) + guard let bodyBuffer = resp.body.buffer else { XCTFail(); return } + + let decoded = try JSONDecoder().decode(Galaxy.self, from: bodyBuffer) + + XCTAssert(decoded.name == "Milky Way") + XCTAssert(decoded.id == 1) + }) + } catch { + XCTFail("Probably couldn't decode to public galaxy: \(error.localizedDescription)") + } + } + + func testPostSiblings() throws { + app.crud(register: Planet.self) { (controller) in + controller.crud(siblings: \.$tags) + } + + app.crud(register: Tag.self) { controller in + controller.crud(siblings: \.$planets) + } + + do { + try app.testable().test(.POST, "/planet", closure: { (resp) in + XCTAssert(resp.status == .ok) + guard let bodyBuffer = resp.body.buffer else { XCTFail(); return } + + let decoded = try JSONDecoder().decode([Planet].self, from: bodyBuffer) + + XCTAssert(decoded.count == 1) + XCTAssert(decoded[0].name == "Earth") + XCTAssert(decoded[0].id == 1) + }) + + try app.testable().test(.POST, "/tag", closure: { (resp) in + XCTAssert(resp.status == .ok) + guard let bodyBuffer = resp.body.buffer else { XCTFail(); return } + + let decoded = try JSONDecoder().decode([Tag].self, from: bodyBuffer) + + XCTAssert(decoded.count == 1) + XCTAssert(decoded[0].name == "Life-Supporting") + XCTAssert(decoded[0].id == 1) + }) + + try app.testable().test(.POST, "/planet/1/tag", closure: { (resp) in + XCTAssert(resp.status == .ok) + guard let bodyBuffer = resp.body.buffer else { XCTFail(); return } + + let decoded = try JSONDecoder().decode([Tag].self, from: bodyBuffer) + + XCTAssert(decoded.count == 1) + XCTAssert(decoded[0].name == "Life-Supporting") + XCTAssert(decoded[0].id == 1) + }) + + try app.testable().test(.POST, "/tag/1/planet", closure: { (resp) in + XCTAssert(resp.status == .ok) + guard let bodyBuffer = resp.body.buffer else { XCTFail(); return } + + let decoded = try JSONDecoder().decode([Planet].self, from: bodyBuffer) + + XCTAssert(decoded.count == 1) + XCTAssert(decoded[0].name == "Earth") + XCTAssert(decoded[0].id == 1) + }) + } catch { + XCTFail("Probably couldn't decode to public galaxy: \(error.localizedDescription)") + } + } + + private func configure(_ app: Application) throws { + // Serves files from `Public/` directory + // app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory)) + + // Configure SQLite database + app.databases.use(.sqlite(), as: .sqlite) + + // Configure migrations + app.migrations.add(GalaxyMigration()) + app.migrations.add(PlanetMigration()) + app.migrations.add(PlanetTagMigration()) + app.migrations.add(TagMigration()) + + app.migrations.add(BaseGalaxySeeding()) + app.migrations.add(ChildSeeding()) + app.migrations.add(SiblingSeeding()) + } + + static var allTests = [ + ("testPublicable", testPostBase), + ] +} + From 5f1b88bac379bb42f2c69b6fec618051c6fbd1f9 Mon Sep 17 00:00:00 2001 From: twof Date: Sat, 4 Jan 2020 18:43:36 -0800 Subject: [PATCH 18/38] Post children and base succeeding --- .../CrudRouteGetResponseTests.swift | 2 +- .../CrudRoutePostResponseTests.swift | 69 ++++++++++++++----- 2 files changed, 52 insertions(+), 19 deletions(-) diff --git a/Tests/CrudRouterTests/CrudRouteGetResponseTests.swift b/Tests/CrudRouterTests/CrudRouteGetResponseTests.swift index 7ac3ce4..26ae352 100644 --- a/Tests/CrudRouterTests/CrudRouteGetResponseTests.swift +++ b/Tests/CrudRouterTests/CrudRouteGetResponseTests.swift @@ -37,7 +37,7 @@ final class CrudRouteGetResponseTests: XCTestCase { guard let bodyBuffer = resp.body.buffer else { XCTFail(); return } let decoded = try JSONDecoder().decode([Galaxy].self, from: bodyBuffer) - print(decoded) + XCTAssert(decoded.count == 1) XCTAssert(decoded[0].name == "Milky Way") XCTAssert(decoded[0].id == 1) diff --git a/Tests/CrudRouterTests/CrudRoutePostResponseTests.swift b/Tests/CrudRouterTests/CrudRoutePostResponseTests.swift index 8a7fee6..6445b3a 100644 --- a/Tests/CrudRouterTests/CrudRoutePostResponseTests.swift +++ b/Tests/CrudRouterTests/CrudRoutePostResponseTests.swift @@ -32,15 +32,26 @@ final class CrudRoutePostResponseTests: XCTestCase { app.crud(register: Galaxy.self) do { - try app.testable().test(.POST, "/galaxy", closure: { (resp) in - XCTAssert(resp.status == .ok) + let newGalaxy = Galaxy(name: "Andromeda") + + try app.testable().test(.POST, "/galaxy", json: newGalaxy, closure: { (resp) in + XCTAssertEqual(resp.status, .ok) + guard let bodyBuffer = resp.body.buffer else { XCTFail(); return } - let decoded = try JSONDecoder().decode([Galaxy].self, from: bodyBuffer) - print(decoded) - XCTAssert(decoded.count == 1) - XCTAssert(decoded[0].name == "Milky Way") - XCTAssert(decoded[0].id == 1) + let decoded = try JSONDecoder().decode(Galaxy.self, from: bodyBuffer) + + XCTAssertEqual(decoded.name, "Andromeda") + XCTAssertEqual(decoded.id, 2) + + let newGalaxies = try app.db.query(Galaxy.self).all().wait() + + XCTAssertEqual(newGalaxies.count, 2) + XCTAssert(newGalaxies.contains { $0.name == "Andromeda" }) + + let newAndromeda = newGalaxies.first { $0.name == "Andromeda" } + + XCTAssertEqual(newAndromeda?.id, 2) }) } catch { XCTFail("Probably couldn't decode to public galaxy: \(error.localizedDescription)") @@ -53,26 +64,48 @@ final class CrudRoutePostResponseTests: XCTestCase { } do { - try app.testable().test(.POST, "/galaxy", closure: { (resp) in - XCTAssert(resp.status == .ok) + let newGalaxy = Galaxy(name: "Andromeda") + + try app.testable().test(.POST, "/galaxy", json: newGalaxy, closure: { (resp) in + XCTAssertEqual(resp.status, .ok) guard let bodyBuffer = resp.body.buffer else { XCTFail(); return } - let decoded = try JSONDecoder().decode([Galaxy].self, from: bodyBuffer) + let decoded = try JSONDecoder().decode(Galaxy.self, from: bodyBuffer) - XCTAssert(decoded.count == 1) - XCTAssert(decoded[0].name == "Milky Way") - XCTAssert(decoded[0].id == 1) + XCTAssertEqual(decoded.name, "Andromeda") + XCTAssertEqual(decoded.id, 2) + + let newGalaxies = try app.db.query(Galaxy.self).all().wait() + + XCTAssertEqual(newGalaxies.count, 2) + XCTAssert(newGalaxies.contains { $0.name == "Andromeda" }) + + let newAndromeda = newGalaxies.first { $0.name == "Andromeda" } + + XCTAssertEqual(newAndromeda?.id, 2) }) - try app.testable().test(.POST, "/galaxy/1/planet", closure: { (resp) in + let newPlanet = Planet(name: "Mars", galaxyID: 2) + + try app.testable().test(.POST, "/galaxy/1/planet", json: newPlanet, closure: { (resp) in XCTAssert(resp.status == .ok) guard let bodyBuffer = resp.body.buffer else { XCTFail(); return } - let decoded = try JSONDecoder().decode([Planet].self, from: bodyBuffer) + let decoded = try JSONDecoder().decode(Planet.self, from: bodyBuffer) - XCTAssert(decoded.count == 1) - XCTAssert(decoded[0].name == "Earth") - XCTAssert(decoded[0].id == 1) + XCTAssertEqual(decoded.name, "Mars") + XCTAssertEqual(decoded.id, 2) + XCTAssertEqual(decoded.$galaxy.id, 2) + + + let newPlanets = try app.db.query(Planet.self).all().wait() + + XCTAssertEqual(newPlanets.count, 2) + XCTAssert(newPlanets.contains { $0.name == "Mars" }) + + let newMars = newPlanets.first { $0.name == "Mars" } + + XCTAssertEqual(newMars?.id, 2) }) } catch { XCTFail("Probably couldn't decode to public galaxy: \(error.localizedDescription)") From 55dbf1b4b2a918a4f662f902e3e3ebae2b2606a2 Mon Sep 17 00:00:00 2001 From: twof Date: Sat, 11 Jan 2020 20:25:14 -0800 Subject: [PATCH 19/38] post tests passing --- .../CrudRouter/CrudSiblingsController.swift | 43 +++--- .../CrudRoutePostResponseTests.swift | 122 ++++++++++++------ 2 files changed, 103 insertions(+), 62 deletions(-) diff --git a/Sources/CrudRouter/CrudSiblingsController.swift b/Sources/CrudRouter/CrudSiblingsController.swift index 9f449c0..a3dd5d5 100644 --- a/Sources/CrudRouter/CrudSiblingsController.swift +++ b/Sources/CrudRouter/CrudSiblingsController.swift @@ -52,32 +52,33 @@ extension CrudSiblingsController: RouteCollection {} // } //} // -//public extension CrudSiblingsController where +public extension CrudSiblingsController +// where // ThroughType.Left == ParentType, // ThroughType.Right == ChildType -//{ +{ + func boot(routes router: RoutesBuilder) throws { + let parentPath = self.path + let parentIdPath = self.path.appending(.parameter("\(ParentType.schema)ID")) + + self.activeMethods.forEach { + $0.register( + router: router, + controller: self, + path: parentPath, + idPath: parentIdPath + ) + } + } +} + +//public extension CrudSiblingsController { // func boot(routes router: RoutesBuilder) throws { // let parentPath = self.path // let parentIdPath = self.path.appending(.parameter("\(ParentType.schema)ID")) // -// self.activeMethods.forEach { -// $0.register( -// router: router, -// controller: self, -// path: parentPath, -// idPath: parentIdPath -// ) -// } +// router.on(.GET, parentIdPath, use: self.index) +// router.on(.GET, parentPath, use: self.indexAll) +// router.put(parentIdPath, use: self.update) // } //} - -public extension CrudSiblingsController { - func boot(routes router: RoutesBuilder) throws { - let parentPath = self.path - let parentIdPath = self.path.appending(.parameter("\(ParentType.schema)ID")) - - router.on(.GET, parentIdPath, use: self.index) - router.on(.GET, parentPath, use: self.indexAll) - router.put(parentIdPath, use: self.update) - } -} diff --git a/Tests/CrudRouterTests/CrudRoutePostResponseTests.swift b/Tests/CrudRouterTests/CrudRoutePostResponseTests.swift index 6445b3a..a75cfff 100644 --- a/Tests/CrudRouterTests/CrudRoutePostResponseTests.swift +++ b/Tests/CrudRouterTests/CrudRoutePostResponseTests.swift @@ -88,7 +88,7 @@ final class CrudRoutePostResponseTests: XCTestCase { let newPlanet = Planet(name: "Mars", galaxyID: 2) try app.testable().test(.POST, "/galaxy/1/planet", json: newPlanet, closure: { (resp) in - XCTAssert(resp.status == .ok) + XCTAssertEqual(resp.status, .ok) guard let bodyBuffer = resp.body.buffer else { XCTFail(); return } let decoded = try JSONDecoder().decode(Planet.self, from: bodyBuffer) @@ -118,25 +118,33 @@ final class CrudRoutePostResponseTests: XCTestCase { } do { - try app.testable().test(.POST, "/planet", closure: { (resp) in - XCTAssert(resp.status == .ok) + let newPlanet = Planet(name: "Mars", galaxyID: 2) + + try app.testable().test(.POST, "/planet", json: newPlanet, closure: { (resp) in + XCTAssertEqual(resp.status, .ok) guard let bodyBuffer = resp.body.buffer else { XCTFail(); return } - - let decoded = try JSONDecoder().decode([Planet].self, from: bodyBuffer) - - XCTAssert(decoded.count == 1) - XCTAssert(decoded[0].name == "Earth") - XCTAssert(decoded[0].id == 1) + + let decoded = try JSONDecoder().decode(Planet.self, from: bodyBuffer) + + XCTAssertEqual(decoded.name, "Mars") + XCTAssertEqual(decoded.id, 2) + XCTAssertEqual(decoded.$galaxy.id, 2) + + + let newPlanets = try app.db.query(Planet.self).all().wait() + + XCTAssertEqual(newPlanets.count, 2) + XCTAssert(newPlanets.contains { $0.name == "Mars" }) + + let newMars = newPlanets.first { $0.name == "Mars" } + + XCTAssertEqual(newMars?.id, 2) }) - try app.testable().test(.POST, "/planet/1/galaxy", closure: { (resp) in - XCTAssert(resp.status == .ok) - guard let bodyBuffer = resp.body.buffer else { XCTFail(); return } - - let decoded = try JSONDecoder().decode(Galaxy.self, from: bodyBuffer) - - XCTAssert(decoded.name == "Milky Way") - XCTAssert(decoded.id == 1) + let newGalaxy = Galaxy(name: "Andromeda") + + try app.testable().test(.POST, "/planet/1/galaxy", json: newGalaxy, closure: { (resp) in + XCTAssertEqual(resp.status, .notFound) }) } catch { XCTFail("Probably couldn't decode to public galaxy: \(error.localizedDescription)") @@ -153,48 +161,80 @@ final class CrudRoutePostResponseTests: XCTestCase { } do { - try app.testable().test(.POST, "/planet", closure: { (resp) in - XCTAssert(resp.status == .ok) + let newPlanet = Planet(name: "Mars", galaxyID: 2) + + try app.testable().test(.POST, "/planet", json: newPlanet, closure: { (resp) in + XCTAssertEqual(resp.status, .ok) guard let bodyBuffer = resp.body.buffer else { XCTFail(); return } - let decoded = try JSONDecoder().decode([Planet].self, from: bodyBuffer) + let decoded = try JSONDecoder().decode(Planet.self, from: bodyBuffer) + + XCTAssertEqual(decoded.name, "Mars") + XCTAssertEqual(decoded.id, 2) + XCTAssertEqual(decoded.$galaxy.id, 2) + + let newPlanets = try app.db.query(Planet.self).all().wait() + + XCTAssertEqual(newPlanets.count, 2) + XCTAssert(newPlanets.contains { $0.name == "Mars" }) + + let newMars = newPlanets.first { $0.name == "Mars" } - XCTAssert(decoded.count == 1) - XCTAssert(decoded[0].name == "Earth") - XCTAssert(decoded[0].id == 1) + XCTAssertEqual(newMars?.id, 2) }) - try app.testable().test(.POST, "/tag", closure: { (resp) in - XCTAssert(resp.status == .ok) + let newTag = Tag(id: 2, name: "Red") + + try app.testable().test(.POST, "/tag", json: newTag, closure: { (resp) in + XCTAssertEqual(resp.status, .ok) guard let bodyBuffer = resp.body.buffer else { XCTFail(); return } - let decoded = try JSONDecoder().decode([Tag].self, from: bodyBuffer) + let decoded = try JSONDecoder().decode(Tag.self, from: bodyBuffer) - XCTAssert(decoded.count == 1) - XCTAssert(decoded[0].name == "Life-Supporting") - XCTAssert(decoded[0].id == 1) + XCTAssertEqual(decoded.name, "Red") + XCTAssertEqual(decoded.id, 2) + + let newTags = try app.db.query(Tag.self).all().wait() + + XCTAssertEqual(newTags.count, 2) + XCTAssert(newTags.contains { $0.name == "Red" }) + + let newMarsTag = newTags.first { $0.name == "Red" } + + XCTAssertEqual(newMarsTag?.id, 2) }) - try app.testable().test(.POST, "/planet/1/tag", closure: { (resp) in - XCTAssert(resp.status == .ok) + let otherNewTag = Tag(id: 3, name: "Uninhabitable") + + try app.testable().test(.POST, "/planet/2/tag", json: otherNewTag, closure: { (resp) in + XCTAssertEqual(resp.status, .ok) guard let bodyBuffer = resp.body.buffer else { XCTFail(); return } - let decoded = try JSONDecoder().decode([Tag].self, from: bodyBuffer) + let decoded = try JSONDecoder().decode(Tag.self, from: bodyBuffer) + + XCTAssertEqual(decoded.name, "Uninhabitable") + XCTAssertEqual(decoded.id, 3) - XCTAssert(decoded.count == 1) - XCTAssert(decoded[0].name == "Life-Supporting") - XCTAssert(decoded[0].id == 1) + let newTags = try app.db.query(Tag.self).all().wait() + + XCTAssertEqual(newTags.count, 2) + XCTAssert(newTags.contains { $0.name == "Red" }) + + let newMarsTag = newTags.first { $0.name == "Red" } + + XCTAssertEqual(newMarsTag?.id, 2) }) - try app.testable().test(.POST, "/tag/1/planet", closure: { (resp) in - XCTAssert(resp.status == .ok) + let otherNewPlanet = Planet(id: 3, name: "Venus", galaxyID: 2) + + try app.testable().test(.POST, "/tag/2/planet", json: otherNewPlanet, closure: { (resp) in + XCTAssertEqual(resp.status, .ok) guard let bodyBuffer = resp.body.buffer else { XCTFail(); return } - let decoded = try JSONDecoder().decode([Planet].self, from: bodyBuffer) + let decoded = try JSONDecoder().decode(Planet.self, from: bodyBuffer) - XCTAssert(decoded.count == 1) - XCTAssert(decoded[0].name == "Earth") - XCTAssert(decoded[0].id == 1) + XCTAssertEqual(decoded.name, "Venus") + XCTAssertEqual(decoded.id, 3) }) } catch { XCTFail("Probably couldn't decode to public galaxy: \(error.localizedDescription)") From 220842d5d89db581b17463037406d44f6af2fa35 Mon Sep 17 00:00:00 2001 From: twof Date: Sat, 11 Jan 2020 23:17:55 -0800 Subject: [PATCH 20/38] start of put tests --- .../CrudRoutePutResponseTests.swift | 266 ++++++++++++++++++ 1 file changed, 266 insertions(+) create mode 100644 Tests/CrudRouterTests/CrudRoutePutResponseTests.swift diff --git a/Tests/CrudRouterTests/CrudRoutePutResponseTests.swift b/Tests/CrudRouterTests/CrudRoutePutResponseTests.swift new file mode 100644 index 0000000..46ef17c --- /dev/null +++ b/Tests/CrudRouterTests/CrudRoutePutResponseTests.swift @@ -0,0 +1,266 @@ +// +// CrudRoutePostResponseTests.swift +// AsyncHTTPClient +// +// Created by fnord on 1/4/20. +// + +import FluentSQLiteDriver +import Fluent +import XCTVapor + +final class CrudRoutePutResponseTests: XCTestCase { + var app: Application! + + override func setUp() { + super.setUp() + + app = Application() + try! configure(app) + + try! app.migrator.setupIfNeeded().wait() + try! app.migrator.prepareBatch().wait() + } + + override func tearDown() { + super.tearDown() + + try! app.migrator.revertAllBatches().wait() + } + + func testPutBase() throws { + app.crud(register: Galaxy.self) + + do { + let existingGalaxy = Galaxy(id: 1, name: "Milky Way 2") + + try app.testable().test(.PUT, "/galaxy/1", json: existingGalaxy, closure: { (resp) in + XCTAssertEqual(resp.status, .ok) + + guard let bodyBuffer = resp.body.buffer else { XCTFail(); return } + + let decoded = try JSONDecoder().decode(Galaxy.self, from: bodyBuffer) + + XCTAssertEqual(decoded.name, "Milky Way 2") + XCTAssertEqual(decoded.id, 1) + + let newGalaxies = try app.db.query(Galaxy.self).all().wait() + + XCTAssertEqual(newGalaxies.count, 1) + XCTAssert(newGalaxies.contains { $0.name == "Milky Way 2" }) + + let newMilkyWay = newGalaxies.first { $0.name == "Milky Way 2" } + + XCTAssertEqual(newMilkyWay?.id, 1) + }) + } catch { + XCTFail("Probably couldn't decode to public galaxy: \(error.localizedDescription)") + } + } + + func testPutChildren() throws { + app.crud(register: Galaxy.self) { (controller) in + controller.crud(children: \.$planets) + } + + do { + let existingGalaxy = Galaxy(id: 1, name: "Milky Way 2") + + try app.testable().test(.PUT, "/galaxy/1", json: existingGalaxy, closure: { (resp) in + XCTAssertEqual(resp.status, .ok) + guard let bodyBuffer = resp.body.buffer else { XCTFail(); return } + + let decoded = try JSONDecoder().decode(Galaxy.self, from: bodyBuffer) + + XCTAssertEqual(decoded.name, "Milky Way 2") + XCTAssertEqual(decoded.id, 1) + + let newGalaxies = try app.db.query(Galaxy.self).all().wait() + + XCTAssertEqual(newGalaxies.count, 1) + XCTAssert(newGalaxies.contains { $0.name == "Milky Way 2" }) + + let newAndromeda = newGalaxies.first { $0.name == "Milky Way 2" } + + XCTAssertEqual(newAndromeda?.id, 1) + }) + + let existingPlanet = Planet(name: "Earth 2", galaxyID: 1) + + try app.testable().test(.PUT, "/galaxy/1/planet/", json: existingPlanet, closure: { (resp) in + XCTAssertEqual(resp.status, .ok) + guard let bodyBuffer = resp.body.buffer else { XCTFail(); return } + + let decoded = try JSONDecoder().decode(Planet.self, from: bodyBuffer) + + XCTAssertEqual(decoded.name, "Earth 2") + XCTAssertEqual(decoded.id, 1) + XCTAssertEqual(decoded.$galaxy.id, 1) + + + let newPlanets = try app.db.query(Planet.self).all().wait() + + XCTAssertEqual(newPlanets.count, 2) + XCTAssert(newPlanets.contains { $0.name == "Earth 2" }) + + let newEarth = newPlanets.first { $0.name == "Earth 2" } + + XCTAssertEqual(newEarth?.id, 1) + }) + } catch { + XCTFail("Probably couldn't decode to public galaxy: \(error.localizedDescription)") + } + } + + func testPutParents() throws { + app.crud(register: Planet.self) { (controller) in + controller.crud(parent: \.$galaxy) + } + + do { + let existingPlanet = Planet(name: "Earth 2", galaxyID: 1) + + try app.testable().test(.PUT, "/planet/1", json: existingPlanet, closure: { (resp) in + XCTAssertEqual(resp.status, .ok) + guard let bodyBuffer = resp.body.buffer else { XCTFail(); return } + + let decoded = try JSONDecoder().decode(Planet.self, from: bodyBuffer) + + XCTAssertEqual(decoded.name, "Earth 2") + XCTAssertEqual(decoded.id, 1) + XCTAssertEqual(decoded.$galaxy.id, 1) + + + let newPlanets = try app.db.query(Planet.self).all().wait() + + XCTAssertEqual(newPlanets.count, 1) + XCTAssert(newPlanets.contains { $0.name == "Earth 2" }) + + let newMars = newPlanets.first { $0.name == "Earth 2" } + + XCTAssertEqual(newMars?.id, 1) + }) + + let existingGalaxy = Galaxy(id: 1, name: "Milky Way 2") + + try app.testable().test(.PUT, "/planet/1/galaxy", json: existingGalaxy, closure: { (resp) in + XCTAssertEqual(resp.status, .notFound) + }) + } catch { + XCTFail("Probably couldn't decode to public galaxy: \(error.localizedDescription)") + } + } + + func testPutSiblings() throws { + app.crud(register: Planet.self) { (controller) in + controller.crud(siblings: \.$tags) + } + + app.crud(register: Tag.self) { controller in + controller.crud(siblings: \.$planets) + } + + do { + let existingPlanet = Planet(name: "Earth 2", galaxyID: 1) + + try app.testable().test(.PUT, "/planet/1", json: existingPlanet, closure: { (resp) in + XCTAssertEqual(resp.status, .ok) + guard let bodyBuffer = resp.body.buffer else { XCTFail(); return } + + let decoded = try JSONDecoder().decode(Planet.self, from: bodyBuffer) + + XCTAssertEqual(decoded.name, "Earth 2") + XCTAssertEqual(decoded.id, 1) + XCTAssertEqual(decoded.$galaxy.id, 1) + + let newPlanets = try app.db.query(Planet.self).all().wait() + + XCTAssertEqual(newPlanets.count, 1) + XCTAssert(newPlanets.contains { $0.name == "Earth 2" }) + + let newMars = newPlanets.first { $0.name == "Earth 2" } + + XCTAssertEqual(newMars?.id, 1) + }) + + let existingTag = Tag(id: 1, name: "Kind of Life Supporting") + + try app.testable().test(.PUT, "/tag/1", json: existingTag, closure: { (resp) in + XCTAssertEqual(resp.status, .ok) + guard let bodyBuffer = resp.body.buffer else { XCTFail(); return } + + let decoded = try JSONDecoder().decode(Tag.self, from: bodyBuffer) + + XCTAssertEqual(decoded.name, "Kind of Life Supporting") + XCTAssertEqual(decoded.id, 1) + + let newTags = try app.db.query(Tag.self).all().wait() + + XCTAssertEqual(newTags.count, 1) + XCTAssert(newTags.contains { $0.name == "Kind of Life Supporting" }) + + let newMarsTag = newTags.first { $0.name == "Kind of Life Supporting" } + + XCTAssertEqual(newMarsTag?.id, 1) + }) + + let otherExistingTag = Tag(id: 3, name: "Sort of Life Supporting") + + try app.testable().test(.PUT, "/planet/1/tag/1", json: otherExistingTag, closure: { (resp) in + XCTAssertEqual(resp.status, .ok) + guard let bodyBuffer = resp.body.buffer else { XCTFail(); return } + + let decoded = try JSONDecoder().decode(Tag.self, from: bodyBuffer) + + XCTAssertEqual(decoded.name, "Sort of Life Supporting") + XCTAssertEqual(decoded.id, 1) + + let newTags = try app.db.query(Tag.self).all().wait() + + XCTAssertEqual(newTags.count, 1) + XCTAssert(newTags.contains { $0.name == "Sort of Life Supporting" }) + + let newEarthTag = newTags.first { $0.name == "Sort of Life Supporting" } + + XCTAssertEqual(newEarthTag?.id, 1) + }) + + let otherExistingPlanet = Planet(name: "Earth 3", galaxyID: 1) + + try app.testable().test(.PUT, "/tag/1/planet/1", json: otherExistingPlanet, closure: { (resp) in + XCTAssertEqual(resp.status, .ok) + guard let bodyBuffer = resp.body.buffer else { XCTFail(); return } + + let decoded = try JSONDecoder().decode(Planet.self, from: bodyBuffer) + + XCTAssertEqual(decoded.name, "Earth") + XCTAssertEqual(decoded.id, 1) + }) + } catch { + XCTFail("Probably couldn't decode to public galaxy: \(error.localizedDescription)") + } + } + + private func configure(_ app: Application) throws { + // Serves files from `Public/` directory + // app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory)) + + // Configure SQLite database + app.databases.use(.sqlite(), as: .sqlite) + + // Configure migrations + app.migrations.add(GalaxyMigration()) + app.migrations.add(PlanetMigration()) + app.migrations.add(PlanetTagMigration()) + app.migrations.add(TagMigration()) + + app.migrations.add(BaseGalaxySeeding()) + app.migrations.add(ChildSeeding()) + app.migrations.add(SiblingSeeding()) + } + + static var allTests = [ + ("testPublicable", testPutBase), + ] +} + From dcbc1ec489f58685a323c5af9bc6a4b1cda211c2 Mon Sep 17 00:00:00 2001 From: twof Date: Sun, 21 Nov 2021 11:05:01 -0800 Subject: [PATCH 21/38] first method upgraded to async await --- Package.resolved | 112 ++++++++++-------- Package.swift | 21 ++-- .../CrudChildrenControllerProtocol.swift | 35 +++--- .../CrudParentControllerProtocol.swift | 2 +- .../CrudSiblingsControllerProtocol.swift | 4 +- .../ControllerProtocols/Crudable.swift | 12 +- .../CrudRouter/CrudChildrenController.swift | 4 +- Sources/CrudRouter/CrudParentController.swift | 4 +- .../CrudRouter/CrudSiblingsController.swift | 4 +- Tests/CrudRouterTests/TestModels/Galaxy.swift | 2 +- 10 files changed, 110 insertions(+), 90 deletions(-) diff --git a/Package.resolved b/Package.resolved index 1a08d7d..030cf6b 100644 --- a/Package.resolved +++ b/Package.resolved @@ -6,8 +6,8 @@ "repositoryURL": "https://github.com/swift-server/async-http-client.git", "state": { "branch": null, - "revision": "48e284d1ea6d0e8baac1af1c4ad8bd298670caf6", - "version": "1.0.1" + "revision": "ec2e080d7011a81bd67f10bf41efe6104d7799d6", + "version": "1.7.0" } }, { @@ -15,8 +15,8 @@ "repositoryURL": "https://github.com/vapor/async-kit.git", "state": { "branch": null, - "revision": "cb8e6ee62dc6684a95132f8d46511956e78ee0f1", - "version": "1.0.0-beta.2" + "revision": "748c026f4dc93c0b9d05fe43a07d3922ca126744", + "version": "1.10.0" } }, { @@ -24,8 +24,8 @@ "repositoryURL": "https://github.com/vapor/console-kit.git", "state": { "branch": null, - "revision": "535874e4654b17cebb422b9e17353e85d421a118", - "version": "4.0.0-beta.2" + "revision": "75ea3b627d88221440b878e5dfccc73fd06842ed", + "version": "4.2.7" } }, { @@ -33,8 +33,8 @@ "repositoryURL": "https://github.com/vapor/fluent.git", "state": { "branch": null, - "revision": "5adacb3bd3e073fb2142b7713acce8992e3aadae", - "version": "4.0.0-beta.2" + "revision": "ea707ee318066a073c95b2b2df1aa640fcb67f9e", + "version": "4.4.0" } }, { @@ -42,8 +42,8 @@ "repositoryURL": "https://github.com/vapor/fluent-kit.git", "state": { "branch": null, - "revision": "c232981184891cb762f041d0c70f96b254d97a78", - "version": "1.0.0-beta.2.5" + "revision": "4dc196da17177d0099510c6a3e8b6db0e33466b5", + "version": "1.16.2" } }, { @@ -51,8 +51,8 @@ "repositoryURL": "https://github.com/vapor/fluent-sqlite-driver.git", "state": { "branch": null, - "revision": "d2bb65b333a046e8ed64053312a6d6bdeb3a3590", - "version": "4.0.0-beta.2" + "revision": "9ca34be792979fb0f1dbd8e45b8af9f1e1440474", + "version": "4.1.0" } }, { @@ -60,17 +60,8 @@ "repositoryURL": "https://github.com/vapor/multipart-kit.git", "state": { "branch": null, - "revision": "b41a49b5756ac3fbf8f2b07228a7e75f9b60731a", - "version": "4.0.0-beta.2" - } - }, - { - "package": "open-crypto", - "repositoryURL": "https://github.com/vapor/open-crypto.git", - "state": { - "branch": null, - "revision": "90c49bc68ee6d992fa13cf84ca8fc54b97eaf4cc", - "version": "4.0.0-beta.2" + "revision": "2dd9368a3c9580792b77c7ef364f3735909d9996", + "version": "4.5.1" } }, { @@ -78,8 +69,8 @@ "repositoryURL": "https://github.com/vapor/routing-kit.git", "state": { "branch": null, - "revision": "6a8a1636ad26494b03f3c72d74a420fc3a44949c", - "version": "4.0.0-beta.3" + "revision": "a0801a36a6ad501d5ad6285cbcd4774de6b0a734", + "version": "4.3.0" } }, { @@ -87,8 +78,8 @@ "repositoryURL": "https://github.com/vapor/sql-kit.git", "state": { "branch": null, - "revision": "d09b5527fb0c341f6993e4f5233fadbb7dee1c76", - "version": "3.0.0-beta.3" + "revision": "dff8aba9973f2edd7ddb1d29d8d6994a516cf3c1", + "version": "3.13.0" } }, { @@ -96,8 +87,8 @@ "repositoryURL": "https://github.com/vapor/sqlite-kit.git", "state": { "branch": null, - "revision": "9f3a7fb91c983b943edd239351ed9cd0cb75eda1", - "version": "4.0.0-beta.3" + "revision": "2ec279b9c845cec254646834b66338551a024561", + "version": "4.0.2" } }, { @@ -105,8 +96,26 @@ "repositoryURL": "https://github.com/vapor/sqlite-nio.git", "state": { "branch": null, - "revision": "1bf9748cc62cd96d440a712a06cb301b66a27550", - "version": "1.0.0-beta.2.1" + "revision": "6481dd0b01112d082dd7eb362782126e81964138", + "version": "1.1.0" + } + }, + { + "package": "swift-backtrace", + "repositoryURL": "https://github.com/swift-server/swift-backtrace.git", + "state": { + "branch": null, + "revision": "d3e04a9d4b3833363fb6192065b763310b156d54", + "version": "1.3.1" + } + }, + { + "package": "swift-crypto", + "repositoryURL": "https://github.com/apple/swift-crypto.git", + "state": { + "branch": null, + "revision": "9b5ef28601a9c745c9cdb54d3f243e28ac830982", + "version": "2.0.1" } }, { @@ -114,8 +123,8 @@ "repositoryURL": "https://github.com/apple/swift-log.git", "state": { "branch": null, - "revision": "74d7b91ceebc85daf387ebb206003f78813f71aa", - "version": "1.2.0" + "revision": "5d66f7ba25daf4f94100e7022febf3c75e37a6c7", + "version": "1.4.2" } }, { @@ -123,8 +132,8 @@ "repositoryURL": "https://github.com/apple/swift-metrics.git", "state": { "branch": null, - "revision": "3fefedaaef285830cc98ae80231140122076a7e0", - "version": "1.2.0" + "revision": "3edd2f57afc4e68e23c3e4956bc8b65ca6b5b2ff", + "version": "2.2.0" } }, { @@ -132,8 +141,8 @@ "repositoryURL": "https://github.com/apple/swift-nio.git", "state": { "branch": null, - "revision": "f6487a11d80bfb9a0a0a752b7442847c7e3a8253", - "version": "2.12.0" + "revision": "addf69cfe60376c325397c8926589415576b1dd1", + "version": "2.34.0" } }, { @@ -141,8 +150,8 @@ "repositoryURL": "https://github.com/apple/swift-nio-extras.git", "state": { "branch": null, - "revision": "53808818c2015c45247cad74dc05c7a032c96a2f", - "version": "1.3.2" + "revision": "f73ca5ee9c6806800243f1ac415fcf82de9a4c91", + "version": "1.10.2" } }, { @@ -150,8 +159,8 @@ "repositoryURL": "https://github.com/apple/swift-nio-http2.git", "state": { "branch": null, - "revision": "c1bfb7ce3f201e41ff60ef38fa63e67e0eb66a24", - "version": "1.9.0" + "revision": "326f7f9a8c8c8402e3691adac04911cac9f9d87f", + "version": "1.18.4" } }, { @@ -159,8 +168,17 @@ "repositoryURL": "https://github.com/apple/swift-nio-ssl.git", "state": { "branch": null, - "revision": "b75ffaba05b2cffdb1420d558f1a90b4e6c46dcc", - "version": "2.5.0" + "revision": "36f6419f2b1b6490a8c0faa840298e28027cefe9", + "version": "2.16.3" + } + }, + { + "package": "swift-nio-transport-services", + "repositoryURL": "https://github.com/apple/swift-nio-transport-services.git", + "state": { + "branch": null, + "revision": "e7f5278a26442dc46783ba7e063643d524e414a0", + "version": "1.11.3" } }, { @@ -168,8 +186,8 @@ "repositoryURL": "https://github.com/vapor/vapor.git", "state": { "branch": null, - "revision": "88bc369b95036fc02394f045efe0a36af3bfccdb", - "version": "4.0.0-beta.3" + "revision": "6a5a3b5244d39e2614382c77ddf62e63b712ad06", + "version": "4.53.0" } }, { @@ -177,8 +195,8 @@ "repositoryURL": "https://github.com/vapor/websocket-kit.git", "state": { "branch": null, - "revision": "4c9da4fe7d8186243418254031288ab5ee0202e4", - "version": "2.0.0-beta.2.1" + "revision": "b1c4df8f6c848c2e977726903bbe6578eed723ad", + "version": "2.2.0" } } ] diff --git a/Package.swift b/Package.swift index 6c8f7e7..3339f70 100644 --- a/Package.swift +++ b/Package.swift @@ -1,21 +1,28 @@ -// swift-tools-version:5.1 +// swift-tools-version:5.5 import PackageDescription let package = Package( name: "CrudRouter", platforms: [ - .macOS(.v10_14) + .macOS(.v12) ], products: [ .library(name: "CrudRouter", targets: ["CrudRouter"]), ], dependencies: [ - .package(url: "https://github.com/vapor/vapor.git", from: "4.0.0-beta.3"), - .package(url: "https://github.com/vapor/fluent.git", from: "4.0.0-beta.2"), - .package(url: "https://github.com/vapor/fluent-sqlite-driver.git", from: "4.0.0-beta.2"), + .package(url: "https://github.com/vapor/vapor.git", from: "4.53.0"), + .package(url: "https://github.com/vapor/fluent.git", from: "4.4.0"), + .package(url: "https://github.com/vapor/fluent-sqlite-driver.git", from: "4.1.0"), ], targets: [ - .target(name: "CrudRouter", dependencies: ["Fluent", "FluentSQLiteDriver", "Vapor"]), - .testTarget(name: "CrudRouterTests", dependencies: ["CrudRouter", "FluentSQLiteDriver", "XCTVapor"]), + .target(name: "CrudRouter", dependencies: [ + .product(name: "Vapor", package: "vapor"), + .product(name: "Fluent", package: "fluent"), + .product(name: "FluentSQLiteDriver", package: "fluent-sqlite-driver") + ]), + .testTarget(name: "CrudRouterTests", dependencies: [ + .product(name: "FluentSQLiteDriver", package: "fluent-sqlite-driver"), + .product(name: "XCTVapor", package: "vapor") + ]), ] ) diff --git a/Sources/CrudRouter/ControllerProtocols/CrudChildrenControllerProtocol.swift b/Sources/CrudRouter/ControllerProtocols/CrudChildrenControllerProtocol.swift index 3a4d3f5..da010c3 100644 --- a/Sources/CrudRouter/ControllerProtocols/CrudChildrenControllerProtocol.swift +++ b/Sources/CrudRouter/ControllerProtocols/CrudChildrenControllerProtocol.swift @@ -3,19 +3,17 @@ import FluentKit import Fluent import NIOExtras -extension EventLoopFuture where Value: Model { - func delete(on db: Database) -> EventLoopFuture { - return self.map { model in - return model.delete(on: db) - } - } -} +//extension EventLoopFuture where Value: Model { +// func delete(on db: Database) async -> Void { +// return model.delete(on: db) +// } +//} public protocol CrudChildrenControllerProtocol { associatedtype ParentType: Model & Content where ParentType.IDValue: LosslessStringConvertible associatedtype ChildType: Model & Content where ChildType.IDValue: LosslessStringConvertible - var children: KeyPath> { get } + var children: KeyPath> { get } func index(_ req: Request) throws -> EventLoopFuture func indexAll(_ req: Request) throws -> EventLoopFuture<[ChildType]> @@ -82,19 +80,16 @@ public extension CrudChildrenControllerProtocol { } } - func delete(_ req: Request) throws -> EventLoopFuture { + func delete(_ req: Request) async throws -> HTTPStatus { let parentId = try req.getId(modelType: ParentType.self) - - return try ParentType - .find(parentId, on: req.db) - .unwrap(or: Abort(.notFound)) - .throwingFlatMap { parent -> EventLoopFuture in - return try parent[keyPath: self.children] - .query(on: req.db) - .first() - .unwrap(or: Abort(.notFound)) - .delete(on: req.db) - .transform(to: HTTPStatus.ok) + guard + let parent = await ParentType.find(parentId, on: req.db), + let child = try await parent[keyPath: self.children].query(on: req.db).first() + { + throw Abort(.notFound) } + + try await child.delete(on: req.db) + return HTTPStatus.ok } } diff --git a/Sources/CrudRouter/ControllerProtocols/CrudParentControllerProtocol.swift b/Sources/CrudRouter/ControllerProtocols/CrudParentControllerProtocol.swift index 4511a63..4d1e057 100644 --- a/Sources/CrudRouter/ControllerProtocols/CrudParentControllerProtocol.swift +++ b/Sources/CrudRouter/ControllerProtocols/CrudParentControllerProtocol.swift @@ -5,7 +5,7 @@ public protocol CrudParentControllerProtocol { associatedtype ParentType: Model & Content where ParentType.IDValue: LosslessStringConvertible associatedtype ChildType: Model & Content where ChildType.IDValue: LosslessStringConvertible - var relation: KeyPath> { get } + var relation: KeyPath> { get } func index(_ req: Request) throws -> EventLoopFuture func update(_ req: Request) throws -> EventLoopFuture diff --git a/Sources/CrudRouter/ControllerProtocols/CrudSiblingsControllerProtocol.swift b/Sources/CrudRouter/ControllerProtocols/CrudSiblingsControllerProtocol.swift index e9634d1..ed43d20 100644 --- a/Sources/CrudRouter/ControllerProtocols/CrudSiblingsControllerProtocol.swift +++ b/Sources/CrudRouter/ControllerProtocols/CrudSiblingsControllerProtocol.swift @@ -19,7 +19,7 @@ public protocol CrudSiblingsControllerProtocol { associatedtype ChildType: Model & Content where ChildType.IDValue: LosslessStringConvertible associatedtype ThroughType: Model - var siblings: KeyPath> { get } + var siblings: KeyPath> { get } func index(_ req: Request) throws -> EventLoopFuture func indexAll(_ req: Request) throws -> EventLoopFuture<[ChildType]> @@ -57,7 +57,7 @@ public extension CrudSiblingsControllerProtocol { .find(parentId, on: req.db) .unwrap(or: Abort(.notFound)) .throwingFlatMap { parent -> EventLoopFuture in - let siblings: Siblings = parent[keyPath: self.siblings] + let siblings: SiblingsProperty = parent[keyPath: self.siblings] let siblingsQuery = try siblings.query(on: req.db) return siblingsQuery .first() diff --git a/Sources/CrudRouter/ControllerProtocols/Crudable.swift b/Sources/CrudRouter/ControllerProtocols/Crudable.swift index 8b3e60b..edce1db 100644 --- a/Sources/CrudRouter/ControllerProtocols/Crudable.swift +++ b/Sources/CrudRouter/ControllerProtocols/Crudable.swift @@ -6,7 +6,7 @@ public protocol Crudable: ControllerProtocol { func crud( at path: PathComponent..., - parent relation: KeyPath>, + parent relation: KeyPath>, _ either: OnlyExceptEither, relationConfiguration: ((CrudParentController) -> Void)? ) where @@ -16,7 +16,7 @@ public protocol Crudable: ControllerProtocol { func crud( at path: PathComponent..., - children relation: KeyPath>, + children relation: KeyPath>, _ either: OnlyExceptEither, relationConfiguration: ((CrudChildrenController) -> Void)? ) where @@ -25,7 +25,7 @@ public protocol Crudable: ControllerProtocol { func crud( at path: PathComponent..., - siblings relation: KeyPath>, + siblings relation: KeyPath>, _ either: OnlyExceptEither, relationConfiguration: ((CrudSiblingsController) -> Void)? ) where @@ -51,7 +51,7 @@ public protocol Crudable: ControllerProtocol { extension Crudable { public func crud( at path: PathComponent..., - parent relation: KeyPath>, + parent relation: KeyPath>, _ either: OnlyExceptEither = .only([.read, .update]), relationConfiguration: ((CrudParentController) -> Void)?=nil ) where @@ -82,7 +82,7 @@ extension Crudable { public func crud( at path: PathComponent..., - children relation: KeyPath>, + children relation: KeyPath>, _ either: OnlyExceptEither = .only([.read, .readAll, .create, .update, .delete]), relationConfiguration: ((CrudChildrenController) -> Void)?=nil ) where @@ -112,7 +112,7 @@ extension Crudable { public func crud( at path: PathComponent..., - siblings relation: KeyPath>, + siblings relation: KeyPath>, _ either: OnlyExceptEither = .only([.read, .readAll, .create, .update, .delete]), relationConfiguration: ((CrudSiblingsController) -> Void)?=nil ) where diff --git a/Sources/CrudRouter/CrudChildrenController.swift b/Sources/CrudRouter/CrudChildrenController.swift index 28b9ecd..9241eeb 100644 --- a/Sources/CrudRouter/CrudChildrenController.swift +++ b/Sources/CrudRouter/CrudChildrenController.swift @@ -8,12 +8,12 @@ public struct CrudChildrenController> + public var children: KeyPath> public let path: [PathComponent] let activeMethods: Set init( - childrenRelation: KeyPath>, + childrenRelation: KeyPath>, path: [PathComponent], router: RoutesBuilder, activeMethods: Set diff --git a/Sources/CrudRouter/CrudParentController.swift b/Sources/CrudRouter/CrudParentController.swift index 867f1b7..133f9d8 100644 --- a/Sources/CrudRouter/CrudParentController.swift +++ b/Sources/CrudRouter/CrudParentController.swift @@ -7,13 +7,13 @@ public struct CrudParentController> + public let relation: KeyPath> public let path: [PathComponent] public let router: RoutesBuilder let activeMethods: Set init( - relation: KeyPath>, + relation: KeyPath>, path: [PathComponent], router: RoutesBuilder, activeMethods: Set diff --git a/Sources/CrudRouter/CrudSiblingsController.swift b/Sources/CrudRouter/CrudSiblingsController.swift index a3dd5d5..bfba2ae 100644 --- a/Sources/CrudRouter/CrudSiblingsController.swift +++ b/Sources/CrudRouter/CrudSiblingsController.swift @@ -13,13 +13,13 @@ public struct CrudSiblingsController< public typealias ChildType = ChildType public typealias ThroughType = ThroughType - public var siblings: KeyPath> + public var siblings: KeyPath> public let path: [PathComponent] public let router: RoutesBuilder let activeMethods: Set init( - siblingRelation: KeyPath>, + siblingRelation: KeyPath>, path: [PathComponent], router: RoutesBuilder, activeMethods: Set diff --git a/Tests/CrudRouterTests/TestModels/Galaxy.swift b/Tests/CrudRouterTests/TestModels/Galaxy.swift index fafadb1..f385131 100644 --- a/Tests/CrudRouterTests/TestModels/Galaxy.swift +++ b/Tests/CrudRouterTests/TestModels/Galaxy.swift @@ -1,5 +1,5 @@ import Vapor -import FluentSQLiteDriver +import FluentKit import CrudRouter public final class Galaxy: Model, Content { From 43d845fc462f07847a716008dfe23f9b981bbb4c Mon Sep 17 00:00:00 2001 From: twof Date: Sun, 21 Nov 2021 11:26:34 -0800 Subject: [PATCH 22/38] crudcontrollerprotocol updated to async --- .../CrudChildrenControllerProtocol.swift | 46 ++++++++----------- .../CrudControllerProtocol.swift | 42 +++++++++-------- .../ControllerProtocols/Crudable.swift | 4 -- 3 files changed, 42 insertions(+), 50 deletions(-) diff --git a/Sources/CrudRouter/ControllerProtocols/CrudChildrenControllerProtocol.swift b/Sources/CrudRouter/ControllerProtocols/CrudChildrenControllerProtocol.swift index da010c3..6e4d404 100644 --- a/Sources/CrudRouter/ControllerProtocols/CrudChildrenControllerProtocol.swift +++ b/Sources/CrudRouter/ControllerProtocols/CrudChildrenControllerProtocol.swift @@ -3,41 +3,33 @@ import FluentKit import Fluent import NIOExtras -//extension EventLoopFuture where Value: Model { -// func delete(on db: Database) async -> Void { -// return model.delete(on: db) -// } -//} - public protocol CrudChildrenControllerProtocol { associatedtype ParentType: Model & Content where ParentType.IDValue: LosslessStringConvertible associatedtype ChildType: Model & Content where ChildType.IDValue: LosslessStringConvertible var children: KeyPath> { get } - func index(_ req: Request) throws -> EventLoopFuture - func indexAll(_ req: Request) throws -> EventLoopFuture<[ChildType]> - func create(_ req: Request) throws -> EventLoopFuture - func update(_ req: Request) throws -> EventLoopFuture - func delete(_ req: Request) throws -> EventLoopFuture + func index(_ req: Request) async throws -> ChildType + func indexAll(_ req: Request) async throws -> [ChildType] + func create(_ req: Request) async throws -> ChildType + func update(_ req: Request) async throws -> ChildType + func delete(_ req: Request) async throws -> HTTPStatus } public extension CrudChildrenControllerProtocol { - func index(_ req: Request) throws -> EventLoopFuture { + func index(_ req: Request) async throws -> ChildType { let parentId = try req.getId(modelType: ParentType.self) - - return try ParentType - .find(parentId, on: req.db) - .unwrap(or: Abort(.notFound)) - .throwingFlatMap { parent -> EventLoopFuture in - return try parent[keyPath: self.children] - .query(on: req.db) - .first() - .unwrap(or: Abort(.notFound)) - } + guard + let parent = try await ParentType.find(parentId, on: req.db), + let child = try await parent[keyPath: self.children].query(on: req.db).first() + else { + throw Abort(.notFound) + } + + return child } - func indexAll(_ req: Request) throws -> EventLoopFuture<[ChildType]> { + func indexAll(_ req: Request) async throws -> [ChildType] { let parentId = try req.getId(modelType: ParentType.self) return try ParentType .find(parentId, on: req.db) @@ -49,7 +41,7 @@ public extension CrudChildrenControllerProtocol { } } - func create(_ req: Request) throws -> EventLoopFuture { + func create(_ req: Request) async throws -> ChildType { let parentId = try req.getId(modelType: ParentType.self) return try ParentType @@ -61,7 +53,7 @@ public extension CrudChildrenControllerProtocol { } } - func update(_ req: Request) throws -> EventLoopFuture { + func update(_ req: Request) async throws -> ChildType { let parentId = try req.getId(modelType: ParentType.self) return try ParentType @@ -83,9 +75,9 @@ public extension CrudChildrenControllerProtocol { func delete(_ req: Request) async throws -> HTTPStatus { let parentId = try req.getId(modelType: ParentType.self) guard - let parent = await ParentType.find(parentId, on: req.db), + let parent = try await ParentType.find(parentId, on: req.db), let child = try await parent[keyPath: self.children].query(on: req.db).first() - { + else { throw Abort(.notFound) } diff --git a/Sources/CrudRouter/ControllerProtocols/CrudControllerProtocol.swift b/Sources/CrudRouter/ControllerProtocols/CrudControllerProtocol.swift index 7978e16..6d4ac79 100644 --- a/Sources/CrudRouter/ControllerProtocols/CrudControllerProtocol.swift +++ b/Sources/CrudRouter/ControllerProtocols/CrudControllerProtocol.swift @@ -4,44 +4,48 @@ import Fluent public protocol CrudControllerProtocol { associatedtype ModelType: Model, Content where ModelType.IDValue: LosslessStringConvertible - func indexAll(_ req: Request) throws -> EventLoopFuture<[ModelType]> - func index(_ req: Request) throws -> EventLoopFuture - func update(_ req: Request) throws -> EventLoopFuture - func create(_ req: Request) throws -> EventLoopFuture - func delete(_ req: Request) throws -> EventLoopFuture + func indexAll(_ req: Request) async throws -> [ModelType] + func index(_ req: Request) async throws -> ModelType + func update(_ req: Request) async throws -> ModelType + func create(_ req: Request) async throws -> ModelType + func delete(_ req: Request) async throws -> HTTPStatus } public extension CrudControllerProtocol { - func indexAll(_ req: Request) throws -> EventLoopFuture<[ModelType]> { - return ModelType.query(on: req.db).all().map { Array($0) } + func indexAll(_ req: Request) async throws -> [ModelType] { + return try await ModelType.query(on: req.db).all() } - func index(_ req: Request) throws -> EventLoopFuture { + func index(_ req: Request) async throws -> ModelType { let id = try req.getId(modelType: ModelType.self) - return ModelType.find(id, on: req.db).unwrap(or: Abort(.notFound)) + guard let model = try await ModelType.find(id, on: req.db) else { + throw Abort(.notFound) + } + return model } - func create(_ req: Request) throws -> EventLoopFuture { + func create(_ req: Request) async throws -> ModelType { let model = try req.content.decode(ModelType.self) - return model.save(on: req.db).transform(to: model) + try await model.create(on: req.db) + return model } - func update(_ req: Request) throws -> EventLoopFuture { + func update(_ req: Request) async throws -> ModelType { let id = try req.getId(modelType: ModelType.self) let model = try req.content.decode(ModelType.self) let temp = model temp.id = id - return temp.update(on: req.db).transform(to: temp) + try await temp.update(on: req.db) + return temp } - func delete(_ req: Request) throws -> EventLoopFuture { + func delete(_ req: Request) async throws -> HTTPStatus { let id = try req.getId(modelType: ModelType.self) - return ModelType - .find(id, on: req.db) - .unwrap(or: Abort(.notFound)) - .flatMap { model in - return model.delete(on: req.db).transform(to: HTTPStatus.ok) + guard let model = try await ModelType.find(id, on: req.db) else { + throw Abort(.notFound) } + try await model.delete(on: req.db) + return HTTPStatus.ok } } diff --git a/Sources/CrudRouter/ControllerProtocols/Crudable.swift b/Sources/CrudRouter/ControllerProtocols/Crudable.swift index edce1db..f61e109 100644 --- a/Sources/CrudRouter/ControllerProtocols/Crudable.swift +++ b/Sources/CrudRouter/ControllerProtocols/Crudable.swift @@ -11,7 +11,6 @@ public protocol Crudable: ControllerProtocol { relationConfiguration: ((CrudParentController) -> Void)? ) where ParentType: Model & Content, -// ChildType.Database == ParentType.Database, ParentType.IDValue: LosslessStringConvertible func crud( @@ -21,7 +20,6 @@ public protocol Crudable: ControllerProtocol { relationConfiguration: ((CrudChildrenController) -> Void)? ) where ChildChildType: Model & Content -// ChildType.Database == ChildChildType.Database func crud( at path: PathComponent..., @@ -32,8 +30,6 @@ public protocol Crudable: ControllerProtocol { ChildChildType: Content, ChildChildType.IDValue: LosslessStringConvertible, ThroughType: Model -// ThroughType.Left == ChildType, -// ThroughType.Right == ChildChildType // func crud( // at path: PathComponent..., From 7291a12ed824c0a52e690e940d415972d3efabde Mon Sep 17 00:00:00 2001 From: twof Date: Sun, 21 Nov 2021 11:42:48 -0800 Subject: [PATCH 23/38] move children controller to async --- .../CrudChildrenControllerProtocol.swift | 51 +++++++++---------- 1 file changed, 24 insertions(+), 27 deletions(-) diff --git a/Sources/CrudRouter/ControllerProtocols/CrudChildrenControllerProtocol.swift b/Sources/CrudRouter/ControllerProtocols/CrudChildrenControllerProtocol.swift index 6e4d404..d68e4aa 100644 --- a/Sources/CrudRouter/ControllerProtocols/CrudChildrenControllerProtocol.swift +++ b/Sources/CrudRouter/ControllerProtocols/CrudChildrenControllerProtocol.swift @@ -31,45 +31,42 @@ public extension CrudChildrenControllerProtocol { func indexAll(_ req: Request) async throws -> [ChildType] { let parentId = try req.getId(modelType: ParentType.self) - return try ParentType - .find(parentId, on: req.db) - .unwrap(or: Abort(.notFound)) - .throwingFlatMap { parent -> EventLoopFuture<[ChildType]> in - return try parent[keyPath: self.children] - .query(on: req.db) - .all() - } + + guard let parent = try await ParentType.find(parentId, on: req.db) else { + throw Abort(.notFound) + } + + return try await parent[keyPath: self.children].query(on: req.db).all() } func create(_ req: Request) async throws -> ChildType { let parentId = try req.getId(modelType: ParentType.self) - return try ParentType - .find(parentId, on: req.db) - .unwrap(or: Abort(.notFound)) - .throwingFlatMap { parent -> EventLoopFuture in - let child = try req.content.decode(ChildType.self) - return child.save(on: req.db).transform(to: child) + guard let parent = try await ParentType.find(parentId, on: req.db) else { + throw Abort(.notFound) } + + let child = try req.content.decode(ChildType.self) + try await parent[keyPath: self.children].create(child, on: req.db) + + return child } func update(_ req: Request) async throws -> ChildType { let parentId = try req.getId(modelType: ParentType.self) - return try ParentType - .find(parentId, on: req.db) - .unwrap(or: Abort(.notFound)) - .throwingFlatMap { parent -> EventLoopFuture in - return try parent[keyPath: self.children] - .query(on: req.db) - .first() - .unwrap(or: Abort(.notFound)) - }.throwingFlatMap { oldChild in - let newChild = try req.content.decode(ChildType.self) - let temp = newChild - temp.id = oldChild.id - return temp.update(on: req.db).transform(to: temp) + guard + let parent = try await ParentType.find(parentId, on: req.db), + let oldChild = try await parent[keyPath: self.children].query(on: req.db).first() + else { + throw Abort(.notFound) } + + let newChild = try req.content.decode(ChildType.self) + let temp = newChild + temp.id = oldChild.id + try await temp.update(on: req.db) + return temp } func delete(_ req: Request) async throws -> HTTPStatus { From d5a793d11692b6cadb3f2cd2432f0971bd06b14f Mon Sep 17 00:00:00 2001 From: twof Date: Sun, 21 Nov 2021 14:03:24 -0800 Subject: [PATCH 24/38] tests build, but there are still issues --- .../CrudParentControllerProtocol.swift | 29 ++--- .../CrudSiblingsControllerProtocol.swift | 107 +++++++++--------- .../RouterMethods/SiblingRouterMethod.swift | 29 +---- .../CrudRouteGetResponseTests.swift | 65 +++++------ .../CrudRoutePostResponseTests.swift | 95 +++++++++------- .../CrudRoutePutResponseTests.swift | 81 +++++++------ 6 files changed, 197 insertions(+), 209 deletions(-) diff --git a/Sources/CrudRouter/ControllerProtocols/CrudParentControllerProtocol.swift b/Sources/CrudRouter/ControllerProtocols/CrudParentControllerProtocol.swift index 4d1e057..cfb03a0 100644 --- a/Sources/CrudRouter/ControllerProtocols/CrudParentControllerProtocol.swift +++ b/Sources/CrudRouter/ControllerProtocols/CrudParentControllerProtocol.swift @@ -7,27 +7,30 @@ public protocol CrudParentControllerProtocol { var relation: KeyPath> { get } - func index(_ req: Request) throws -> EventLoopFuture - func update(_ req: Request) throws -> EventLoopFuture + func index(_ req: Request) async throws -> ParentType + func update(_ req: Request) async throws -> ParentType } public extension CrudParentControllerProtocol { - func index(_ req: Request) throws -> EventLoopFuture { + func index(_ req: Request) async throws -> ParentType { let childId = try req.getId(modelType: ChildType.self) - return ChildType.find(childId, on: req.db).unwrap(or: Abort(.notFound)).flatMap { child in - child[keyPath: self.relation].get(on: req.db) + guard let child = try await ChildType.find(childId, on: req.db) else { + throw Abort(.notFound) } + + return try await child[keyPath: self.relation].get(on: req.db) } - func update(_ req: Request) throws -> EventLoopFuture { - let childId = try req.getId(modelType: ChildType.self) + func update(_ req: Request) async throws -> ParentType { +// let childId = try req.getId(modelType: ChildType.self) + let parentId = try req.getId(modelType: ParentType.self) + let newParent = try req.content.decode(ParentType.self) + // TODO: make sure this actually updates the parent - return ChildType - .find(childId, on: req.db) - .unwrap(or: Abort(.notFound)) - .flatMap { child in - return child[keyPath: self.relation].get(on: req.db) - } + let temp = newParent + temp.id = parentId + try await temp.update(on: req.db) + return temp } } diff --git a/Sources/CrudRouter/ControllerProtocols/CrudSiblingsControllerProtocol.swift b/Sources/CrudRouter/ControllerProtocols/CrudSiblingsControllerProtocol.swift index ed43d20..21d812d 100644 --- a/Sources/CrudRouter/ControllerProtocols/CrudSiblingsControllerProtocol.swift +++ b/Sources/CrudRouter/ControllerProtocols/CrudSiblingsControllerProtocol.swift @@ -14,6 +14,7 @@ extension EventLoopFuture { } } +// TODO: Do these proocols actually need to be public? public protocol CrudSiblingsControllerProtocol { associatedtype ParentType: Model & Content where ParentType.IDValue: LosslessStringConvertible associatedtype ChildType: Model & Content where ChildType.IDValue: LosslessStringConvertible @@ -21,53 +22,54 @@ public protocol CrudSiblingsControllerProtocol { var siblings: KeyPath> { get } - func index(_ req: Request) throws -> EventLoopFuture - func indexAll(_ req: Request) throws -> EventLoopFuture<[ChildType]> - func update(_ req: Request) throws -> EventLoopFuture + func index(_ req: Request) async throws -> ChildType + func indexAll(_ req: Request) async throws -> [ChildType] + func update(_ req: Request) async throws -> ChildType } public extension CrudSiblingsControllerProtocol { - func index(_ req: Request) throws -> EventLoopFuture { + func index(_ req: Request) async throws -> ChildType { let parentId = try req.getId(modelType: ParentType.self) - - return try ParentType.find(parentId, on: req.db).unwrap(or: Abort(.notFound)).throwingFlatMap { parent -> EventLoopFuture in - - return try parent[keyPath: self.siblings] - .query(on: req.db) - .first() - .unwrap(or: Abort(.notFound)) + let childId = try req.getId(modelType: ChildType.self) + + // TODO: childId isn't being used. This probably isn't correct. + guard + let parent = try await ParentType.find(parentId, on: req.db), + let child = try await parent[keyPath: self.siblings].query(on: req.db).first() + else { + throw Abort(.notFound) } + + return child } - func indexAll(_ req: Request) throws -> EventLoopFuture<[ChildType]> { + func indexAll(_ req: Request) async throws -> [ChildType] { let parentId = try req.getId(modelType: ParentType.self) - return try ParentType.find(parentId, on: req.db).unwrap(or: Abort(.notFound)).throwingFlatMap { parent -> EventLoopFuture<[ChildType]> in - let siblingsRelation = parent[keyPath: self.siblings] - return try siblingsRelation - .query(on: req.db) - .all() + guard let parent = try await ParentType.find(parentId, on: req.db) else { + throw Abort(.notFound) } + + return try await parent[keyPath: self.siblings].query(on: req.db).all() } - func update(_ req: Request) throws -> EventLoopFuture { + func update(_ req: Request) async throws -> ChildType { let parentId = try req.getId(modelType: ParentType.self) + let childId = try req.getId(modelType: ChildType.self) - return try ParentType - .find(parentId, on: req.db) - .unwrap(or: Abort(.notFound)) - .throwingFlatMap { parent -> EventLoopFuture in - let siblings: SiblingsProperty = parent[keyPath: self.siblings] - let siblingsQuery = try siblings.query(on: req.db) - return siblingsQuery - .first() - .unwrap(or: Abort(.notFound)) - }.throwingFlatMap { oldChild in - let newChild = try req.content.decode(ChildType.self) - let temp = newChild - temp.id = oldChild.id - return temp.update(on: req.db).transform(to: temp) - } + // TODO: Make sure this actually updates the siblings. Parent is never used. + guard + let parent = try await ParentType.find(parentId, on: req.db) + else { + throw Abort(.notFound) + } + + let newChild = try req.content.decode(ChildType.self) + let temp = newChild + temp.id = childId + + try await temp.update(on: req.db) + return temp } } @@ -76,33 +78,34 @@ public extension CrudSiblingsControllerProtocol // ThroughType.Left == ParentType, // ThroughType.Right == ChildType { - func create(_ req: Request) throws -> EventLoopFuture { + func create(_ req: Request) async throws -> ChildType { let parentId = try req.getId(modelType: ParentType.self) - return try ParentType.find(parentId, on: req.db).unwrap(or: Abort(.notFound)).throwingFlatMap { parent -> EventLoopFuture in - let child = try req.content.decode(ChildType.self) - - let relation = parent[keyPath: self.siblings] - return relation.attach(child, on: req.db).transform(to: child) + guard let parent = try await ParentType.find(parentId, on: req.db) else { + throw Abort(.notFound) } + + let newChild = try req.content.decode(ChildType.self) + try await parent[keyPath: self.siblings].attach(newChild, on: req.db) + return newChild } - func delete(_ req: Request) throws -> EventLoopFuture { + func delete(_ req: Request) async throws -> HTTPStatus { let parentId = try req.getId(modelType: ParentType.self) - return try ParentType - .find(parentId, on: req.db) - .unwrap(or: Abort(.notFound)) - .throwingFlatMap { parent -> EventLoopFuture in - let siblingsRelation = parent[keyPath: self.siblings] - return try siblingsRelation - .query(on: req.db) - .first() - .unwrap(or: Abort(.notFound)) - .flatMap { siblingsRelation.detach($0, on: req.db).transform(to: $0) } - .delete(on: req.db) - .transform(to: HTTPStatus.ok) + guard let parent = try await ParentType.find(parentId, on: req.db) else { + throw Abort(.notFound) + } + + let siblingsRelation = parent[keyPath: self.siblings] + + guard let child = try await siblingsRelation.query(on: req.db).first() else { + throw Abort(.notFound) } + + try await siblingsRelation.detach(child, on: req.db) + try await child.delete(on: req.db) + return HTTPStatus.ok } } diff --git a/Sources/CrudRouter/RouterMethods/SiblingRouterMethod.swift b/Sources/CrudRouter/RouterMethods/SiblingRouterMethod.swift index 0f19e2a..a2e9158 100644 --- a/Sources/CrudRouter/RouterMethods/SiblingRouterMethod.swift +++ b/Sources/CrudRouter/RouterMethods/SiblingRouterMethod.swift @@ -8,39 +8,12 @@ public enum ModifiableSiblingRouterMethod { case update case delete -// func register( -// router: RoutesBuilder, -// controller: CrudSiblingsController, -// path: [PathComponent], -// idPath: [PathComponent] -// ) where -// ThroughType.Left == ParentType, -// ThroughType.Right == ChildType -// { -// switch self { -// case .read: -// router.get(idPath, use: controller.index) -// case .readAll: -// router.get(path, use: controller.indexAll) -// case .create: -// router.post(path, use: controller.create) -// case .update: -// router.put(idPath, use: controller.update) -// case .delete: -// router.delete(idPath, use: controller.delete) -// } -// } - func register( router: RoutesBuilder, controller: CrudSiblingsController, path: [PathComponent], idPath: [PathComponent] - ) -// where -// ThroughType.Left == ChildType, -// ThroughType.Right == ParentType - { + ) { switch self { case .read: router.on(.GET, idPath, use: controller.index) diff --git a/Tests/CrudRouterTests/CrudRouteGetResponseTests.swift b/Tests/CrudRouterTests/CrudRouteGetResponseTests.swift index 26ae352..fad1aed 100644 --- a/Tests/CrudRouterTests/CrudRouteGetResponseTests.swift +++ b/Tests/CrudRouterTests/CrudRouteGetResponseTests.swift @@ -15,7 +15,7 @@ final class CrudRouteGetResponseTests: XCTestCase { override func setUp() { super.setUp() - app = Application() + app = Application(.testing) try! configure(app) try! app.migrator.setupIfNeeded().wait() @@ -32,16 +32,15 @@ final class CrudRouteGetResponseTests: XCTestCase { app.crud(register: Galaxy.self) do { - try app.testable().test(.GET, "/galaxy", closure: { (resp) in + try app.testable().test(.GET, "/galaxy") { (resp) in XCTAssert(resp.status == .ok) - guard let bodyBuffer = resp.body.buffer else { XCTFail(); return } - let decoded = try JSONDecoder().decode([Galaxy].self, from: bodyBuffer) + let decoded = try resp.content.decode([Galaxy].self) XCTAssert(decoded.count == 1) XCTAssert(decoded[0].name == "Milky Way") XCTAssert(decoded[0].id == 1) - }) + } } catch { XCTFail("Probably couldn't decode to public galaxy: \(error.localizedDescription)") } @@ -53,27 +52,25 @@ final class CrudRouteGetResponseTests: XCTestCase { } do { - try app.testable().test(.GET, "/galaxy", closure: { (resp) in + try app.testable().test(.GET, "/galaxy") { (resp) in XCTAssert(resp.status == .ok) - guard let bodyBuffer = resp.body.buffer else { XCTFail(); return } - let decoded = try JSONDecoder().decode([Galaxy].self, from: bodyBuffer) + let decoded = try resp.content.decode([Galaxy].self) XCTAssert(decoded.count == 1) XCTAssert(decoded[0].name == "Milky Way") XCTAssert(decoded[0].id == 1) - }) + } - try app.testable().test(.GET, "/galaxy/1/planet", closure: { (resp) in + try app.testable().test(.GET, "/galaxy/1/planet") { (resp) in XCTAssert(resp.status == .ok) - guard let bodyBuffer = resp.body.buffer else { XCTFail(); return } - let decoded = try JSONDecoder().decode([Planet].self, from: bodyBuffer) + let decoded = try resp.content.decode([Planet].self) XCTAssert(decoded.count == 1) XCTAssert(decoded[0].name == "Earth") XCTAssert(decoded[0].id == 1) - }) + } } catch { XCTFail("Probably couldn't decode to public galaxy: \(error.localizedDescription)") } @@ -85,26 +82,24 @@ final class CrudRouteGetResponseTests: XCTestCase { } do { - try app.testable().test(.GET, "/planet", closure: { (resp) in + try app.testable().test(.GET, "/planet") { (resp) in XCTAssert(resp.status == .ok) - guard let bodyBuffer = resp.body.buffer else { XCTFail(); return } - let decoded = try JSONDecoder().decode([Planet].self, from: bodyBuffer) + let decoded = try resp.content.decode([Planet].self) XCTAssert(decoded.count == 1) XCTAssert(decoded[0].name == "Earth") XCTAssert(decoded[0].id == 1) - }) + } - try app.testable().test(.GET, "/planet/1/galaxy", closure: { (resp) in + try app.testable().test(.GET, "/planet/1/galaxy") { (resp) in XCTAssert(resp.status == .ok) - guard let bodyBuffer = resp.body.buffer else { XCTFail(); return } - let decoded = try JSONDecoder().decode(Galaxy.self, from: bodyBuffer) + let decoded = try resp.content.decode(Galaxy.self) XCTAssert(decoded.name == "Milky Way") XCTAssert(decoded.id == 1) - }) + } } catch { XCTFail("Probably couldn't decode to public galaxy: \(error.localizedDescription)") } @@ -120,49 +115,45 @@ final class CrudRouteGetResponseTests: XCTestCase { } do { - try app.testable().test(.GET, "/planet", closure: { (resp) in + try app.testable().test(.GET, "/planet") { (resp) in XCTAssert(resp.status == .ok) - guard let bodyBuffer = resp.body.buffer else { XCTFail(); return } - let decoded = try JSONDecoder().decode([Planet].self, from: bodyBuffer) + let decoded = try resp.content.decode([Planet].self) XCTAssert(decoded.count == 1) XCTAssert(decoded[0].name == "Earth") XCTAssert(decoded[0].id == 1) - }) + } - try app.testable().test(.GET, "/tag", closure: { (resp) in + try app.testable().test(.GET, "/tag") { (resp) in XCTAssert(resp.status == .ok) - guard let bodyBuffer = resp.body.buffer else { XCTFail(); return } - let decoded = try JSONDecoder().decode([Tag].self, from: bodyBuffer) + let decoded = try resp.content.decode([Tag].self) XCTAssert(decoded.count == 1) XCTAssert(decoded[0].name == "Life-Supporting") XCTAssert(decoded[0].id == 1) - }) + } - try app.testable().test(.GET, "/planet/1/tag", closure: { (resp) in + try app.testable().test(.GET, "/planet/1/tag") { (resp) in XCTAssert(resp.status == .ok) - guard let bodyBuffer = resp.body.buffer else { XCTFail(); return } - let decoded = try JSONDecoder().decode([Tag].self, from: bodyBuffer) + let decoded = try resp.content.decode([Tag].self) XCTAssert(decoded.count == 1) XCTAssert(decoded[0].name == "Life-Supporting") XCTAssert(decoded[0].id == 1) - }) + } - try app.testable().test(.GET, "/tag/1/planet", closure: { (resp) in + try app.testable().test(.GET, "/tag/1/planet") { (resp) in XCTAssert(resp.status == .ok) - guard let bodyBuffer = resp.body.buffer else { XCTFail(); return } - let decoded = try JSONDecoder().decode([Planet].self, from: bodyBuffer) + let decoded = try resp.content.decode([Planet].self) XCTAssert(decoded.count == 1) XCTAssert(decoded[0].name == "Earth") XCTAssert(decoded[0].id == 1) - }) + } } catch { XCTFail("Probably couldn't decode to public galaxy: \(error.localizedDescription)") } diff --git a/Tests/CrudRouterTests/CrudRoutePostResponseTests.swift b/Tests/CrudRouterTests/CrudRoutePostResponseTests.swift index a75cfff..3e79d08 100644 --- a/Tests/CrudRouterTests/CrudRoutePostResponseTests.swift +++ b/Tests/CrudRouterTests/CrudRoutePostResponseTests.swift @@ -15,7 +15,7 @@ final class CrudRoutePostResponseTests: XCTestCase { override func setUp() { super.setUp() - app = Application() + app = Application(.testing) try! configure(app) try! app.migrator.setupIfNeeded().wait() @@ -26,6 +26,7 @@ final class CrudRoutePostResponseTests: XCTestCase { super.tearDown() try! app.migrator.revertAllBatches().wait() + app.shutdown() } func testPostBase() throws { @@ -33,26 +34,25 @@ final class CrudRoutePostResponseTests: XCTestCase { do { let newGalaxy = Galaxy(name: "Andromeda") - - try app.testable().test(.POST, "/galaxy", json: newGalaxy, closure: { (resp) in + try app.testable().test(.POST, "/galaxy", beforeRequest: { req in + try req.content.encode(newGalaxy) + }) { (resp) in XCTAssertEqual(resp.status, .ok) - - guard let bodyBuffer = resp.body.buffer else { XCTFail(); return } - - let decoded = try JSONDecoder().decode(Galaxy.self, from: bodyBuffer) - + + let decoded = try resp.content.decode(Galaxy.self) + XCTAssertEqual(decoded.name, "Andromeda") XCTAssertEqual(decoded.id, 2) - + let newGalaxies = try app.db.query(Galaxy.self).all().wait() - + XCTAssertEqual(newGalaxies.count, 2) XCTAssert(newGalaxies.contains { $0.name == "Andromeda" }) - + let newAndromeda = newGalaxies.first { $0.name == "Andromeda" } - + XCTAssertEqual(newAndromeda?.id, 2) - }) + } } catch { XCTFail("Probably couldn't decode to public galaxy: \(error.localizedDescription)") } @@ -66,11 +66,12 @@ final class CrudRoutePostResponseTests: XCTestCase { do { let newGalaxy = Galaxy(name: "Andromeda") - try app.testable().test(.POST, "/galaxy", json: newGalaxy, closure: { (resp) in + try app.testable().test(.POST, "/galaxy", beforeRequest: { req in + try req.content.encode(newGalaxy) + }) { (resp) in XCTAssertEqual(resp.status, .ok) - guard let bodyBuffer = resp.body.buffer else { XCTFail(); return } - let decoded = try JSONDecoder().decode(Galaxy.self, from: bodyBuffer) + let decoded = try resp.content.decode(Galaxy.self) XCTAssertEqual(decoded.name, "Andromeda") XCTAssertEqual(decoded.id, 2) @@ -83,15 +84,16 @@ final class CrudRoutePostResponseTests: XCTestCase { let newAndromeda = newGalaxies.first { $0.name == "Andromeda" } XCTAssertEqual(newAndromeda?.id, 2) - }) + } let newPlanet = Planet(name: "Mars", galaxyID: 2) - try app.testable().test(.POST, "/galaxy/1/planet", json: newPlanet, closure: { (resp) in + try app.testable().test(.POST, "/galaxy/1/planet", beforeRequest: { req in + try req.content.encode(newPlanet) + }) { (resp) in XCTAssertEqual(resp.status, .ok) - guard let bodyBuffer = resp.body.buffer else { XCTFail(); return } - let decoded = try JSONDecoder().decode(Planet.self, from: bodyBuffer) + let decoded = try resp.content.decode(Planet.self) XCTAssertEqual(decoded.name, "Mars") XCTAssertEqual(decoded.id, 2) @@ -106,7 +108,7 @@ final class CrudRoutePostResponseTests: XCTestCase { let newMars = newPlanets.first { $0.name == "Mars" } XCTAssertEqual(newMars?.id, 2) - }) + } } catch { XCTFail("Probably couldn't decode to public galaxy: \(error.localizedDescription)") } @@ -120,11 +122,12 @@ final class CrudRoutePostResponseTests: XCTestCase { do { let newPlanet = Planet(name: "Mars", galaxyID: 2) - try app.testable().test(.POST, "/planet", json: newPlanet, closure: { (resp) in + try app.testable().test(.POST, "/planet", beforeRequest: { req in + try req.content.encode(newPlanet) + }) { (resp) in XCTAssertEqual(resp.status, .ok) - guard let bodyBuffer = resp.body.buffer else { XCTFail(); return } - let decoded = try JSONDecoder().decode(Planet.self, from: bodyBuffer) + let decoded = try resp.content.decode(Planet.self) XCTAssertEqual(decoded.name, "Mars") XCTAssertEqual(decoded.id, 2) @@ -139,13 +142,15 @@ final class CrudRoutePostResponseTests: XCTestCase { let newMars = newPlanets.first { $0.name == "Mars" } XCTAssertEqual(newMars?.id, 2) - }) + } let newGalaxy = Galaxy(name: "Andromeda") - try app.testable().test(.POST, "/planet/1/galaxy", json: newGalaxy, closure: { (resp) in + try app.testable().test(.POST, "/planet/1/galaxy", beforeRequest: { req in + try req.content.encode(newGalaxy) + }) { (resp) in XCTAssertEqual(resp.status, .notFound) - }) + } } catch { XCTFail("Probably couldn't decode to public galaxy: \(error.localizedDescription)") } @@ -163,11 +168,12 @@ final class CrudRoutePostResponseTests: XCTestCase { do { let newPlanet = Planet(name: "Mars", galaxyID: 2) - try app.testable().test(.POST, "/planet", json: newPlanet, closure: { (resp) in + try app.testable().test(.POST, "/planet", beforeRequest: { req in + try req.content.encode(newPlanet) + }) { (resp) in XCTAssertEqual(resp.status, .ok) - guard let bodyBuffer = resp.body.buffer else { XCTFail(); return } - let decoded = try JSONDecoder().decode(Planet.self, from: bodyBuffer) + let decoded = try resp.content.decode(Planet.self) XCTAssertEqual(decoded.name, "Mars") XCTAssertEqual(decoded.id, 2) @@ -181,15 +187,16 @@ final class CrudRoutePostResponseTests: XCTestCase { let newMars = newPlanets.first { $0.name == "Mars" } XCTAssertEqual(newMars?.id, 2) - }) + } let newTag = Tag(id: 2, name: "Red") - try app.testable().test(.POST, "/tag", json: newTag, closure: { (resp) in + try app.testable().test(.POST, "/tag", beforeRequest: { req in + try req.content.encode(newTag) + }) { (resp) in XCTAssertEqual(resp.status, .ok) - guard let bodyBuffer = resp.body.buffer else { XCTFail(); return } - let decoded = try JSONDecoder().decode(Tag.self, from: bodyBuffer) + let decoded = try resp.content.decode(Tag.self) XCTAssertEqual(decoded.name, "Red") XCTAssertEqual(decoded.id, 2) @@ -202,15 +209,16 @@ final class CrudRoutePostResponseTests: XCTestCase { let newMarsTag = newTags.first { $0.name == "Red" } XCTAssertEqual(newMarsTag?.id, 2) - }) + } let otherNewTag = Tag(id: 3, name: "Uninhabitable") - try app.testable().test(.POST, "/planet/2/tag", json: otherNewTag, closure: { (resp) in + try app.testable().test(.POST, "/planet/2/tag", beforeRequest: { req in + try req.content.encode(otherNewTag) + }) { (resp) in XCTAssertEqual(resp.status, .ok) - guard let bodyBuffer = resp.body.buffer else { XCTFail(); return } - let decoded = try JSONDecoder().decode(Tag.self, from: bodyBuffer) + let decoded = try resp.content.decode(Tag.self) XCTAssertEqual(decoded.name, "Uninhabitable") XCTAssertEqual(decoded.id, 3) @@ -223,19 +231,20 @@ final class CrudRoutePostResponseTests: XCTestCase { let newMarsTag = newTags.first { $0.name == "Red" } XCTAssertEqual(newMarsTag?.id, 2) - }) + } let otherNewPlanet = Planet(id: 3, name: "Venus", galaxyID: 2) - try app.testable().test(.POST, "/tag/2/planet", json: otherNewPlanet, closure: { (resp) in + try app.testable().test(.POST, "/tag/2/planet", beforeRequest: { req in + try req.content.encode(otherNewPlanet) + }) { (resp) in XCTAssertEqual(resp.status, .ok) - guard let bodyBuffer = resp.body.buffer else { XCTFail(); return } - let decoded = try JSONDecoder().decode(Planet.self, from: bodyBuffer) + let decoded = try resp.content.decode(Planet.self) XCTAssertEqual(decoded.name, "Venus") XCTAssertEqual(decoded.id, 3) - }) + } } catch { XCTFail("Probably couldn't decode to public galaxy: \(error.localizedDescription)") } diff --git a/Tests/CrudRouterTests/CrudRoutePutResponseTests.swift b/Tests/CrudRouterTests/CrudRoutePutResponseTests.swift index 46ef17c..cdaa262 100644 --- a/Tests/CrudRouterTests/CrudRoutePutResponseTests.swift +++ b/Tests/CrudRouterTests/CrudRoutePutResponseTests.swift @@ -34,12 +34,12 @@ final class CrudRoutePutResponseTests: XCTestCase { do { let existingGalaxy = Galaxy(id: 1, name: "Milky Way 2") - try app.testable().test(.PUT, "/galaxy/1", json: existingGalaxy, closure: { (resp) in + try app.testable().test(.PUT, "/galaxy/1", beforeRequest: { req in + try req.content.encode(existingGalaxy) + }) { (resp) in XCTAssertEqual(resp.status, .ok) - guard let bodyBuffer = resp.body.buffer else { XCTFail(); return } - - let decoded = try JSONDecoder().decode(Galaxy.self, from: bodyBuffer) + let decoded = try resp.content.decode(Galaxy.self) XCTAssertEqual(decoded.name, "Milky Way 2") XCTAssertEqual(decoded.id, 1) @@ -52,7 +52,7 @@ final class CrudRoutePutResponseTests: XCTestCase { let newMilkyWay = newGalaxies.first { $0.name == "Milky Way 2" } XCTAssertEqual(newMilkyWay?.id, 1) - }) + } } catch { XCTFail("Probably couldn't decode to public galaxy: \(error.localizedDescription)") } @@ -66,11 +66,12 @@ final class CrudRoutePutResponseTests: XCTestCase { do { let existingGalaxy = Galaxy(id: 1, name: "Milky Way 2") - try app.testable().test(.PUT, "/galaxy/1", json: existingGalaxy, closure: { (resp) in + try app.testable().test(.PUT, "/galaxy/1", beforeRequest: { req in + try req.content.encode(existingGalaxy) + }) { (resp) in XCTAssertEqual(resp.status, .ok) - guard let bodyBuffer = resp.body.buffer else { XCTFail(); return } - let decoded = try JSONDecoder().decode(Galaxy.self, from: bodyBuffer) + let decoded = try resp.content.decode(Galaxy.self) XCTAssertEqual(decoded.name, "Milky Way 2") XCTAssertEqual(decoded.id, 1) @@ -83,15 +84,16 @@ final class CrudRoutePutResponseTests: XCTestCase { let newAndromeda = newGalaxies.first { $0.name == "Milky Way 2" } XCTAssertEqual(newAndromeda?.id, 1) - }) + } let existingPlanet = Planet(name: "Earth 2", galaxyID: 1) - try app.testable().test(.PUT, "/galaxy/1/planet/", json: existingPlanet, closure: { (resp) in + try app.testable().test(.PUT, "/galaxy/1/planet/", beforeRequest: { req in + try req.content.encode(existingGalaxy) + }) { (resp) in XCTAssertEqual(resp.status, .ok) - guard let bodyBuffer = resp.body.buffer else { XCTFail(); return } - let decoded = try JSONDecoder().decode(Planet.self, from: bodyBuffer) + let decoded = try resp.content.decode(Planet.self) XCTAssertEqual(decoded.name, "Earth 2") XCTAssertEqual(decoded.id, 1) @@ -106,7 +108,7 @@ final class CrudRoutePutResponseTests: XCTestCase { let newEarth = newPlanets.first { $0.name == "Earth 2" } XCTAssertEqual(newEarth?.id, 1) - }) + } } catch { XCTFail("Probably couldn't decode to public galaxy: \(error.localizedDescription)") } @@ -120,11 +122,12 @@ final class CrudRoutePutResponseTests: XCTestCase { do { let existingPlanet = Planet(name: "Earth 2", galaxyID: 1) - try app.testable().test(.PUT, "/planet/1", json: existingPlanet, closure: { (resp) in + try app.testable().test(.PUT, "/planet/1", beforeRequest: { req in + try req.content.encode(existingPlanet) + }) { (resp) in XCTAssertEqual(resp.status, .ok) - guard let bodyBuffer = resp.body.buffer else { XCTFail(); return } - let decoded = try JSONDecoder().decode(Planet.self, from: bodyBuffer) + let decoded = try resp.content.decode(Planet.self) XCTAssertEqual(decoded.name, "Earth 2") XCTAssertEqual(decoded.id, 1) @@ -139,13 +142,15 @@ final class CrudRoutePutResponseTests: XCTestCase { let newMars = newPlanets.first { $0.name == "Earth 2" } XCTAssertEqual(newMars?.id, 1) - }) + } let existingGalaxy = Galaxy(id: 1, name: "Milky Way 2") - try app.testable().test(.PUT, "/planet/1/galaxy", json: existingGalaxy, closure: { (resp) in + try app.testable().test(.PUT, "/planet/1/galaxy", beforeRequest: { req in + try req.content.encode(existingGalaxy) + }) { (resp) in XCTAssertEqual(resp.status, .notFound) - }) + } } catch { XCTFail("Probably couldn't decode to public galaxy: \(error.localizedDescription)") } @@ -163,11 +168,12 @@ final class CrudRoutePutResponseTests: XCTestCase { do { let existingPlanet = Planet(name: "Earth 2", galaxyID: 1) - try app.testable().test(.PUT, "/planet/1", json: existingPlanet, closure: { (resp) in + try app.testable().test(.PUT, "/planet/1", beforeRequest: { req in + try req.content.encode(existingPlanet) + }) { (resp) in XCTAssertEqual(resp.status, .ok) - guard let bodyBuffer = resp.body.buffer else { XCTFail(); return } - let decoded = try JSONDecoder().decode(Planet.self, from: bodyBuffer) + let decoded = try resp.content.decode(Planet.self) XCTAssertEqual(decoded.name, "Earth 2") XCTAssertEqual(decoded.id, 1) @@ -181,15 +187,16 @@ final class CrudRoutePutResponseTests: XCTestCase { let newMars = newPlanets.first { $0.name == "Earth 2" } XCTAssertEqual(newMars?.id, 1) - }) + } let existingTag = Tag(id: 1, name: "Kind of Life Supporting") - try app.testable().test(.PUT, "/tag/1", json: existingTag, closure: { (resp) in + try app.testable().test(.PUT, "/tag/1", beforeRequest: { req in + try req.content.encode(existingTag) + }) { (resp) in XCTAssertEqual(resp.status, .ok) - guard let bodyBuffer = resp.body.buffer else { XCTFail(); return } - let decoded = try JSONDecoder().decode(Tag.self, from: bodyBuffer) + let decoded = try resp.content.decode(Tag.self) XCTAssertEqual(decoded.name, "Kind of Life Supporting") XCTAssertEqual(decoded.id, 1) @@ -202,15 +209,16 @@ final class CrudRoutePutResponseTests: XCTestCase { let newMarsTag = newTags.first { $0.name == "Kind of Life Supporting" } XCTAssertEqual(newMarsTag?.id, 1) - }) + } let otherExistingTag = Tag(id: 3, name: "Sort of Life Supporting") - try app.testable().test(.PUT, "/planet/1/tag/1", json: otherExistingTag, closure: { (resp) in + try app.testable().test(.PUT, "/planet/1/tag/1", beforeRequest: { req in + try req.content.encode(otherExistingTag) + }) { (resp) in XCTAssertEqual(resp.status, .ok) - guard let bodyBuffer = resp.body.buffer else { XCTFail(); return } - - let decoded = try JSONDecoder().decode(Tag.self, from: bodyBuffer) + + let decoded = try resp.content.decode(Tag.self) XCTAssertEqual(decoded.name, "Sort of Life Supporting") XCTAssertEqual(decoded.id, 1) @@ -223,19 +231,20 @@ final class CrudRoutePutResponseTests: XCTestCase { let newEarthTag = newTags.first { $0.name == "Sort of Life Supporting" } XCTAssertEqual(newEarthTag?.id, 1) - }) + } let otherExistingPlanet = Planet(name: "Earth 3", galaxyID: 1) - try app.testable().test(.PUT, "/tag/1/planet/1", json: otherExistingPlanet, closure: { (resp) in + try app.testable().test(.PUT, "/tag/1/planet/1", beforeRequest: { req in + try req.content.encode(otherExistingPlanet) + }) { (resp) in XCTAssertEqual(resp.status, .ok) - guard let bodyBuffer = resp.body.buffer else { XCTFail(); return } - let decoded = try JSONDecoder().decode(Planet.self, from: bodyBuffer) + let decoded = try resp.content.decode(Planet.self) XCTAssertEqual(decoded.name, "Earth") XCTAssertEqual(decoded.id, 1) - }) + } } catch { XCTFail("Probably couldn't decode to public galaxy: \(error.localizedDescription)") } From a557d3aa563f10242fb94f55b9af056f53bffd5f Mon Sep 17 00:00:00 2001 From: twof Date: Sun, 21 Nov 2021 20:57:02 -0800 Subject: [PATCH 25/38] new tests added, not all passing --- Package.swift | 3 +- .../CrudControllerProtocol.swift | 9 +- .../ControllerProtocols/Crudable.swift | 2 +- .../CrudRouter/CrudChildrenController.swift | 2 +- .../CrudChildrenRouteCreationTests.swift | 67 +++++++++ .../CrudRouteCreationTests.swift | 39 ++++-- .../CrudRouteGetResponseTests.swift | 34 ++--- .../CrudRoutePostResponseTests.swift | 91 ++++++------ .../CrudRoutePutResponseTests.swift | 131 +++++++++--------- .../TestMigrations/BaseGalaxySeeding.swift | 6 +- .../TestMigrations/ChildSeeding.swift | 6 +- .../TestMigrations/SiblingSeeding.swift | 6 +- .../TestModels/Galaxy.swift | 9 +- .../TestModels/Planet.swift | 8 +- .../TestModels/PlanetTag.swift | 13 +- .../TestModels/Tag.swift | 8 +- .../XCTestManifests.swift | 0 17 files changed, 264 insertions(+), 170 deletions(-) create mode 100644 Tests/CrudRouterIntegrationTests/CrudChildrenRouteCreationTests.swift rename Tests/{CrudRouterTests => CrudRouterIntegrationTests}/CrudRouteCreationTests.swift (64%) rename Tests/{CrudRouterTests => CrudRouterIntegrationTests}/CrudRouteGetResponseTests.swift (81%) rename Tests/{CrudRouterTests => CrudRouterIntegrationTests}/CrudRoutePostResponseTests.swift (74%) rename Tests/{CrudRouterTests => CrudRouterIntegrationTests}/CrudRoutePutResponseTests.swift (68%) rename Tests/{CrudRouterTests => CrudRouterIntegrationTests}/TestMigrations/BaseGalaxySeeding.swift (81%) rename Tests/{CrudRouterTests => CrudRouterIntegrationTests}/TestMigrations/ChildSeeding.swift (79%) rename Tests/{CrudRouterTests => CrudRouterIntegrationTests}/TestMigrations/SiblingSeeding.swift (86%) rename Tests/{CrudRouterTests => CrudRouterIntegrationTests}/TestModels/Galaxy.swift (82%) rename Tests/{CrudRouterTests => CrudRouterIntegrationTests}/TestModels/Planet.swift (83%) rename Tests/{CrudRouterTests => CrudRouterIntegrationTests}/TestModels/PlanetTag.swift (70%) rename Tests/{CrudRouterTests => CrudRouterIntegrationTests}/TestModels/Tag.swift (82%) rename Tests/{CrudRouterTests => CrudRouterIntegrationTests}/XCTestManifests.swift (100%) diff --git a/Package.swift b/Package.swift index 3339f70..c15b0c1 100644 --- a/Package.swift +++ b/Package.swift @@ -20,7 +20,8 @@ let package = Package( .product(name: "Fluent", package: "fluent"), .product(name: "FluentSQLiteDriver", package: "fluent-sqlite-driver") ]), - .testTarget(name: "CrudRouterTests", dependencies: [ + .testTarget(name: "CrudRouterIntegrationTests", dependencies: [ + .target(name: "CrudRouter"), .product(name: "FluentSQLiteDriver", package: "fluent-sqlite-driver"), .product(name: "XCTVapor", package: "vapor") ]), diff --git a/Sources/CrudRouter/ControllerProtocols/CrudControllerProtocol.swift b/Sources/CrudRouter/ControllerProtocols/CrudControllerProtocol.swift index 6d4ac79..31d70c8 100644 --- a/Sources/CrudRouter/ControllerProtocols/CrudControllerProtocol.swift +++ b/Sources/CrudRouter/ControllerProtocols/CrudControllerProtocol.swift @@ -33,9 +33,14 @@ public extension CrudControllerProtocol { func update(_ req: Request) async throws -> ModelType { let id = try req.getId(modelType: ModelType.self) let model = try req.content.decode(ModelType.self) - + + guard let existingModel = try await ModelType.find(id, on: req.db) else { + throw Abort(.notFound) + } + let temp = model - temp.id = id + temp._$id.exists = true + temp.id = existingModel.id try await temp.update(on: req.db) return temp } diff --git a/Sources/CrudRouter/ControllerProtocols/Crudable.swift b/Sources/CrudRouter/ControllerProtocols/Crudable.swift index f61e109..d24e84e 100644 --- a/Sources/CrudRouter/ControllerProtocols/Crudable.swift +++ b/Sources/CrudRouter/ControllerProtocols/Crudable.swift @@ -91,7 +91,7 @@ extension Crudable { let fullPath = baseIdPath + adjustedPath - let allMethods: Set = Set([.read, .update]) + let allMethods: Set = Set([.create, .read, .readAll, .update, .delete]) let controller: CrudChildrenController switch either { diff --git a/Sources/CrudRouter/CrudChildrenController.swift b/Sources/CrudRouter/CrudChildrenController.swift index 9241eeb..d662562 100644 --- a/Sources/CrudRouter/CrudChildrenController.swift +++ b/Sources/CrudRouter/CrudChildrenController.swift @@ -28,7 +28,7 @@ public struct CrudChildrenController EventLoopFuture { @@ -19,6 +20,7 @@ struct BaseGalaxySeeding: Migration { $0.delete(on: database).transform(to: ()) }.flatten(on: database.eventLoop) } - - static let galaxies = [Galaxy(name: "Milky Way")] + + static let milkyWayId = UUID() + static let galaxies = [Galaxy(id: milkyWayId, name: "Milky Way")] } diff --git a/Tests/CrudRouterTests/TestMigrations/ChildSeeding.swift b/Tests/CrudRouterIntegrationTests/TestMigrations/ChildSeeding.swift similarity index 79% rename from Tests/CrudRouterTests/TestMigrations/ChildSeeding.swift rename to Tests/CrudRouterIntegrationTests/TestMigrations/ChildSeeding.swift index f287b09..448c8a1 100644 --- a/Tests/CrudRouterTests/TestMigrations/ChildSeeding.swift +++ b/Tests/CrudRouterIntegrationTests/TestMigrations/ChildSeeding.swift @@ -6,6 +6,7 @@ // import FluentKit +import Foundation struct ChildSeeding: Migration { func prepare(on database: Database) -> EventLoopFuture { @@ -21,6 +22,7 @@ struct ChildSeeding: Migration { $0.delete(on: database).transform(to: ()) }.flatten(on: database.eventLoop) } - - static let planets = [Planet(id: 1, name: "Earth", galaxyID: 1)] + + static let earthId = UUID() + static let planets = [Planet(id: earthId, name: "Earth", galaxyID: BaseGalaxySeeding.milkyWayId)] } diff --git a/Tests/CrudRouterTests/TestMigrations/SiblingSeeding.swift b/Tests/CrudRouterIntegrationTests/TestMigrations/SiblingSeeding.swift similarity index 86% rename from Tests/CrudRouterTests/TestMigrations/SiblingSeeding.swift rename to Tests/CrudRouterIntegrationTests/TestMigrations/SiblingSeeding.swift index 77c87df..1f0d902 100644 --- a/Tests/CrudRouterTests/TestMigrations/SiblingSeeding.swift +++ b/Tests/CrudRouterIntegrationTests/TestMigrations/SiblingSeeding.swift @@ -6,6 +6,7 @@ // import FluentKit +import Foundation struct SiblingSeeding: Migration { func prepare(on database: Database) -> EventLoopFuture { @@ -25,7 +26,8 @@ struct SiblingSeeding: Migration { ].flatten(on: database.eventLoop) }.flatten(on: database.eventLoop) } - - static var tags = [Tag(id: 1, name: "Life-Supporting")] + + static let lifeSupportingId = UUID() + static var tags = [Tag(id: lifeSupportingId, name: "Life-Supporting")] } diff --git a/Tests/CrudRouterTests/TestModels/Galaxy.swift b/Tests/CrudRouterIntegrationTests/TestModels/Galaxy.swift similarity index 82% rename from Tests/CrudRouterTests/TestModels/Galaxy.swift rename to Tests/CrudRouterIntegrationTests/TestModels/Galaxy.swift index f385131..931e949 100644 --- a/Tests/CrudRouterTests/TestModels/Galaxy.swift +++ b/Tests/CrudRouterIntegrationTests/TestModels/Galaxy.swift @@ -1,6 +1,7 @@ import Vapor import FluentKit import CrudRouter +import Foundation public final class Galaxy: Model, Content { public static let schema = "galaxies" @@ -9,8 +10,8 @@ public final class Galaxy: Model, Content { return GalaxyMigration() } - @ID(key: "id") - public var id: Int? + @ID(key: .id) + public var id: UUID? @Field(key: "name") public var name: String @@ -20,7 +21,7 @@ public final class Galaxy: Model, Content { public init() { } - public init(id: Int? = nil, name: String) { + public init(id: UUID? = nil, name: String) { self.id = id self.name = name } @@ -30,7 +31,7 @@ struct GalaxyMigration: Migration { init() {} func prepare(on database: Database) -> EventLoopFuture { return database.schema("galaxies") - .field("id", .int, .identifier(auto: true)) + .field("id", .uuid, .identifier(auto: true)) .field("name", .string, .required) .create() } diff --git a/Tests/CrudRouterTests/TestModels/Planet.swift b/Tests/CrudRouterIntegrationTests/TestModels/Planet.swift similarity index 83% rename from Tests/CrudRouterTests/TestModels/Planet.swift rename to Tests/CrudRouterIntegrationTests/TestModels/Planet.swift index 0a9eb19..1010d7a 100644 --- a/Tests/CrudRouterTests/TestModels/Planet.swift +++ b/Tests/CrudRouterIntegrationTests/TestModels/Planet.swift @@ -4,8 +4,8 @@ import Vapor public final class Planet: Model, Content { public static let schema = "planets" - @ID(key: "id") - public var id: Int? + @ID(key: .id) + public var id: UUID? @Field(key: "name") public var name: String @@ -18,7 +18,7 @@ public final class Planet: Model, Content { public init() { } - public init(id: Int? = nil, name: String, galaxyID: Galaxy.IDValue) { + public init(id: UUID? = nil, name: String, galaxyID: Galaxy.IDValue) { self.id = id self.name = name self.$galaxy.id = galaxyID @@ -28,7 +28,7 @@ public final class Planet: Model, Content { public struct PlanetMigration: Migration { public func prepare(on database: Database) -> EventLoopFuture { return database.schema("planets") - .field("id", .int, .identifier(auto: true)) + .field("id", .uuid, .identifier(auto: true)) .field("name", .string, .required) .field("galaxy_id", .int, .required) .create() diff --git a/Tests/CrudRouterTests/TestModels/PlanetTag.swift b/Tests/CrudRouterIntegrationTests/TestModels/PlanetTag.swift similarity index 70% rename from Tests/CrudRouterTests/TestModels/PlanetTag.swift rename to Tests/CrudRouterIntegrationTests/TestModels/PlanetTag.swift index bc38597..b642f31 100644 --- a/Tests/CrudRouterTests/TestModels/PlanetTag.swift +++ b/Tests/CrudRouterIntegrationTests/TestModels/PlanetTag.swift @@ -1,10 +1,11 @@ import FluentSQLiteDriver +import Foundation final class PlanetTag: Model { static let schema = "planet+tag" - @ID(key: "id") - var id: Int? + @ID(key: .id) + var id: UUID? @Parent(key: "planet_id") var planet: Planet @@ -14,7 +15,7 @@ final class PlanetTag: Model { init() { } - init(planetID: Int, tagID: Int) { + init(planetID: UUID, tagID: UUID) { self.$planet.id = planetID self.$tag.id = tagID } @@ -23,9 +24,9 @@ final class PlanetTag: Model { struct PlanetTagMigration: Migration { func prepare(on database: Database) -> EventLoopFuture { return database.schema("planet+tag") - .field("id", .int, .identifier(auto: true)) - .field("planet_id", .int, .required) - .field("tag_id", .int, .required) + .field("id", .uuid, .identifier(auto: true)) + .field("planet_id", .uuid, .required) + .field("tag_id", .uuid, .required) .create() } diff --git a/Tests/CrudRouterTests/TestModels/Tag.swift b/Tests/CrudRouterIntegrationTests/TestModels/Tag.swift similarity index 82% rename from Tests/CrudRouterTests/TestModels/Tag.swift rename to Tests/CrudRouterIntegrationTests/TestModels/Tag.swift index b01826d..6440418 100644 --- a/Tests/CrudRouterTests/TestModels/Tag.swift +++ b/Tests/CrudRouterIntegrationTests/TestModels/Tag.swift @@ -4,8 +4,8 @@ import Vapor public final class Tag: Model, Content { public static let schema = "tags" - @ID(key: "id") - public var id: Int? + @ID(key: .id) + public var id: UUID? @Field(key: "name") public var name: String @@ -15,7 +15,7 @@ public final class Tag: Model, Content { public init() { } - public init(id: Int? = nil, name: String) { + public init(id: UUID? = nil, name: String) { self.id = id self.name = name } @@ -24,7 +24,7 @@ public final class Tag: Model, Content { public struct TagMigration: Migration { public func prepare(on database: Database) -> EventLoopFuture { return database.schema("tags") - .field("id", .int, .identifier(auto: true)) + .field("id", .uuid, .identifier(auto: true)) .field("name", .string, .required) .create() } diff --git a/Tests/CrudRouterTests/XCTestManifests.swift b/Tests/CrudRouterIntegrationTests/XCTestManifests.swift similarity index 100% rename from Tests/CrudRouterTests/XCTestManifests.swift rename to Tests/CrudRouterIntegrationTests/XCTestManifests.swift From 67f72d13e13758385e9e910203d769bf936d12e5 Mon Sep 17 00:00:00 2001 From: twof Date: Sun, 21 Nov 2021 21:07:54 -0800 Subject: [PATCH 26/38] crud parent route creation tests --- .../CrudChildrenRouteCreationTests.swift | 0 .../CrudParentRouteCreationTests.swift | 61 +++++++++++++++++++ .../CrudRouteCreationTests.swift | 0 3 files changed, 61 insertions(+) rename Tests/CrudRouterIntegrationTests/{ => RouteCreationTests}/CrudChildrenRouteCreationTests.swift (100%) create mode 100644 Tests/CrudRouterIntegrationTests/RouteCreationTests/CrudParentRouteCreationTests.swift rename Tests/CrudRouterIntegrationTests/{ => RouteCreationTests}/CrudRouteCreationTests.swift (100%) diff --git a/Tests/CrudRouterIntegrationTests/CrudChildrenRouteCreationTests.swift b/Tests/CrudRouterIntegrationTests/RouteCreationTests/CrudChildrenRouteCreationTests.swift similarity index 100% rename from Tests/CrudRouterIntegrationTests/CrudChildrenRouteCreationTests.swift rename to Tests/CrudRouterIntegrationTests/RouteCreationTests/CrudChildrenRouteCreationTests.swift diff --git a/Tests/CrudRouterIntegrationTests/RouteCreationTests/CrudParentRouteCreationTests.swift b/Tests/CrudRouterIntegrationTests/RouteCreationTests/CrudParentRouteCreationTests.swift new file mode 100644 index 0000000..1d58d52 --- /dev/null +++ b/Tests/CrudRouterIntegrationTests/RouteCreationTests/CrudParentRouteCreationTests.swift @@ -0,0 +1,61 @@ +import XCTest +@testable import CrudRouter +import Vapor +import XCTVapor + +final class CrudParentRouteCreationTests: XCTestCase { + func testCrudRegistrationWithDefaultRoute() throws { + let app = Application() + app.crud(register: Planet.self) { router in + router.crud(parent: \.$galaxy) + } + + XCTAssert(app.routes.all.count == 7) + let paths = app.routes.all.map { [$0.method.rawValue] + $0.path.map { $0.stringComponent } } + + XCTAssert(paths.contains { $0 == ["GET", "planet"] }) + XCTAssert(paths.contains { $0 == ["GET", "planet", ":planetsID"] }) + XCTAssert(paths.contains { $0 == ["POST", "planet"] }) + XCTAssert(paths.contains { $0 == ["PUT", "planet", ":planetsID"] }) + XCTAssert(paths.contains { $0 == ["DELETE", "planet", ":planetsID"] }) + + XCTAssert(paths.contains { $0 == ["GET", "planet", ":planetsID", "galaxy"] }) + XCTAssert(paths.contains { $0 == ["PUT", "planet", ":planetsID", "galaxy"] }) + } + + func testChildrenCrudRegistrationWithMethodsSelected() throws { + let app = Application() + app.crud(register: Planet.self) { router in + router.crud(parent: \.$galaxy, .only([.read])) + } + + XCTAssert(app.routes.all.count == 6) + let paths = app.routes.all.map { [$0.method.rawValue] + $0.path.map { $0.stringComponent } } + + XCTAssert(paths.contains { $0 == ["GET", "planet"] }) + XCTAssert(paths.contains { $0 == ["GET", "planet", ":planetsID"] }) + XCTAssert(paths.contains { $0 == ["POST", "planet"] }) + XCTAssert(paths.contains { $0 == ["PUT", "planet", ":planetsID"] }) + XCTAssert(paths.contains { $0 == ["DELETE", "planet", ":planetsID"] }) + + XCTAssert(paths.contains { $0 == ["GET", "planet", ":planetsID", "galaxy"] }) + } + + func testChildrenCrudRegistrationWithMethodsExcluded() throws { + let app = Application() + app.crud(register: Planet.self) { router in + router.crud(parent: \.$galaxy, .except([.read])) + } + + XCTAssert(app.routes.all.count == 6) + let paths = app.routes.all.map { [$0.method.rawValue] + $0.path.map { $0.stringComponent } } + + XCTAssert(paths.contains { $0 == ["GET", "planet"] }) + XCTAssert(paths.contains { $0 == ["GET", "planet", ":planetsID"] }) + XCTAssert(paths.contains { $0 == ["POST", "planet"] }) + XCTAssert(paths.contains { $0 == ["PUT", "planet", ":planetsID"] }) + XCTAssert(paths.contains { $0 == ["DELETE", "planet", ":planetsID"] }) + + XCTAssert(paths.contains { $0 == ["PUT", "planet", ":planetsID", "galaxy"] }) + } +} diff --git a/Tests/CrudRouterIntegrationTests/CrudRouteCreationTests.swift b/Tests/CrudRouterIntegrationTests/RouteCreationTests/CrudRouteCreationTests.swift similarity index 100% rename from Tests/CrudRouterIntegrationTests/CrudRouteCreationTests.swift rename to Tests/CrudRouterIntegrationTests/RouteCreationTests/CrudRouteCreationTests.swift From 87df819644b1011603d87d317193c8f695799d42 Mon Sep 17 00:00:00 2001 From: twof Date: Sun, 21 Nov 2021 21:45:01 -0800 Subject: [PATCH 27/38] siblings produce correct route sets --- .../ControllerProtocols/Crudable.swift | 48 ------- .../CrudRouter/CrudSiblingsController.swift | 44 +----- .../CrudChildrenRouteCreationTests.swift | 6 +- .../CrudParentRouteCreationTests.swift | 6 +- .../CrudSiblingsRouteCreationTests.swift | 128 ++++++++++++++++++ 5 files changed, 139 insertions(+), 93 deletions(-) create mode 100644 Tests/CrudRouterIntegrationTests/RouteCreationTests/CrudSiblingsRouteCreationTests.swift diff --git a/Sources/CrudRouter/ControllerProtocols/Crudable.swift b/Sources/CrudRouter/ControllerProtocols/Crudable.swift index d24e84e..1d14c07 100644 --- a/Sources/CrudRouter/ControllerProtocols/Crudable.swift +++ b/Sources/CrudRouter/ControllerProtocols/Crudable.swift @@ -30,18 +30,6 @@ public protocol Crudable: ControllerProtocol { ChildChildType: Content, ChildChildType.IDValue: LosslessStringConvertible, ThroughType: Model - -// func crud( -// at path: PathComponent..., -// siblings relation: KeyPath>, -// _ either: OnlyExceptEither, -// relationConfiguration: ((CrudSiblingsController) -> Void)? -// ) where -// ChildChildType: Content, -// ChildChildType.IDValue: LosslessStringConvertible, -// ThroughType: Model, -// ThroughType.Right == ChildType, -// ThroughType.Left == ChildChildType } extension Crudable { @@ -52,7 +40,6 @@ extension Crudable { relationConfiguration: ((CrudParentController) -> Void)?=nil ) where ParentType: Model & Content, -// ChildType.Database == ParentType.Database, ParentType.IDValue: LosslessStringConvertible { let baseIdPath = self.path.appending(.parameter("\(ChildType.schema)ID")) @@ -83,7 +70,6 @@ extension Crudable { relationConfiguration: ((CrudChildrenController) -> Void)?=nil ) where ChildChildType: Model & Content, -// ChildType.Database == ChildChildType.Database, ChildChildType.IDValue: LosslessStringConvertible { let baseIdPath = self.path.appending(.parameter("\(ChildType.schema)ID")) @@ -115,8 +101,6 @@ extension Crudable { ChildChildType: Content, ChildChildType.IDValue: LosslessStringConvertible, ThroughType: Model -// ThroughType.Left == ChildType, -// ThroughType.Right == ChildChildType { let baseIdPath = self.path.appending(.parameter("\(ChildType.schema)ID")) let adjustedPath = path.adjustedPath(for: ChildChildType.self) @@ -138,36 +122,4 @@ extension Crudable { relationConfiguration?(controller) } - -// public func crud( -// at path: PathComponent..., -// siblings relation: KeyPath>, -// _ either: OnlyExceptEither = .only([.read, .readAll, .create, .update, .delete]), -// relationConfiguration: ((CrudSiblingsController) -> Void)?=nil -// ) where -// ChildChildType: Content, -// ChildChildType.IDValue: LosslessStringConvertible, -// ThroughType: Model -//// ThroughType.Right == ChildType, -//// ThroughType.Left == ChildChildType -// { -// let baseIdPath = self.path.appending(.parameter("\(ChildType.schema)ID")) -// let adjustedPath = path.adjustedPath(for: ChildChildType.self) -// -// let fullPath = baseIdPath + adjustedPath -// -// let allMethods: Set = Set([.read, .readAll, .create, .update, .delete]) -// let controller: CrudSiblingsController -// -// switch either { -// case .only(let methods): -// controller = CrudSiblingsController(siblingRelation: relation, path: fullPath, router: self.router, activeMethods: Set(methods)) -// case .except(let methods): -// controller = CrudSiblingsController(siblingRelation: relation, path: fullPath, router: self.router, activeMethods: allMethods.subtracting(Set(methods))) -// } -// -// do { try controller.boot(routes: self.router) } catch { fatalError("I have no reason to expect boot to throw") } -// -// relationConfiguration?(controller) -// } } diff --git a/Sources/CrudRouter/CrudSiblingsController.swift b/Sources/CrudRouter/CrudSiblingsController.swift index bfba2ae..87ac129 100644 --- a/Sources/CrudRouter/CrudSiblingsController.swift +++ b/Sources/CrudRouter/CrudSiblingsController.swift @@ -33,52 +33,18 @@ public struct CrudSiblingsController< extension CrudSiblingsController: RouteCollection {} -//public extension CrudSiblingsController where -// ThroughType.Right == ParentType, -// ThroughType.Left == ChildType -//{ -// func boot(routes router: RoutesBuilder) throws { -// let parentPath = self.path -// let parentIdPath = self.path.appending(.parameter("\(ParentType.schema)ID")) -// -// self.activeMethods.forEach { -// $0.register( -// router: router, -// controller: self, -// path: parentPath, -// idPath: parentIdPath -// ) -// } -// } -//} -// -public extension CrudSiblingsController -// where -// ThroughType.Left == ParentType, -// ThroughType.Right == ChildType -{ +public extension CrudSiblingsController { func boot(routes router: RoutesBuilder) throws { - let parentPath = self.path - let parentIdPath = self.path.appending(.parameter("\(ParentType.schema)ID")) + let childPath = self.path + let childIdPath = self.path.appending(.parameter("\(ChildType.schema)ID")) self.activeMethods.forEach { $0.register( router: router, controller: self, - path: parentPath, - idPath: parentIdPath + path: childPath, + idPath: childIdPath ) } } } - -//public extension CrudSiblingsController { -// func boot(routes router: RoutesBuilder) throws { -// let parentPath = self.path -// let parentIdPath = self.path.appending(.parameter("\(ParentType.schema)ID")) -// -// router.on(.GET, parentIdPath, use: self.index) -// router.on(.GET, parentPath, use: self.indexAll) -// router.put(parentIdPath, use: self.update) -// } -//} diff --git a/Tests/CrudRouterIntegrationTests/RouteCreationTests/CrudChildrenRouteCreationTests.swift b/Tests/CrudRouterIntegrationTests/RouteCreationTests/CrudChildrenRouteCreationTests.swift index 0b7542a..575df56 100644 --- a/Tests/CrudRouterIntegrationTests/RouteCreationTests/CrudChildrenRouteCreationTests.swift +++ b/Tests/CrudRouterIntegrationTests/RouteCreationTests/CrudChildrenRouteCreationTests.swift @@ -4,7 +4,7 @@ import Vapor import XCTVapor final class CrudChildrenRouteCreationTests: XCTestCase { - func testChildrenCrudRegistrationWithDefaultRoute() throws { + func testRegistrationWithDefaultRoute() throws { let app = Application() app.crud(register: Galaxy.self) { router in router.crud(children: \.$planets) @@ -26,7 +26,7 @@ final class CrudChildrenRouteCreationTests: XCTestCase { XCTAssert(paths.contains { $0 == ["DELETE", "galaxy", ":galaxiesID", "planet", ":planetsID"] }) } - func testChildrenCrudRegistrationWithMethodsSelected() throws { + func testRegistrationWithMethodsSelected() throws { let app = Application() app.crud(register: Galaxy.self) { router in router.crud(children: \.$planets, .only([.delete, .readAll])) @@ -45,7 +45,7 @@ final class CrudChildrenRouteCreationTests: XCTestCase { XCTAssert(paths.contains { $0 == ["DELETE", "galaxy", ":galaxiesID", "planet", ":planetsID"] }) } - func testChildrenCrudRegistrationWithMethodsExcluded() throws { + func testRegistrationWithMethodsExcluded() throws { let app = Application() app.crud(register: Galaxy.self) { router in router.crud(children: \.$planets, .except([.readAll, .update])) diff --git a/Tests/CrudRouterIntegrationTests/RouteCreationTests/CrudParentRouteCreationTests.swift b/Tests/CrudRouterIntegrationTests/RouteCreationTests/CrudParentRouteCreationTests.swift index 1d58d52..b13b616 100644 --- a/Tests/CrudRouterIntegrationTests/RouteCreationTests/CrudParentRouteCreationTests.swift +++ b/Tests/CrudRouterIntegrationTests/RouteCreationTests/CrudParentRouteCreationTests.swift @@ -4,7 +4,7 @@ import Vapor import XCTVapor final class CrudParentRouteCreationTests: XCTestCase { - func testCrudRegistrationWithDefaultRoute() throws { + func testRegistrationWithDefaultRoute() throws { let app = Application() app.crud(register: Planet.self) { router in router.crud(parent: \.$galaxy) @@ -23,7 +23,7 @@ final class CrudParentRouteCreationTests: XCTestCase { XCTAssert(paths.contains { $0 == ["PUT", "planet", ":planetsID", "galaxy"] }) } - func testChildrenCrudRegistrationWithMethodsSelected() throws { + func testRegistrationWithMethodsSelected() throws { let app = Application() app.crud(register: Planet.self) { router in router.crud(parent: \.$galaxy, .only([.read])) @@ -41,7 +41,7 @@ final class CrudParentRouteCreationTests: XCTestCase { XCTAssert(paths.contains { $0 == ["GET", "planet", ":planetsID", "galaxy"] }) } - func testChildrenCrudRegistrationWithMethodsExcluded() throws { + func testRegistrationWithMethodsExcluded() throws { let app = Application() app.crud(register: Planet.self) { router in router.crud(parent: \.$galaxy, .except([.read])) diff --git a/Tests/CrudRouterIntegrationTests/RouteCreationTests/CrudSiblingsRouteCreationTests.swift b/Tests/CrudRouterIntegrationTests/RouteCreationTests/CrudSiblingsRouteCreationTests.swift new file mode 100644 index 0000000..5bf19e7 --- /dev/null +++ b/Tests/CrudRouterIntegrationTests/RouteCreationTests/CrudSiblingsRouteCreationTests.swift @@ -0,0 +1,128 @@ +import XCTest +@testable import CrudRouter +import Vapor +import XCTVapor + +final class CrudSiblingsRouteCreationTests: XCTestCase { + func testRegistrationWithDefaultRoute() throws { + let app = Application() + app.crud(register: Planet.self) { router in + router.crud(siblings: \.$tags) + } + + XCTAssert(app.routes.all.count == 10) + let paths = app.routes.all.map { [$0.method.rawValue] + $0.path.map { $0.stringComponent } } + + XCTAssert(paths.contains { $0 == ["GET", "planet"] }) + XCTAssert(paths.contains { $0 == ["GET", "planet", ":planetsID"] }) + XCTAssert(paths.contains { $0 == ["POST", "planet"] }) + XCTAssert(paths.contains { $0 == ["PUT", "planet", ":planetsID"] }) + XCTAssert(paths.contains { $0 == ["DELETE", "planet", ":planetsID"] }) + + XCTAssert(paths.contains { $0 == ["GET", "planet", ":planetsID", "tag"] }) + XCTAssert(paths.contains { $0 == ["GET", "planet", ":planetsID", "tag", ":tagsID"] }) + XCTAssert(paths.contains { $0 == ["POST", "planet", ":planetsID", "tag"] }) + XCTAssert(paths.contains { $0 == ["PUT", "planet", ":planetsID", "tag", ":tagsID"] }) + XCTAssert(paths.contains { $0 == ["DELETE", "planet", ":planetsID", "tag", ":tagsID"] }) + } + + func testRegistrationWithDefaultRouteInverted() throws { + let app = Application() + app.crud(register: Tag.self) { router in + router.crud(siblings: \.$planets) + } + + XCTAssert(app.routes.all.count == 10) + let paths = app.routes.all.map { [$0.method.rawValue] + $0.path.map { $0.stringComponent } } + + XCTAssert(paths.contains { $0 == ["GET", "tag"] }) + XCTAssert(paths.contains { $0 == ["GET", "tag", ":tagsID"] }) + XCTAssert(paths.contains { $0 == ["POST", "tag"] }) + XCTAssert(paths.contains { $0 == ["PUT", "tag", ":tagsID"] }) + XCTAssert(paths.contains { $0 == ["DELETE", "tag", ":tagsID"] }) + + XCTAssert(paths.contains { $0 == ["GET", "tag", ":tagsID", "planet"] }) + XCTAssert(paths.contains { $0 == ["GET", "tag", ":tagsID", "planet", ":planetsID"] }) + XCTAssert(paths.contains { $0 == ["POST", "tag", ":tagsID", "planet"] }) + XCTAssert(paths.contains { $0 == ["PUT", "tag", ":tagsID", "planet", ":planetsID"] }) + XCTAssert(paths.contains { $0 == ["DELETE", "tag", ":tagsID", "planet", ":planetsID"] }) + } + + func testRegistrationWithMethodsSelected() throws { + let app = Application() + app.crud(register: Planet.self) { router in + router.crud(siblings: \.$tags, .only([.delete, .readAll])) + } + + XCTAssert(app.routes.all.count == 7) + let paths = app.routes.all.map { [$0.method.rawValue] + $0.path.map { $0.stringComponent } } + + XCTAssert(paths.contains { $0 == ["GET", "planet"] }) + XCTAssert(paths.contains { $0 == ["GET", "planet", ":planetsID"] }) + XCTAssert(paths.contains { $0 == ["POST", "planet"] }) + XCTAssert(paths.contains { $0 == ["PUT", "planet", ":planetsID"] }) + XCTAssert(paths.contains { $0 == ["DELETE", "planet", ":planetsID"] }) + + XCTAssert(paths.contains { $0 == ["GET", "planet", ":planetsID", "tag"] }) + XCTAssert(paths.contains { $0 == ["DELETE", "planet", ":planetsID", "tag", ":tagsID"] }) + } + + func testRegistrationWithMethodsSelectedInverted() throws { + let app = Application() + app.crud(register: Tag.self) { router in + router.crud(siblings: \.$planets, .only([.delete, .readAll])) + } + + XCTAssert(app.routes.all.count == 7) + let paths = app.routes.all.map { [$0.method.rawValue] + $0.path.map { $0.stringComponent } } + + XCTAssert(paths.contains { $0 == ["GET", "tag"] }) + XCTAssert(paths.contains { $0 == ["GET", "tag", ":tagsID"] }) + XCTAssert(paths.contains { $0 == ["POST", "tag"] }) + XCTAssert(paths.contains { $0 == ["PUT", "tag", ":tagsID"] }) + XCTAssert(paths.contains { $0 == ["DELETE", "tag", ":tagsID"] }) + + XCTAssert(paths.contains { $0 == ["GET", "tag", ":tagsID", "planet"] }) + XCTAssert(paths.contains { $0 == ["DELETE", "tag", ":tagsID", "planet", ":planetsID"] }) + } + + func testRegistrationWithMethodsExcluded() throws { + let app = Application() + app.crud(register: Planet.self) { router in + router.crud(siblings: \.$tags, .except([.readAll, .update])) + } + + XCTAssert(app.routes.all.count == 8) + let paths = app.routes.all.map { [$0.method.rawValue] + $0.path.map { $0.stringComponent } } + + XCTAssert(paths.contains { $0 == ["GET", "planet"] }) + XCTAssert(paths.contains { $0 == ["GET", "planet", ":planetsID"] }) + XCTAssert(paths.contains { $0 == ["POST", "planet"] }) + XCTAssert(paths.contains { $0 == ["PUT", "planet", ":planetsID"] }) + XCTAssert(paths.contains { $0 == ["DELETE", "planet", ":planetsID"] }) + + XCTAssert(paths.contains { $0 == ["GET", "planet", ":planetsID", "tag", ":tagsID"] }) + XCTAssert(paths.contains { $0 == ["POST", "planet", ":planetsID", "tag"] }) + XCTAssert(paths.contains { $0 == ["DELETE", "planet", ":planetsID", "tag", ":tagsID"] }) + } + + func testRegistrationWithMethodsExcludedInverted() throws { + let app = Application() + app.crud(register: Tag.self) { router in + router.crud(siblings: \.$planets, .except([.readAll, .update])) + } + + XCTAssert(app.routes.all.count == 8) + let paths = app.routes.all.map { [$0.method.rawValue] + $0.path.map { $0.stringComponent } } + + XCTAssert(paths.contains { $0 == ["GET", "tag"] }) + XCTAssert(paths.contains { $0 == ["GET", "tag", ":tagsID"] }) + XCTAssert(paths.contains { $0 == ["POST", "tag"] }) + XCTAssert(paths.contains { $0 == ["PUT", "tag", ":tagsID"] }) + XCTAssert(paths.contains { $0 == ["DELETE", "tag", ":tagsID"] }) + + XCTAssert(paths.contains { $0 == ["GET", "tag", ":tagsID", "planet", ":planetsID"] }) + XCTAssert(paths.contains { $0 == ["POST", "tag", ":tagsID", "planet"] }) + XCTAssert(paths.contains { $0 == ["DELETE", "tag", ":tagsID", "planet", ":planetsID"] }) + } +} From a20db72e83700b7bfcd3fc77c03c7bae09db41f7 Mon Sep 17 00:00:00 2001 From: twof Date: Sun, 21 Nov 2021 22:37:54 -0800 Subject: [PATCH 28/38] parent put test passing --- .../CrudChildrenControllerProtocol.swift | 1 + .../CrudParentControllerProtocol.swift | 12 +++-- .../CrudSiblingsControllerProtocol.swift | 42 +-------------- .../CrudRoutePutResponseTests.swift | 51 ++++++++----------- 4 files changed, 32 insertions(+), 74 deletions(-) diff --git a/Sources/CrudRouter/ControllerProtocols/CrudChildrenControllerProtocol.swift b/Sources/CrudRouter/ControllerProtocols/CrudChildrenControllerProtocol.swift index d68e4aa..25be2f7 100644 --- a/Sources/CrudRouter/ControllerProtocols/CrudChildrenControllerProtocol.swift +++ b/Sources/CrudRouter/ControllerProtocols/CrudChildrenControllerProtocol.swift @@ -64,6 +64,7 @@ public extension CrudChildrenControllerProtocol { let newChild = try req.content.decode(ChildType.self) let temp = newChild + temp._$id.exists = true temp.id = oldChild.id try await temp.update(on: req.db) return temp diff --git a/Sources/CrudRouter/ControllerProtocols/CrudParentControllerProtocol.swift b/Sources/CrudRouter/ControllerProtocols/CrudParentControllerProtocol.swift index cfb03a0..b3acfe3 100644 --- a/Sources/CrudRouter/ControllerProtocols/CrudParentControllerProtocol.swift +++ b/Sources/CrudRouter/ControllerProtocols/CrudParentControllerProtocol.swift @@ -23,13 +23,19 @@ public extension CrudParentControllerProtocol { } func update(_ req: Request) async throws -> ParentType { -// let childId = try req.getId(modelType: ChildType.self) - let parentId = try req.getId(modelType: ParentType.self) + let childId = try req.getId(modelType: ChildType.self) let newParent = try req.content.decode(ParentType.self) // TODO: make sure this actually updates the parent + guard let child = try await ChildType.find(childId, on: req.db) else { + throw Abort(.notFound) + } + + let oldParent = try await child[keyPath: self.relation].get(on: req.db) + let temp = newParent - temp.id = parentId + temp.id = oldParent.id + temp._$id.exists = true try await temp.update(on: req.db) return temp } diff --git a/Sources/CrudRouter/ControllerProtocols/CrudSiblingsControllerProtocol.swift b/Sources/CrudRouter/ControllerProtocols/CrudSiblingsControllerProtocol.swift index 21d812d..66495b2 100644 --- a/Sources/CrudRouter/ControllerProtocols/CrudSiblingsControllerProtocol.swift +++ b/Sources/CrudRouter/ControllerProtocols/CrudSiblingsControllerProtocol.swift @@ -73,11 +73,7 @@ public extension CrudSiblingsControllerProtocol { } } -public extension CrudSiblingsControllerProtocol -// where -// ThroughType.Left == ParentType, -// ThroughType.Right == ChildType -{ +public extension CrudSiblingsControllerProtocol { func create(_ req: Request) async throws -> ChildType { let parentId = try req.getId(modelType: ParentType.self) @@ -108,39 +104,3 @@ public extension CrudSiblingsControllerProtocol return HTTPStatus.ok } } - -//public extension CrudSiblingsControllerProtocol where ThroughType.Right == ParentType, -//ThroughType.Left == ChildType { -// func create(_ req: Request) throws -> EventLoopFuture { -// let parentId: ParentType.IDValue = try req.getId() -// -// return try ParentType.find(parentId, on: req.db).unwrap(or: Abort(.notFound)).throwingFlatMap { parent -> EventLoopFuture in -// -// return try req.content.decode(ChildType.self).flatMap { child in -// return child.create(on: req.db) -// }.flatMap { child in -// let relation = parent[keyPath: self.siblings] -// return relation.attach(child, on: req.db).transform(to: child) -// } -// } -// } -// -// func delete(_ req: Request) throws -> EventLoopFuture { -// let parentId: ParentType.IDValue = try req.getId() -// let childId: ChildType.IDValue = try req.getId() -// -// return try ParentType -// .find(parentId, on: req.db) -// .unwrap(or: Abort(.notFound)) -// .throwingFlatMap { parent -> EventLoopFuture in -// let siblingsRelation = parent[keyPath: self.siblings] -// return try siblingsRelation -// .query(on: req.db) -// .filter(\ChildType.fluentID == childId) -// .first() -// .unwrap(or: Abort(.notFound)) -// .delete(on: req.db) -// .transform(to: HTTPStatus.ok) -// } -// } -//} diff --git a/Tests/CrudRouterIntegrationTests/CrudRoutePutResponseTests.swift b/Tests/CrudRouterIntegrationTests/CrudRoutePutResponseTests.swift index 08ba769..c4802af 100644 --- a/Tests/CrudRouterIntegrationTests/CrudRoutePutResponseTests.swift +++ b/Tests/CrudRouterIntegrationTests/CrudRoutePutResponseTests.swift @@ -71,7 +71,7 @@ final class CrudRoutePutResponseTests: XCTestCase { let existingPlanet = Planet(name: "Earth 2", galaxyID: galaxyId) // TODO: I think this test is wrong and shouldn't work - try app.testable().test(.PUT, "/galaxy/\(galaxyId)/planet/", beforeRequest: { req in + try app.testable().test(.PUT, "/galaxy/\(galaxyId)/planet/\(planetId)", beforeRequest: { req in try req.content.encode(existingPlanet) }) { (resp) in XCTAssertEqual(resp.status, .ok) @@ -85,12 +85,17 @@ final class CrudRoutePutResponseTests: XCTestCase { let newPlanets = try app.db.query(Planet.self).all().wait() - XCTAssertEqual(newPlanets.count, 2) + XCTAssertEqual(newPlanets.count, 1) XCTAssert(newPlanets.contains { $0.name == "Earth 2" }) let newEarth = newPlanets.first { $0.name == "Earth 2" } XCTAssertEqual(newEarth?.id, planetId) + XCTAssertEqual(newEarth?.$galaxy.id, BaseGalaxySeeding.milkyWayId) + + let parent = try Galaxy.find(BaseGalaxySeeding.milkyWayId, on: app.db).wait() + let doesContainPlanet = try parent?.$planets.get(on: app.db).wait().contains { $0.name == decoded.name } + XCTAssertEqual(doesContainPlanet, true) } } catch { XCTFail("Probably couldn't decode to public galaxy: \(error.localizedDescription)") @@ -103,39 +108,25 @@ final class CrudRoutePutResponseTests: XCTestCase { } do { - let planetId = UUID() - let existingPlanet = Planet(name: "Earth 2", galaxyID: planetId) - - try app.testable().test(.PUT, "/planet/\(planetId)", beforeRequest: { req in - try req.content.encode(existingPlanet) - }) { (resp) in - XCTAssertEqual(resp.status, .ok) - - let decoded = try resp.content.decode(Planet.self) - - XCTAssertEqual(decoded.name, "Earth 2") - XCTAssertEqual(decoded.id, planetId) - // TODO: This is probably broken - XCTAssertEqual(decoded.$galaxy.id, UUID()) - - - let newPlanets = try app.db.query(Planet.self).all().wait() - - XCTAssertEqual(newPlanets.count, 1) - XCTAssert(newPlanets.contains { $0.name == "Earth 2" }) - - let newMars = newPlanets.first { $0.name == "Earth 2" } - - XCTAssertEqual(newMars?.id, planetId) - } - - let galaxyId = UUID() + let planetId = ChildSeeding.earthId + let galaxyId = BaseGalaxySeeding.milkyWayId let existingGalaxy = Galaxy(id: galaxyId, name: "Milky Way 2") try app.testable().test(.PUT, "/planet/\(planetId)/galaxy", beforeRequest: { req in try req.content.encode(existingGalaxy) }) { (resp) in - XCTAssertEqual(resp.status, .notFound) + XCTAssertEqual(resp.status, .ok) + + let decoded = try resp.content.decode(Galaxy.self) + + XCTAssertEqual(decoded.name, "Milky Way 2") + XCTAssertEqual(decoded.id, galaxyId) + + let childPlanet = try Planet.find(planetId, on: app.db).wait() + let parentGalaxy = try childPlanet?.$galaxy.get(on: app.db).wait() + + XCTAssertEqual(parentGalaxy?.name, "Milky Way 2") + XCTAssertEqual(parentGalaxy?.id, galaxyId) } } catch { XCTFail("Probably couldn't decode to public galaxy: \(error.localizedDescription)") From 06935187b4e4b26e03152f36d054a2d193ecc381 Mon Sep 17 00:00:00 2001 From: twof Date: Sun, 21 Nov 2021 23:11:07 -0800 Subject: [PATCH 29/38] siblings put test passing --- .../CrudSiblingsControllerProtocol.swift | 7 +- .../CrudRouteGetResponseTests.swift | 0 .../CrudRoutePostResponseTests.swift | 0 .../CrudRoutePutResponseTests.swift | 102 ++++++------------ 4 files changed, 35 insertions(+), 74 deletions(-) rename Tests/CrudRouterIntegrationTests/{ => ResponseTests}/CrudRouteGetResponseTests.swift (100%) rename Tests/CrudRouterIntegrationTests/{ => ResponseTests}/CrudRoutePostResponseTests.swift (100%) rename Tests/CrudRouterIntegrationTests/{ => ResponseTests}/CrudRoutePutResponseTests.swift (68%) diff --git a/Sources/CrudRouter/ControllerProtocols/CrudSiblingsControllerProtocol.swift b/Sources/CrudRouter/ControllerProtocols/CrudSiblingsControllerProtocol.swift index 66495b2..3dadca6 100644 --- a/Sources/CrudRouter/ControllerProtocols/CrudSiblingsControllerProtocol.swift +++ b/Sources/CrudRouter/ControllerProtocols/CrudSiblingsControllerProtocol.swift @@ -57,17 +57,16 @@ public extension CrudSiblingsControllerProtocol { let parentId = try req.getId(modelType: ParentType.self) let childId = try req.getId(modelType: ChildType.self) - // TODO: Make sure this actually updates the siblings. Parent is never used. guard - let parent = try await ParentType.find(parentId, on: req.db) + let parent = try await ParentType.find(parentId, on: req.db), + let sibling = try await parent[keyPath: self.siblings].query(on: req.db).filter(\._$id == childId).first() else { throw Abort(.notFound) } let newChild = try req.content.decode(ChildType.self) let temp = newChild - temp.id = childId - + temp._$id.exists = true try await temp.update(on: req.db) return temp } diff --git a/Tests/CrudRouterIntegrationTests/CrudRouteGetResponseTests.swift b/Tests/CrudRouterIntegrationTests/ResponseTests/CrudRouteGetResponseTests.swift similarity index 100% rename from Tests/CrudRouterIntegrationTests/CrudRouteGetResponseTests.swift rename to Tests/CrudRouterIntegrationTests/ResponseTests/CrudRouteGetResponseTests.swift diff --git a/Tests/CrudRouterIntegrationTests/CrudRoutePostResponseTests.swift b/Tests/CrudRouterIntegrationTests/ResponseTests/CrudRoutePostResponseTests.swift similarity index 100% rename from Tests/CrudRouterIntegrationTests/CrudRoutePostResponseTests.swift rename to Tests/CrudRouterIntegrationTests/ResponseTests/CrudRoutePostResponseTests.swift diff --git a/Tests/CrudRouterIntegrationTests/CrudRoutePutResponseTests.swift b/Tests/CrudRouterIntegrationTests/ResponseTests/CrudRoutePutResponseTests.swift similarity index 68% rename from Tests/CrudRouterIntegrationTests/CrudRoutePutResponseTests.swift rename to Tests/CrudRouterIntegrationTests/ResponseTests/CrudRoutePutResponseTests.swift index c4802af..e0441a7 100644 --- a/Tests/CrudRouterIntegrationTests/CrudRoutePutResponseTests.swift +++ b/Tests/CrudRouterIntegrationTests/ResponseTests/CrudRoutePutResponseTests.swift @@ -134,102 +134,64 @@ final class CrudRoutePutResponseTests: XCTestCase { } func testPutSiblings() throws { - app.crud(register: Planet.self) { (controller) in - controller.crud(siblings: \.$tags) + app.crud(register: Planet.self, .only([])) { (controller) in + controller.crud(siblings: \.$tags, .only([.update])) } - app.crud(register: Tag.self) { controller in - controller.crud(siblings: \.$planets) + app.crud(register: Tag.self, .only([])) { controller in + controller.crud(siblings: \.$planets, .only([.update])) } do { - let planetId = UUID() - let existingPlanet = Planet(name: "Earth 2", galaxyID: planetId) - - try app.testable().test(.PUT, "/planet/\(planetId)", beforeRequest: { req in - try req.content.encode(existingPlanet) - }) { (resp) in - XCTAssertEqual(resp.status, .ok) - - let decoded = try resp.content.decode(Planet.self) - - XCTAssertEqual(decoded.name, "Earth 2") - XCTAssertEqual(decoded.id, planetId) - // TODO: Pretty sure this is wront - XCTAssertEqual(decoded.$galaxy.id, UUID()) - - let newPlanets = try app.db.query(Planet.self).all().wait() - - XCTAssertEqual(newPlanets.count, 1) - XCTAssert(newPlanets.contains { $0.name == "Earth 2" }) - - let newMars = newPlanets.first { $0.name == "Earth 2" } - - // TODO: Rename variable - XCTAssertEqual(newMars?.id, planetId) - } + let planetId = ChildSeeding.earthId + let galaxyId = BaseGalaxySeeding.milkyWayId + let existingPlanet = Planet(id: planetId, name: "Earth 2", galaxyID: galaxyId) - let tagId = UUID() + let tagId = SiblingSeeding.lifeSupportingId let existingTag = Tag(id: tagId, name: "Kind of Life Supporting") - try app.testable().test(.PUT, "/tag/\(tagId)", beforeRequest: { req in + try app.testable().test(.PUT, "/planet/\(planetId)/tag/\(tagId)", beforeRequest: { req in try req.content.encode(existingTag) }) { (resp) in XCTAssertEqual(resp.status, .ok) - + let decoded = try resp.content.decode(Tag.self) - XCTAssertEqual(decoded.name, "Kind of Life Supporting") + XCTAssertEqual(decoded.name, existingTag.name) XCTAssertEqual(decoded.id, tagId) - let newTags = try app.db.query(Tag.self).all().wait() + let allTags = try app.db.query(Tag.self).all().wait() - XCTAssertEqual(newTags.count, 1) - XCTAssert(newTags.contains { $0.name == "Kind of Life Supporting" }) + XCTAssertEqual(allTags.count, 1) + XCTAssert(allTags.contains { $0.name == existingTag.name }) - // TODO: rename variable - let newMarsTag = newTags.first { $0.name == "Kind of Life Supporting" } + let earth = try Planet.find(planetId, on: app.db).wait() + let tags = try earth?.$tags.get(on: app.db).wait() + let tagsContainsUpdatedTag = tags?.contains { $0.name == existingTag.name } - XCTAssertEqual(newMarsTag?.id, tagId) + XCTAssertEqual(tagsContainsUpdatedTag, true) } - - let otherTagId = UUID() - let otherExistingTag = Tag(id: otherTagId, name: "Sort of Life Supporting") - try app.testable().test(.PUT, "/planet/\(planetId)/tag/\(otherTagId)", beforeRequest: { req in - try req.content.encode(otherExistingTag) + try app.testable().test(.PUT, "/tag/\(tagId)/planet/\(planetId)", beforeRequest: { req in + try req.content.encode(existingPlanet) }) { (resp) in XCTAssertEqual(resp.status, .ok) - - let decoded = try resp.content.decode(Tag.self) - - XCTAssertEqual(decoded.name, "Sort of Life Supporting") - XCTAssertEqual(decoded.id, otherTagId) - let newTags = try app.db.query(Tag.self).all().wait() + let decoded = try resp.content.decode(Planet.self) - XCTAssertEqual(newTags.count, 1) - XCTAssert(newTags.contains { $0.name == "Sort of Life Supporting" }) + XCTAssertEqual(decoded.name, existingPlanet.name) + XCTAssertEqual(decoded.id, planetId) - // TODO: Rename variable - let newEarthTag = newTags.first { $0.name == "Sort of Life Supporting" } - - XCTAssertEqual(newEarthTag?.id, otherTagId) - } + let allPlanets = try app.db.query(Planet.self).all().wait() - let galaxyId = UUID() - let otherPlanetId = UUID() - let otherExistingPlanet = Planet(id: otherPlanetId, name: "Earth 3", galaxyID: galaxyId) - - try app.testable().test(.PUT, "/tag/\(tagId)/planet/\(otherPlanetId)", beforeRequest: { req in - try req.content.encode(otherExistingPlanet) - }) { (resp) in - XCTAssertEqual(resp.status, .ok) - - let decoded = try resp.content.decode(Planet.self) - - XCTAssertEqual(decoded.name, "Earth") - XCTAssertEqual(decoded.id, otherPlanetId) + XCTAssertEqual(allPlanets.count, 1) + XCTAssert(allPlanets.contains { $0.name == existingPlanet.name }) + + let parentTag = try Tag.find(tagId, on: app.db).wait() + let planets = try parentTag?.$planets.get(on: app.db).wait() + let tagsContainsUpdatedTag = planets?.contains { $0.name == existingPlanet.name } + + XCTAssertEqual(tagsContainsUpdatedTag, true) } } catch { XCTFail("Probably couldn't decode to public galaxy: \(error.localizedDescription)") From b61f37ca90cabbc32e6f9c086402b1f7507d752c Mon Sep 17 00:00:00 2001 From: twof Date: Sun, 21 Nov 2021 23:57:18 -0800 Subject: [PATCH 30/38] all tests now run. Working on recursive routers --- .../CrudChildrenControllerProtocol.swift | 13 ++++++--- .../CrudParentControllerProtocol.swift | 1 - .../CrudSiblingsControllerProtocol.swift | 5 ++-- .../HighRecursiveDepthTests.swift | 28 +++++++++++++++++++ .../CrudRouteGetResponseTests.swift | 3 ++ .../TestMigrations/BaseGalaxySeeding.swift | 4 ++- .../TestMigrations/ChildSeeding.swift | 4 ++- .../TestMigrations/SiblingSeeding.swift | 4 ++- 8 files changed, 52 insertions(+), 10 deletions(-) create mode 100644 Tests/CrudRouterIntegrationTests/HighRecursiveDepthTests.swift diff --git a/Sources/CrudRouter/ControllerProtocols/CrudChildrenControllerProtocol.swift b/Sources/CrudRouter/ControllerProtocols/CrudChildrenControllerProtocol.swift index 25be2f7..b8004e5 100644 --- a/Sources/CrudRouter/ControllerProtocols/CrudChildrenControllerProtocol.swift +++ b/Sources/CrudRouter/ControllerProtocols/CrudChildrenControllerProtocol.swift @@ -19,9 +19,11 @@ public protocol CrudChildrenControllerProtocol { public extension CrudChildrenControllerProtocol { func index(_ req: Request) async throws -> ChildType { let parentId = try req.getId(modelType: ParentType.self) + let childId = try req.getId(modelType: ChildType.self) + guard let parent = try await ParentType.find(parentId, on: req.db), - let child = try await parent[keyPath: self.children].query(on: req.db).first() + let child = try await parent[keyPath: self.children].query(on: req.db).filter(\._$id == childId).first() else { throw Abort(.notFound) } @@ -54,10 +56,11 @@ public extension CrudChildrenControllerProtocol { func update(_ req: Request) async throws -> ChildType { let parentId = try req.getId(modelType: ParentType.self) + let childId = try req.getId(modelType: ChildType.self) guard let parent = try await ParentType.find(parentId, on: req.db), - let oldChild = try await parent[keyPath: self.children].query(on: req.db).first() + try await parent[keyPath: self.children].query(on: req.db).filter(\._$id == childId).first() != nil else { throw Abort(.notFound) } @@ -65,16 +68,18 @@ public extension CrudChildrenControllerProtocol { let newChild = try req.content.decode(ChildType.self) let temp = newChild temp._$id.exists = true - temp.id = oldChild.id + temp.id = childId try await temp.update(on: req.db) return temp } func delete(_ req: Request) async throws -> HTTPStatus { let parentId = try req.getId(modelType: ParentType.self) + let childId = try req.getId(modelType: ChildType.self) + guard let parent = try await ParentType.find(parentId, on: req.db), - let child = try await parent[keyPath: self.children].query(on: req.db).first() + let child = try await parent[keyPath: self.children].query(on: req.db).filter(\._$id == childId).first() else { throw Abort(.notFound) } diff --git a/Sources/CrudRouter/ControllerProtocols/CrudParentControllerProtocol.swift b/Sources/CrudRouter/ControllerProtocols/CrudParentControllerProtocol.swift index b3acfe3..03dfce5 100644 --- a/Sources/CrudRouter/ControllerProtocols/CrudParentControllerProtocol.swift +++ b/Sources/CrudRouter/ControllerProtocols/CrudParentControllerProtocol.swift @@ -25,7 +25,6 @@ public extension CrudParentControllerProtocol { func update(_ req: Request) async throws -> ParentType { let childId = try req.getId(modelType: ChildType.self) let newParent = try req.content.decode(ParentType.self) - // TODO: make sure this actually updates the parent guard let child = try await ChildType.find(childId, on: req.db) else { throw Abort(.notFound) diff --git a/Sources/CrudRouter/ControllerProtocols/CrudSiblingsControllerProtocol.swift b/Sources/CrudRouter/ControllerProtocols/CrudSiblingsControllerProtocol.swift index 3dadca6..eef97ec 100644 --- a/Sources/CrudRouter/ControllerProtocols/CrudSiblingsControllerProtocol.swift +++ b/Sources/CrudRouter/ControllerProtocols/CrudSiblingsControllerProtocol.swift @@ -35,7 +35,7 @@ public extension CrudSiblingsControllerProtocol { // TODO: childId isn't being used. This probably isn't correct. guard let parent = try await ParentType.find(parentId, on: req.db), - let child = try await parent[keyPath: self.siblings].query(on: req.db).first() + let child = try await parent[keyPath: self.siblings].query(on: req.db).filter(\._$id == childId).first() else { throw Abort(.notFound) } @@ -59,7 +59,7 @@ public extension CrudSiblingsControllerProtocol { guard let parent = try await ParentType.find(parentId, on: req.db), - let sibling = try await parent[keyPath: self.siblings].query(on: req.db).filter(\._$id == childId).first() + try await parent[keyPath: self.siblings].query(on: req.db).filter(\._$id == childId).first() != nil else { throw Abort(.notFound) } @@ -67,6 +67,7 @@ public extension CrudSiblingsControllerProtocol { let newChild = try req.content.decode(ChildType.self) let temp = newChild temp._$id.exists = true + temp.id = childId try await temp.update(on: req.db) return temp } diff --git a/Tests/CrudRouterIntegrationTests/HighRecursiveDepthTests.swift b/Tests/CrudRouterIntegrationTests/HighRecursiveDepthTests.swift new file mode 100644 index 0000000..67899e3 --- /dev/null +++ b/Tests/CrudRouterIntegrationTests/HighRecursiveDepthTests.swift @@ -0,0 +1,28 @@ +import XCTest +import XCTVapor +@testable import CrudRouter +import Vapor + +class HighRecursiveDepthTests: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() throws { + let app = Application() + app.crud(register: Galaxy.self) { router in + router.crud(children: \.$planets) { childRouter in + childRouter.crud(parent: \.$galaxy) { parentRouter in + // TODO: This doesn't compile yet + print() + } + } + } + + } +} diff --git a/Tests/CrudRouterIntegrationTests/ResponseTests/CrudRouteGetResponseTests.swift b/Tests/CrudRouterIntegrationTests/ResponseTests/CrudRouteGetResponseTests.swift index fb862c4..f8307b4 100644 --- a/Tests/CrudRouterIntegrationTests/ResponseTests/CrudRouteGetResponseTests.swift +++ b/Tests/CrudRouterIntegrationTests/ResponseTests/CrudRouteGetResponseTests.swift @@ -56,6 +56,9 @@ final class CrudRouteGetResponseTests: XCTestCase { XCTAssert(resp.status == .ok) let decoded = try resp.content.decode([Galaxy].self) + let allGalaxies = try app.db.query(Galaxy.self).all().wait() + print(allGalaxies) + print() XCTAssert(decoded.count == 1) XCTAssert(decoded[0].name == "Milky Way") diff --git a/Tests/CrudRouterIntegrationTests/TestMigrations/BaseGalaxySeeding.swift b/Tests/CrudRouterIntegrationTests/TestMigrations/BaseGalaxySeeding.swift index 7e965a5..3f14279 100644 --- a/Tests/CrudRouterIntegrationTests/TestMigrations/BaseGalaxySeeding.swift +++ b/Tests/CrudRouterIntegrationTests/TestMigrations/BaseGalaxySeeding.swift @@ -22,5 +22,7 @@ struct BaseGalaxySeeding: Migration { } static let milkyWayId = UUID() - static let galaxies = [Galaxy(id: milkyWayId, name: "Milky Way")] + static var galaxies: [Galaxy] { + [Galaxy(id: milkyWayId, name: "Milky Way")] + } } diff --git a/Tests/CrudRouterIntegrationTests/TestMigrations/ChildSeeding.swift b/Tests/CrudRouterIntegrationTests/TestMigrations/ChildSeeding.swift index 448c8a1..9720508 100644 --- a/Tests/CrudRouterIntegrationTests/TestMigrations/ChildSeeding.swift +++ b/Tests/CrudRouterIntegrationTests/TestMigrations/ChildSeeding.swift @@ -24,5 +24,7 @@ struct ChildSeeding: Migration { } static let earthId = UUID() - static let planets = [Planet(id: earthId, name: "Earth", galaxyID: BaseGalaxySeeding.milkyWayId)] + static var planets: [Planet] { + [Planet(id: earthId, name: "Earth", galaxyID: BaseGalaxySeeding.milkyWayId)] + } } diff --git a/Tests/CrudRouterIntegrationTests/TestMigrations/SiblingSeeding.swift b/Tests/CrudRouterIntegrationTests/TestMigrations/SiblingSeeding.swift index 1f0d902..c0f4e47 100644 --- a/Tests/CrudRouterIntegrationTests/TestMigrations/SiblingSeeding.swift +++ b/Tests/CrudRouterIntegrationTests/TestMigrations/SiblingSeeding.swift @@ -28,6 +28,8 @@ struct SiblingSeeding: Migration { } static let lifeSupportingId = UUID() - static var tags = [Tag(id: lifeSupportingId, name: "Life-Supporting")] + static var tags: [Tag] { + [Tag(id: lifeSupportingId, name: "Life-Supporting")] + } } From a6d7db8ef861a34eba19c9dd2144b56d020ad937 Mon Sep 17 00:00:00 2001 From: twof Date: Mon, 22 Nov 2021 00:07:53 -0800 Subject: [PATCH 31/38] some naming refactors for readability --- README.md | 2 +- .../CrudSiblingsControllerProtocol.swift | 1 - .../ControllerProtocols/Crudable.swift | 56 +++++++++---------- .../CrudRouter/CrudChildrenController.swift | 4 +- Sources/CrudRouter/CrudController.swift | 2 +- Sources/CrudRouter/CrudParentController.swift | 6 +- .../CrudRouter/CrudSiblingsController.swift | 2 +- .../CrudRoutePutResponseTests.swift | 1 - 8 files changed, 36 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 25d6d5e..2fc6836 100644 --- a/README.md +++ b/README.md @@ -119,7 +119,7 @@ GET /todo GET /todo/:id/tag/:id ``` -### EventLoopFuture features +### Future features - query parameter support - PATCH support - more fine grained response statuses diff --git a/Sources/CrudRouter/ControllerProtocols/CrudSiblingsControllerProtocol.swift b/Sources/CrudRouter/ControllerProtocols/CrudSiblingsControllerProtocol.swift index eef97ec..341833f 100644 --- a/Sources/CrudRouter/ControllerProtocols/CrudSiblingsControllerProtocol.swift +++ b/Sources/CrudRouter/ControllerProtocols/CrudSiblingsControllerProtocol.swift @@ -32,7 +32,6 @@ public extension CrudSiblingsControllerProtocol { let parentId = try req.getId(modelType: ParentType.self) let childId = try req.getId(modelType: ChildType.self) - // TODO: childId isn't being used. This probably isn't correct. guard let parent = try await ParentType.find(parentId, on: req.db), let child = try await parent[keyPath: self.siblings].query(on: req.db).filter(\._$id == childId).first() diff --git a/Sources/CrudRouter/ControllerProtocols/Crudable.swift b/Sources/CrudRouter/ControllerProtocols/Crudable.swift index 1d14c07..74a5ebb 100644 --- a/Sources/CrudRouter/ControllerProtocols/Crudable.swift +++ b/Sources/CrudRouter/ControllerProtocols/Crudable.swift @@ -2,54 +2,54 @@ import Vapor import Fluent public protocol Crudable: ControllerProtocol { - associatedtype ChildType: Model, Content where ChildType.IDValue: LosslessStringConvertible + associatedtype OriginType: Model, Content where OriginType.IDValue: LosslessStringConvertible func crud( at path: PathComponent..., - parent relation: KeyPath>, + parent relation: KeyPath>, _ either: OnlyExceptEither, - relationConfiguration: ((CrudParentController) -> Void)? + relationConfiguration: ((CrudParentController) -> Void)? ) where ParentType: Model & Content, ParentType.IDValue: LosslessStringConvertible - func crud( + func crud( at path: PathComponent..., - children relation: KeyPath>, + children relation: KeyPath>, _ either: OnlyExceptEither, - relationConfiguration: ((CrudChildrenController) -> Void)? + relationConfiguration: ((CrudChildrenController) -> Void)? ) where - ChildChildType: Model & Content + ChildType: Model & Content - func crud( + func crud( at path: PathComponent..., - siblings relation: KeyPath>, + siblings relation: KeyPath>, _ either: OnlyExceptEither, - relationConfiguration: ((CrudSiblingsController) -> Void)? + relationConfiguration: ((CrudSiblingsController) -> Void)? ) where - ChildChildType: Content, - ChildChildType.IDValue: LosslessStringConvertible, + SiblingType: Content, + SiblingType.IDValue: LosslessStringConvertible, ThroughType: Model } extension Crudable { public func crud( at path: PathComponent..., - parent relation: KeyPath>, + parent relation: KeyPath>, _ either: OnlyExceptEither = .only([.read, .update]), - relationConfiguration: ((CrudParentController) -> Void)?=nil + relationConfiguration: ((CrudParentController) -> Void)?=nil ) where ParentType: Model & Content, ParentType.IDValue: LosslessStringConvertible { - let baseIdPath = self.path.appending(.parameter("\(ChildType.schema)ID")) + let baseIdPath = self.path.appending(.parameter("\(OriginType.schema)ID")) let adjustedPath = path.adjustedPath(for: ParentType.self) let fullPath = baseIdPath + adjustedPath let allMethods: Set = Set([.read, .update]) - let controller: CrudParentController + let controller: CrudParentController switch either { case .only(let methods): @@ -65,26 +65,26 @@ extension Crudable { public func crud( at path: PathComponent..., - children relation: KeyPath>, + children relation: KeyPath>, _ either: OnlyExceptEither = .only([.read, .readAll, .create, .update, .delete]), - relationConfiguration: ((CrudChildrenController) -> Void)?=nil + relationConfiguration: ((CrudChildrenController) -> Void)?=nil ) where ChildChildType: Model & Content, ChildChildType.IDValue: LosslessStringConvertible { - let baseIdPath = self.path.appending(.parameter("\(ChildType.schema)ID")) + let baseIdPath = self.path.appending(.parameter("\(OriginType.schema)ID")) let adjustedPath = path.adjustedPath(for: ChildChildType.self) let fullPath = baseIdPath + adjustedPath let allMethods: Set = Set([.create, .read, .readAll, .update, .delete]) - let controller: CrudChildrenController + let controller: CrudChildrenController switch either { case .only(let methods): - controller = CrudChildrenController(childrenRelation: relation, path: fullPath, router: self.router, activeMethods: Set(methods)) + controller = CrudChildrenController(childrenRelation: relation, path: fullPath, router: self.router, activeMethods: Set(methods)) case .except(let methods): - controller = CrudChildrenController(childrenRelation: relation, path: fullPath, router: self.router, activeMethods: allMethods.subtracting(Set(methods))) + controller = CrudChildrenController(childrenRelation: relation, path: fullPath, router: self.router, activeMethods: allMethods.subtracting(Set(methods))) } do { try controller.boot(routes: self.router) } catch { fatalError("I have no reason to expect boot to throw") } @@ -94,28 +94,28 @@ extension Crudable { public func crud( at path: PathComponent..., - siblings relation: KeyPath>, + siblings relation: KeyPath>, _ either: OnlyExceptEither = .only([.read, .readAll, .create, .update, .delete]), - relationConfiguration: ((CrudSiblingsController) -> Void)?=nil + relationConfiguration: ((CrudSiblingsController) -> Void)?=nil ) where ChildChildType: Content, ChildChildType.IDValue: LosslessStringConvertible, ThroughType: Model { - let baseIdPath = self.path.appending(.parameter("\(ChildType.schema)ID")) + let baseIdPath = self.path.appending(.parameter("\(OriginType.schema)ID")) let adjustedPath = path.adjustedPath(for: ChildChildType.self) let fullPath = baseIdPath + adjustedPath let allMethods: Set = Set([.read, .readAll, .create, .update, .delete]) - let controller: CrudSiblingsController + let controller: CrudSiblingsController switch either { case .only(let methods): - controller = CrudSiblingsController(siblingRelation: relation, path: fullPath, router: + controller = CrudSiblingsController(siblingRelation: relation, path: fullPath, router: self.router, activeMethods: Set(methods)) case .except(let methods): - controller = CrudSiblingsController(siblingRelation: relation, path: fullPath, router: self.router, activeMethods: allMethods.subtracting(Set(methods))) + controller = CrudSiblingsController(siblingRelation: relation, path: fullPath, router: self.router, activeMethods: allMethods.subtracting(Set(methods))) } do { try controller.boot(routes: self.router) } catch { fatalError("I have no reason to expect boot to throw") } diff --git a/Sources/CrudRouter/CrudChildrenController.swift b/Sources/CrudRouter/CrudChildrenController.swift index d662562..532367c 100644 --- a/Sources/CrudRouter/CrudChildrenController.swift +++ b/Sources/CrudRouter/CrudChildrenController.swift @@ -6,7 +6,7 @@ public struct CrudChildrenController> public let path: [PathComponent] @@ -28,7 +28,7 @@ public struct CrudChildrenController: CrudControllerProtocol, Crudable where ModelT.IDValue: LosslessStringConvertible { - public typealias ChildType = ModelT + public typealias OriginType = ModelT public typealias ModelType = ModelT public let path: [PathComponent] diff --git a/Sources/CrudRouter/CrudParentController.swift b/Sources/CrudRouter/CrudParentController.swift index 133f9d8..d4fa9e3 100644 --- a/Sources/CrudRouter/CrudParentController.swift +++ b/Sources/CrudRouter/CrudParentController.swift @@ -5,15 +5,15 @@ public struct CrudParentController> + public let relation: KeyPath> public let path: [PathComponent] public let router: RoutesBuilder let activeMethods: Set init( - relation: KeyPath>, + relation: KeyPath>, path: [PathComponent], router: RoutesBuilder, activeMethods: Set diff --git a/Sources/CrudRouter/CrudSiblingsController.swift b/Sources/CrudRouter/CrudSiblingsController.swift index 87ac129..3dfcce8 100644 --- a/Sources/CrudRouter/CrudSiblingsController.swift +++ b/Sources/CrudRouter/CrudSiblingsController.swift @@ -10,7 +10,7 @@ public struct CrudSiblingsController< ParentType.IDValue: LosslessStringConvertible { public typealias ParentType = ParentType - public typealias ChildType = ChildType + public typealias OriginType = ChildType public typealias ThroughType = ThroughType public var siblings: KeyPath> diff --git a/Tests/CrudRouterIntegrationTests/ResponseTests/CrudRoutePutResponseTests.swift b/Tests/CrudRouterIntegrationTests/ResponseTests/CrudRoutePutResponseTests.swift index e0441a7..35f6529 100644 --- a/Tests/CrudRouterIntegrationTests/ResponseTests/CrudRoutePutResponseTests.swift +++ b/Tests/CrudRouterIntegrationTests/ResponseTests/CrudRoutePutResponseTests.swift @@ -70,7 +70,6 @@ final class CrudRoutePutResponseTests: XCTestCase { let galaxyId = BaseGalaxySeeding.milkyWayId let existingPlanet = Planet(name: "Earth 2", galaxyID: galaxyId) - // TODO: I think this test is wrong and shouldn't work try app.testable().test(.PUT, "/galaxy/\(galaxyId)/planet/\(planetId)", beforeRequest: { req in try req.content.encode(existingPlanet) }) { (resp) in From baa561e00058b3f2b7eabc4fe25b565a02561e1e Mon Sep 17 00:00:00 2001 From: twof Date: Mon, 22 Nov 2021 01:01:02 -0800 Subject: [PATCH 32/38] cleanup. Project not building --- Sources/CrudRouter/ControllerProtocols/Crudable.swift | 6 +++--- .../HighRecursiveDepthTests.swift | 5 +++++ .../ResponseTests/CrudRouteGetResponseTests.swift | 7 ------- .../ResponseTests/CrudRoutePostResponseTests.swift | 7 ------- .../ResponseTests/CrudRoutePutResponseTests.swift | 7 ------- .../TestMigrations/BaseGalaxySeeding.swift | 7 ------- .../TestMigrations/ChildSeeding.swift | 7 ------- .../TestMigrations/SiblingSeeding.swift | 7 ------- 8 files changed, 8 insertions(+), 45 deletions(-) diff --git a/Sources/CrudRouter/ControllerProtocols/Crudable.swift b/Sources/CrudRouter/ControllerProtocols/Crudable.swift index 74a5ebb..68caaca 100644 --- a/Sources/CrudRouter/ControllerProtocols/Crudable.swift +++ b/Sources/CrudRouter/ControllerProtocols/Crudable.swift @@ -8,7 +8,7 @@ public protocol Crudable: ControllerProtocol { at path: PathComponent..., parent relation: KeyPath>, _ either: OnlyExceptEither, - relationConfiguration: ((CrudParentController) -> Void)? + relationConfiguration: ((CrudParentController) -> Void)? ) where ParentType: Model & Content, ParentType.IDValue: LosslessStringConvertible @@ -37,7 +37,7 @@ extension Crudable { at path: PathComponent..., parent relation: KeyPath>, _ either: OnlyExceptEither = .only([.read, .update]), - relationConfiguration: ((CrudParentController) -> Void)?=nil + relationConfiguration: ((CrudParentController) -> Void)?=nil ) where ParentType: Model & Content, ParentType.IDValue: LosslessStringConvertible @@ -49,7 +49,7 @@ extension Crudable { let allMethods: Set = Set([.read, .update]) - let controller: CrudParentController + let controller: CrudParentController switch either { case .only(let methods): diff --git a/Tests/CrudRouterIntegrationTests/HighRecursiveDepthTests.swift b/Tests/CrudRouterIntegrationTests/HighRecursiveDepthTests.swift index 67899e3..eba0246 100644 --- a/Tests/CrudRouterIntegrationTests/HighRecursiveDepthTests.swift +++ b/Tests/CrudRouterIntegrationTests/HighRecursiveDepthTests.swift @@ -24,5 +24,10 @@ class HighRecursiveDepthTests: XCTestCase { } } + app.crud(register: Planet.self) { router in + router.crud(parent: \.$galaxy) { parentRouter in + parentRouter.crud(children: \.$planets) + } + } } } diff --git a/Tests/CrudRouterIntegrationTests/ResponseTests/CrudRouteGetResponseTests.swift b/Tests/CrudRouterIntegrationTests/ResponseTests/CrudRouteGetResponseTests.swift index f8307b4..a02a94c 100644 --- a/Tests/CrudRouterIntegrationTests/ResponseTests/CrudRouteGetResponseTests.swift +++ b/Tests/CrudRouterIntegrationTests/ResponseTests/CrudRouteGetResponseTests.swift @@ -1,10 +1,3 @@ -// -// File.swift -// -// -// Created by fnord on 12/29/19. -// - import FluentSQLiteDriver import Fluent import XCTVapor diff --git a/Tests/CrudRouterIntegrationTests/ResponseTests/CrudRoutePostResponseTests.swift b/Tests/CrudRouterIntegrationTests/ResponseTests/CrudRoutePostResponseTests.swift index 8e2cd4c..c54fc08 100644 --- a/Tests/CrudRouterIntegrationTests/ResponseTests/CrudRoutePostResponseTests.swift +++ b/Tests/CrudRouterIntegrationTests/ResponseTests/CrudRoutePostResponseTests.swift @@ -1,10 +1,3 @@ -// -// CrudRoutePostResponseTests.swift -// AsyncHTTPClient -// -// Created by fnord on 1/4/20. -// - import FluentSQLiteDriver import Fluent import XCTVapor diff --git a/Tests/CrudRouterIntegrationTests/ResponseTests/CrudRoutePutResponseTests.swift b/Tests/CrudRouterIntegrationTests/ResponseTests/CrudRoutePutResponseTests.swift index 35f6529..d0e56c3 100644 --- a/Tests/CrudRouterIntegrationTests/ResponseTests/CrudRoutePutResponseTests.swift +++ b/Tests/CrudRouterIntegrationTests/ResponseTests/CrudRoutePutResponseTests.swift @@ -1,10 +1,3 @@ -// -// CrudRoutePostResponseTests.swift -// AsyncHTTPClient -// -// Created by fnord on 1/4/20. -// - import FluentSQLiteDriver import Fluent import XCTVapor diff --git a/Tests/CrudRouterIntegrationTests/TestMigrations/BaseGalaxySeeding.swift b/Tests/CrudRouterIntegrationTests/TestMigrations/BaseGalaxySeeding.swift index 3f14279..b421c0a 100644 --- a/Tests/CrudRouterIntegrationTests/TestMigrations/BaseGalaxySeeding.swift +++ b/Tests/CrudRouterIntegrationTests/TestMigrations/BaseGalaxySeeding.swift @@ -1,10 +1,3 @@ -// -// File.swift -// -// -// Created by fnord on 12/29/19. -// - import FluentKit import Foundation diff --git a/Tests/CrudRouterIntegrationTests/TestMigrations/ChildSeeding.swift b/Tests/CrudRouterIntegrationTests/TestMigrations/ChildSeeding.swift index 9720508..d28ec3e 100644 --- a/Tests/CrudRouterIntegrationTests/TestMigrations/ChildSeeding.swift +++ b/Tests/CrudRouterIntegrationTests/TestMigrations/ChildSeeding.swift @@ -1,10 +1,3 @@ -// -// File.swift -// -// -// Created by fnord on 12/29/19. -// - import FluentKit import Foundation diff --git a/Tests/CrudRouterIntegrationTests/TestMigrations/SiblingSeeding.swift b/Tests/CrudRouterIntegrationTests/TestMigrations/SiblingSeeding.swift index c0f4e47..9da88e0 100644 --- a/Tests/CrudRouterIntegrationTests/TestMigrations/SiblingSeeding.swift +++ b/Tests/CrudRouterIntegrationTests/TestMigrations/SiblingSeeding.swift @@ -1,10 +1,3 @@ -// -// SiblingSeeding.swift -// AsyncHTTPClient -// -// Created by fnord on 12/29/19. -// - import FluentKit import Foundation From 3c87805e60d75b0e6adf15905153ba074d46a423 Mon Sep 17 00:00:00 2001 From: twof Date: Mon, 22 Nov 2021 01:31:46 -0800 Subject: [PATCH 33/38] using the origintype naming convention --- .../ControllerProtocols/Crudable.swift | 52 ++++++++++--------- .../CrudRouter/CrudChildrenController.swift | 23 ++++---- Sources/CrudRouter/CrudController.swift | 5 -- Sources/CrudRouter/CrudParentController.swift | 1 - .../CrudRouter/CrudSiblingsController.swift | 20 +++---- 5 files changed, 50 insertions(+), 51 deletions(-) diff --git a/Sources/CrudRouter/ControllerProtocols/Crudable.swift b/Sources/CrudRouter/ControllerProtocols/Crudable.swift index 68caaca..a70ccbd 100644 --- a/Sources/CrudRouter/ControllerProtocols/Crudable.swift +++ b/Sources/CrudRouter/ControllerProtocols/Crudable.swift @@ -1,6 +1,11 @@ import Vapor import Fluent +public protocol ControllerProtocol { + var path: [PathComponent] { get } + var router: RoutesBuilder { get } +} + public protocol Crudable: ControllerProtocol { associatedtype OriginType: Model, Content where OriginType.IDValue: LosslessStringConvertible @@ -8,7 +13,7 @@ public protocol Crudable: ControllerProtocol { at path: PathComponent..., parent relation: KeyPath>, _ either: OnlyExceptEither, - relationConfiguration: ((CrudParentController) -> Void)? + relationConfiguration: ((CrudParentController) -> Void)? ) where ParentType: Model & Content, ParentType.IDValue: LosslessStringConvertible @@ -17,7 +22,7 @@ public protocol Crudable: ControllerProtocol { at path: PathComponent..., children relation: KeyPath>, _ either: OnlyExceptEither, - relationConfiguration: ((CrudChildrenController) -> Void)? + relationConfiguration: ((CrudChildrenController) -> Void)? ) where ChildType: Model & Content @@ -25,7 +30,7 @@ public protocol Crudable: ControllerProtocol { at path: PathComponent..., siblings relation: KeyPath>, _ either: OnlyExceptEither, - relationConfiguration: ((CrudSiblingsController) -> Void)? + relationConfiguration: ((CrudSiblingsController) -> Void)? ) where SiblingType: Content, SiblingType.IDValue: LosslessStringConvertible, @@ -37,7 +42,7 @@ extension Crudable { at path: PathComponent..., parent relation: KeyPath>, _ either: OnlyExceptEither = .only([.read, .update]), - relationConfiguration: ((CrudParentController) -> Void)?=nil + relationConfiguration: ((CrudParentController) -> Void)?=nil ) where ParentType: Model & Content, ParentType.IDValue: LosslessStringConvertible @@ -47,9 +52,8 @@ extension Crudable { let fullPath = baseIdPath + adjustedPath - let allMethods: Set = Set([.read, .update]) - let controller: CrudParentController + let controller: CrudParentController switch either { case .only(let methods): @@ -63,28 +67,28 @@ extension Crudable { relationConfiguration?(controller) } - public func crud( + public func crud( at path: PathComponent..., - children relation: KeyPath>, + children relation: KeyPath>, _ either: OnlyExceptEither = .only([.read, .readAll, .create, .update, .delete]), - relationConfiguration: ((CrudChildrenController) -> Void)?=nil + relationConfiguration: ((CrudChildrenController) -> Void)?=nil ) where - ChildChildType: Model & Content, - ChildChildType.IDValue: LosslessStringConvertible + ChildType: Model & Content, + ChildType.IDValue: LosslessStringConvertible { let baseIdPath = self.path.appending(.parameter("\(OriginType.schema)ID")) - let adjustedPath = path.adjustedPath(for: ChildChildType.self) + let adjustedPath = path.adjustedPath(for: ChildType.self) let fullPath = baseIdPath + adjustedPath let allMethods: Set = Set([.create, .read, .readAll, .update, .delete]) - let controller: CrudChildrenController + let controller: CrudChildrenController switch either { case .only(let methods): - controller = CrudChildrenController(childrenRelation: relation, path: fullPath, router: self.router, activeMethods: Set(methods)) + controller = CrudChildrenController(childrenRelation: relation, path: fullPath, router: self.router, activeMethods: Set(methods)) case .except(let methods): - controller = CrudChildrenController(childrenRelation: relation, path: fullPath, router: self.router, activeMethods: allMethods.subtracting(Set(methods))) + controller = CrudChildrenController(childrenRelation: relation, path: fullPath, router: self.router, activeMethods: allMethods.subtracting(Set(methods))) } do { try controller.boot(routes: self.router) } catch { fatalError("I have no reason to expect boot to throw") } @@ -92,30 +96,30 @@ extension Crudable { relationConfiguration?(controller) } - public func crud( + public func crud( at path: PathComponent..., - siblings relation: KeyPath>, + siblings relation: KeyPath>, _ either: OnlyExceptEither = .only([.read, .readAll, .create, .update, .delete]), - relationConfiguration: ((CrudSiblingsController) -> Void)?=nil + relationConfiguration: ((CrudSiblingsController) -> Void)?=nil ) where - ChildChildType: Content, - ChildChildType.IDValue: LosslessStringConvertible, + SiblingType: Content, + SiblingType.IDValue: LosslessStringConvertible, ThroughType: Model { let baseIdPath = self.path.appending(.parameter("\(OriginType.schema)ID")) - let adjustedPath = path.adjustedPath(for: ChildChildType.self) + let adjustedPath = path.adjustedPath(for: SiblingType.self) let fullPath = baseIdPath + adjustedPath let allMethods: Set = Set([.read, .readAll, .create, .update, .delete]) - let controller: CrudSiblingsController + let controller: CrudSiblingsController switch either { case .only(let methods): - controller = CrudSiblingsController(siblingRelation: relation, path: fullPath, router: + controller = CrudSiblingsController(siblingRelation: relation, path: fullPath, router: self.router, activeMethods: Set(methods)) case .except(let methods): - controller = CrudSiblingsController(siblingRelation: relation, path: fullPath, router: self.router, activeMethods: allMethods.subtracting(Set(methods))) + controller = CrudSiblingsController(siblingRelation: relation, path: fullPath, router: self.router, activeMethods: allMethods.subtracting(Set(methods))) } do { try controller.boot(routes: self.router) } catch { fatalError("I have no reason to expect boot to throw") } diff --git a/Sources/CrudRouter/CrudChildrenController.swift b/Sources/CrudRouter/CrudChildrenController.swift index 532367c..9bf64fb 100644 --- a/Sources/CrudRouter/CrudChildrenController.swift +++ b/Sources/CrudRouter/CrudChildrenController.swift @@ -1,19 +1,20 @@ import Vapor import Fluent -public struct CrudChildrenController: CrudChildrenControllerProtocol, Crudable where ChildT.IDValue: LosslessStringConvertible, ParentT.IDValue: LosslessStringConvertible { -// public var db: Database +public struct CrudChildrenController< + OriginType: Model & Content, + ChildType: Model & Content +>: CrudChildrenControllerProtocol, Crudable +where ChildType.IDValue: LosslessStringConvertible, OriginType.IDValue: LosslessStringConvertible +{ public var router: RoutesBuilder - - public typealias ParentType = ParentT - public typealias OriginType = ChildT - public var children: KeyPath> + public var children: KeyPath> public let path: [PathComponent] let activeMethods: Set init( - childrenRelation: KeyPath>, + childrenRelation: KeyPath>, path: [PathComponent], router: RoutesBuilder, activeMethods: Set @@ -27,15 +28,15 @@ public struct CrudChildrenController: CrudControllerProtocol, Crudable where ModelT.IDValue: LosslessStringConvertible { public typealias OriginType = ModelT public typealias ModelType = ModelT diff --git a/Sources/CrudRouter/CrudParentController.swift b/Sources/CrudRouter/CrudParentController.swift index d4fa9e3..5042e67 100644 --- a/Sources/CrudRouter/CrudParentController.swift +++ b/Sources/CrudRouter/CrudParentController.swift @@ -2,7 +2,6 @@ import Vapor import Fluent public struct CrudParentController: CrudParentControllerProtocol, Crudable where ChildT.IDValue: LosslessStringConvertible, ParentT.IDValue: LosslessStringConvertible { -// public var db: Database public typealias ParentType = ParentT public typealias OriginType = ChildT diff --git a/Sources/CrudRouter/CrudSiblingsController.swift b/Sources/CrudRouter/CrudSiblingsController.swift index 3dfcce8..56467e5 100644 --- a/Sources/CrudRouter/CrudSiblingsController.swift +++ b/Sources/CrudRouter/CrudSiblingsController.swift @@ -2,24 +2,24 @@ import Vapor import Fluent public struct CrudSiblingsController< - ChildType: Model & Content, - ParentType: Model & Content, + OriginType: Model & Content, + SiblingType: Model & Content, ThroughType: Model >: CrudSiblingsControllerProtocol, Crudable where - ChildType.IDValue: LosslessStringConvertible, - ParentType.IDValue: LosslessStringConvertible + SiblingType.IDValue: LosslessStringConvertible, + OriginType.IDValue: LosslessStringConvertible { - public typealias ParentType = ParentType - public typealias OriginType = ChildType - public typealias ThroughType = ThroughType +// public typealias ParentType = OriginType +// public typealias OriginType = SiblingType +// public typealias ThroughType = ThroughType - public var siblings: KeyPath> + public var siblings: KeyPath> public let path: [PathComponent] public let router: RoutesBuilder let activeMethods: Set init( - siblingRelation: KeyPath>, + siblingRelation: KeyPath>, path: [PathComponent], router: RoutesBuilder, activeMethods: Set @@ -36,7 +36,7 @@ extension CrudSiblingsController: RouteCollection {} public extension CrudSiblingsController { func boot(routes router: RoutesBuilder) throws { let childPath = self.path - let childIdPath = self.path.appending(.parameter("\(ChildType.schema)ID")) + let childIdPath = self.path.appending(.parameter("\(SiblingType.schema)ID")) self.activeMethods.forEach { $0.register( From 0c3951669bc20dfe0ea3f16dfd4a31d4aaf6ae92 Mon Sep 17 00:00:00 2001 From: twof Date: Mon, 22 Nov 2021 01:34:45 -0800 Subject: [PATCH 34/38] formatting --- Sources/CrudRouter/CrudChildrenController.swift | 5 +++-- Sources/CrudRouter/CrudController.swift | 4 +++- Sources/CrudRouter/CrudParentController.swift | 12 +++++++----- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/Sources/CrudRouter/CrudChildrenController.swift b/Sources/CrudRouter/CrudChildrenController.swift index 9bf64fb..46eb25f 100644 --- a/Sources/CrudRouter/CrudChildrenController.swift +++ b/Sources/CrudRouter/CrudChildrenController.swift @@ -4,8 +4,9 @@ import Fluent public struct CrudChildrenController< OriginType: Model & Content, ChildType: Model & Content ->: CrudChildrenControllerProtocol, Crudable -where ChildType.IDValue: LosslessStringConvertible, OriginType.IDValue: LosslessStringConvertible +>: CrudChildrenControllerProtocol, Crudable where + ChildType.IDValue: LosslessStringConvertible, + OriginType.IDValue: LosslessStringConvertible { public var router: RoutesBuilder diff --git a/Sources/CrudRouter/CrudController.swift b/Sources/CrudRouter/CrudController.swift index db8248f..6dc3383 100644 --- a/Sources/CrudRouter/CrudController.swift +++ b/Sources/CrudRouter/CrudController.swift @@ -1,7 +1,9 @@ import Vapor import Fluent -public struct CrudController: CrudControllerProtocol, Crudable where ModelT.IDValue: LosslessStringConvertible { +public struct CrudController< + ModelT: Model & Content +>: CrudControllerProtocol, Crudable where ModelT.IDValue: LosslessStringConvertible { public typealias OriginType = ModelT public typealias ModelType = ModelT diff --git a/Sources/CrudRouter/CrudParentController.swift b/Sources/CrudRouter/CrudParentController.swift index 5042e67..a9e88e8 100644 --- a/Sources/CrudRouter/CrudParentController.swift +++ b/Sources/CrudRouter/CrudParentController.swift @@ -1,11 +1,13 @@ import Vapor import Fluent -public struct CrudParentController: CrudParentControllerProtocol, Crudable where ChildT.IDValue: LosslessStringConvertible, ParentT.IDValue: LosslessStringConvertible { - - public typealias ParentType = ParentT - public typealias OriginType = ChildT - +public struct CrudParentController< + OriginType: Model & Content, + ParentType: Model & Content +>: CrudParentControllerProtocol, Crudable where + OriginType.IDValue: LosslessStringConvertible, + ParentType.IDValue: LosslessStringConvertible +{ public let relation: KeyPath> public let path: [PathComponent] public let router: RoutesBuilder From 739a4e4c77ce134bd988bef2aa5238610c742615 Mon Sep 17 00:00:00 2001 From: twof Date: Thu, 25 Nov 2021 14:08:40 -0800 Subject: [PATCH 35/38] tests passing --- .../ControllerProtocols/Crudable.swift | 39 ++++++++++--------- .../CrudRouter/CrudChildrenController.swift | 2 + Sources/CrudRouter/CrudController.swift | 8 ++-- Sources/CrudRouter/CrudParentController.swift | 2 + .../CrudRouter/CrudSiblingsController.swift | 1 + .../HighRecursiveDepthTests.swift | 11 ++---- 6 files changed, 33 insertions(+), 30 deletions(-) diff --git a/Sources/CrudRouter/ControllerProtocols/Crudable.swift b/Sources/CrudRouter/ControllerProtocols/Crudable.swift index a70ccbd..3f50dce 100644 --- a/Sources/CrudRouter/ControllerProtocols/Crudable.swift +++ b/Sources/CrudRouter/ControllerProtocols/Crudable.swift @@ -8,29 +8,30 @@ public protocol ControllerProtocol { public protocol Crudable: ControllerProtocol { associatedtype OriginType: Model, Content where OriginType.IDValue: LosslessStringConvertible + associatedtype TargetType: Model, Content where TargetType.IDValue: LosslessStringConvertible func crud( at path: PathComponent..., - parent relation: KeyPath>, + parent relation: KeyPath>, _ either: OnlyExceptEither, - relationConfiguration: ((CrudParentController) -> Void)? + relationConfiguration: ((CrudParentController) -> Void)? ) where ParentType: Model & Content, ParentType.IDValue: LosslessStringConvertible func crud( at path: PathComponent..., - children relation: KeyPath>, + children relation: KeyPath>, _ either: OnlyExceptEither, - relationConfiguration: ((CrudChildrenController) -> Void)? + relationConfiguration: ((CrudChildrenController) -> Void)? ) where ChildType: Model & Content func crud( at path: PathComponent..., - siblings relation: KeyPath>, + siblings relation: KeyPath>, _ either: OnlyExceptEither, - relationConfiguration: ((CrudSiblingsController) -> Void)? + relationConfiguration: ((CrudSiblingsController) -> Void)? ) where SiblingType: Content, SiblingType.IDValue: LosslessStringConvertible, @@ -40,9 +41,9 @@ public protocol Crudable: ControllerProtocol { extension Crudable { public func crud( at path: PathComponent..., - parent relation: KeyPath>, + parent relation: KeyPath>, _ either: OnlyExceptEither = .only([.read, .update]), - relationConfiguration: ((CrudParentController) -> Void)?=nil + relationConfiguration: ((CrudParentController) -> Void)?=nil ) where ParentType: Model & Content, ParentType.IDValue: LosslessStringConvertible @@ -53,7 +54,7 @@ extension Crudable { let fullPath = baseIdPath + adjustedPath let allMethods: Set = Set([.read, .update]) - let controller: CrudParentController + let controller: CrudParentController switch either { case .only(let methods): @@ -69,9 +70,9 @@ extension Crudable { public func crud( at path: PathComponent..., - children relation: KeyPath>, + children relation: KeyPath>, _ either: OnlyExceptEither = .only([.read, .readAll, .create, .update, .delete]), - relationConfiguration: ((CrudChildrenController) -> Void)?=nil + relationConfiguration: ((CrudChildrenController) -> Void)?=nil ) where ChildType: Model & Content, ChildType.IDValue: LosslessStringConvertible @@ -82,13 +83,13 @@ extension Crudable { let fullPath = baseIdPath + adjustedPath let allMethods: Set = Set([.create, .read, .readAll, .update, .delete]) - let controller: CrudChildrenController + let controller: CrudChildrenController switch either { case .only(let methods): - controller = CrudChildrenController(childrenRelation: relation, path: fullPath, router: self.router, activeMethods: Set(methods)) + controller = CrudChildrenController(childrenRelation: relation, path: fullPath, router: self.router, activeMethods: Set(methods)) case .except(let methods): - controller = CrudChildrenController(childrenRelation: relation, path: fullPath, router: self.router, activeMethods: allMethods.subtracting(Set(methods))) + controller = CrudChildrenController(childrenRelation: relation, path: fullPath, router: self.router, activeMethods: allMethods.subtracting(Set(methods))) } do { try controller.boot(routes: self.router) } catch { fatalError("I have no reason to expect boot to throw") } @@ -98,9 +99,9 @@ extension Crudable { public func crud( at path: PathComponent..., - siblings relation: KeyPath>, + siblings relation: KeyPath>, _ either: OnlyExceptEither = .only([.read, .readAll, .create, .update, .delete]), - relationConfiguration: ((CrudSiblingsController) -> Void)?=nil + relationConfiguration: ((CrudSiblingsController) -> Void)?=nil ) where SiblingType: Content, SiblingType.IDValue: LosslessStringConvertible, @@ -112,14 +113,14 @@ extension Crudable { let fullPath = baseIdPath + adjustedPath let allMethods: Set = Set([.read, .readAll, .create, .update, .delete]) - let controller: CrudSiblingsController + let controller: CrudSiblingsController switch either { case .only(let methods): - controller = CrudSiblingsController(siblingRelation: relation, path: fullPath, router: + controller = CrudSiblingsController(siblingRelation: relation, path: fullPath, router: self.router, activeMethods: Set(methods)) case .except(let methods): - controller = CrudSiblingsController(siblingRelation: relation, path: fullPath, router: self.router, activeMethods: allMethods.subtracting(Set(methods))) + controller = CrudSiblingsController(siblingRelation: relation, path: fullPath, router: self.router, activeMethods: allMethods.subtracting(Set(methods))) } do { try controller.boot(routes: self.router) } catch { fatalError("I have no reason to expect boot to throw") } diff --git a/Sources/CrudRouter/CrudChildrenController.swift b/Sources/CrudRouter/CrudChildrenController.swift index 46eb25f..411f781 100644 --- a/Sources/CrudRouter/CrudChildrenController.swift +++ b/Sources/CrudRouter/CrudChildrenController.swift @@ -8,6 +8,8 @@ public struct CrudChildrenController< ChildType.IDValue: LosslessStringConvertible, OriginType.IDValue: LosslessStringConvertible { + public typealias TargetType = ChildType + public var router: RoutesBuilder public var children: KeyPath> diff --git a/Sources/CrudRouter/CrudController.swift b/Sources/CrudRouter/CrudController.swift index 6dc3383..bc9f39e 100644 --- a/Sources/CrudRouter/CrudController.swift +++ b/Sources/CrudRouter/CrudController.swift @@ -2,10 +2,10 @@ import Vapor import Fluent public struct CrudController< - ModelT: Model & Content ->: CrudControllerProtocol, Crudable where ModelT.IDValue: LosslessStringConvertible { - public typealias OriginType = ModelT - public typealias ModelType = ModelT + OriginType: Model & Content +>: CrudControllerProtocol, Crudable where OriginType.IDValue: LosslessStringConvertible { + public typealias ModelType = OriginType + public typealias TargetType = OriginType public let path: [PathComponent] public let router: RoutesBuilder diff --git a/Sources/CrudRouter/CrudParentController.swift b/Sources/CrudRouter/CrudParentController.swift index a9e88e8..150031a 100644 --- a/Sources/CrudRouter/CrudParentController.swift +++ b/Sources/CrudRouter/CrudParentController.swift @@ -8,6 +8,8 @@ public struct CrudParentController< OriginType.IDValue: LosslessStringConvertible, ParentType.IDValue: LosslessStringConvertible { + public typealias TargetType = ParentType + public let relation: KeyPath> public let path: [PathComponent] public let router: RoutesBuilder diff --git a/Sources/CrudRouter/CrudSiblingsController.swift b/Sources/CrudRouter/CrudSiblingsController.swift index 56467e5..7f0b4ad 100644 --- a/Sources/CrudRouter/CrudSiblingsController.swift +++ b/Sources/CrudRouter/CrudSiblingsController.swift @@ -12,6 +12,7 @@ public struct CrudSiblingsController< // public typealias ParentType = OriginType // public typealias OriginType = SiblingType // public typealias ThroughType = ThroughType + public typealias TargetType = SiblingType public var siblings: KeyPath> public let path: [PathComponent] diff --git a/Tests/CrudRouterIntegrationTests/HighRecursiveDepthTests.swift b/Tests/CrudRouterIntegrationTests/HighRecursiveDepthTests.swift index eba0246..f9824f3 100644 --- a/Tests/CrudRouterIntegrationTests/HighRecursiveDepthTests.swift +++ b/Tests/CrudRouterIntegrationTests/HighRecursiveDepthTests.swift @@ -17,16 +17,13 @@ class HighRecursiveDepthTests: XCTestCase { let app = Application() app.crud(register: Galaxy.self) { router in router.crud(children: \.$planets) { childRouter in - childRouter.crud(parent: \.$galaxy) { parentRouter in - // TODO: This doesn't compile yet - print() - } + childRouter.crud(siblings: \Planet.$tags) } } - app.crud(register: Planet.self) { router in - router.crud(parent: \.$galaxy) { parentRouter in - parentRouter.crud(children: \.$planets) + app.crud(register: Planet.self) { (router: CrudController) in + router.crud(parent: \Planet.$galaxy) { (parentRouter: CrudParentController.OriginType, Galaxy>) in +// parentRouter.crud(children: \Galaxy.$planets) } } } From c7c36c2fdffd7dde4f59b3d3fccb3195f8e1e561 Mon Sep 17 00:00:00 2001 From: twof Date: Thu, 25 Nov 2021 21:23:46 -0800 Subject: [PATCH 36/38] cleanup --- .../CrudChildrenControllerProtocol.swift | 6 +- .../CrudControllerProtocol.swift | 4 +- .../CrudParentControllerProtocol.swift | 6 +- .../CrudSiblingsControllerProtocol.swift | 20 +-- .../CrudRouter/CrudChildrenController.swift | 2 +- .../CrudRouter/CrudSiblingsController.swift | 3 - .../HighRecursiveDepthTests.swift | 27 ++-- .../PublicAPITests.swift | 17 +++ .../CrudRouteDeleteResponseTests.swift | 129 ++++++++++++++++++ .../CrudRouteGetResponseTests.swift | 3 - .../CrudRoutePostResponseTests.swift | 3 - .../CrudRoutePutResponseTests.swift | 3 - .../TestMigrations/SiblingSeeding.swift | 2 +- .../TestModels/Galaxy.swift | 18 ++- .../TestModels/Planet.swift | 24 ++-- .../TestModels/PlanetTag.swift | 2 +- .../TestModels/Tag.swift | 22 +-- .../XCTestManifests.swift | 9 -- Tests/LinuxMain.swift | 7 - 19 files changed, 199 insertions(+), 108 deletions(-) create mode 100644 Tests/CrudRouterIntegrationTests/PublicAPITests.swift create mode 100644 Tests/CrudRouterIntegrationTests/ResponseTests/CrudRouteDeleteResponseTests.swift delete mode 100644 Tests/CrudRouterIntegrationTests/XCTestManifests.swift delete mode 100644 Tests/LinuxMain.swift diff --git a/Sources/CrudRouter/ControllerProtocols/CrudChildrenControllerProtocol.swift b/Sources/CrudRouter/ControllerProtocols/CrudChildrenControllerProtocol.swift index b8004e5..c6d873a 100644 --- a/Sources/CrudRouter/ControllerProtocols/CrudChildrenControllerProtocol.swift +++ b/Sources/CrudRouter/ControllerProtocols/CrudChildrenControllerProtocol.swift @@ -1,9 +1,7 @@ import Vapor import FluentKit -import Fluent -import NIOExtras -public protocol CrudChildrenControllerProtocol { +protocol CrudChildrenControllerProtocol { associatedtype ParentType: Model & Content where ParentType.IDValue: LosslessStringConvertible associatedtype ChildType: Model & Content where ChildType.IDValue: LosslessStringConvertible @@ -16,7 +14,7 @@ public protocol CrudChildrenControllerProtocol { func delete(_ req: Request) async throws -> HTTPStatus } -public extension CrudChildrenControllerProtocol { +extension CrudChildrenControllerProtocol { func index(_ req: Request) async throws -> ChildType { let parentId = try req.getId(modelType: ParentType.self) let childId = try req.getId(modelType: ChildType.self) diff --git a/Sources/CrudRouter/ControllerProtocols/CrudControllerProtocol.swift b/Sources/CrudRouter/ControllerProtocols/CrudControllerProtocol.swift index 31d70c8..d42d9db 100644 --- a/Sources/CrudRouter/ControllerProtocols/CrudControllerProtocol.swift +++ b/Sources/CrudRouter/ControllerProtocols/CrudControllerProtocol.swift @@ -1,7 +1,7 @@ import Vapor import Fluent -public protocol CrudControllerProtocol { +protocol CrudControllerProtocol { associatedtype ModelType: Model, Content where ModelType.IDValue: LosslessStringConvertible func indexAll(_ req: Request) async throws -> [ModelType] @@ -11,7 +11,7 @@ public protocol CrudControllerProtocol { func delete(_ req: Request) async throws -> HTTPStatus } -public extension CrudControllerProtocol { +extension CrudControllerProtocol { func indexAll(_ req: Request) async throws -> [ModelType] { return try await ModelType.query(on: req.db).all() } diff --git a/Sources/CrudRouter/ControllerProtocols/CrudParentControllerProtocol.swift b/Sources/CrudRouter/ControllerProtocols/CrudParentControllerProtocol.swift index 03dfce5..234fd1b 100644 --- a/Sources/CrudRouter/ControllerProtocols/CrudParentControllerProtocol.swift +++ b/Sources/CrudRouter/ControllerProtocols/CrudParentControllerProtocol.swift @@ -1,7 +1,7 @@ import Vapor -import Fluent +import FluentKit -public protocol CrudParentControllerProtocol { +protocol CrudParentControllerProtocol { associatedtype ParentType: Model & Content where ParentType.IDValue: LosslessStringConvertible associatedtype ChildType: Model & Content where ChildType.IDValue: LosslessStringConvertible @@ -11,7 +11,7 @@ public protocol CrudParentControllerProtocol { func update(_ req: Request) async throws -> ParentType } -public extension CrudParentControllerProtocol { +extension CrudParentControllerProtocol { func index(_ req: Request) async throws -> ParentType { let childId = try req.getId(modelType: ChildType.self) diff --git a/Sources/CrudRouter/ControllerProtocols/CrudSiblingsControllerProtocol.swift b/Sources/CrudRouter/ControllerProtocols/CrudSiblingsControllerProtocol.swift index 341833f..9452ff4 100644 --- a/Sources/CrudRouter/ControllerProtocols/CrudSiblingsControllerProtocol.swift +++ b/Sources/CrudRouter/ControllerProtocols/CrudSiblingsControllerProtocol.swift @@ -1,21 +1,7 @@ import Vapor -import Fluent import FluentKit -extension EventLoopFuture { - func throwingFlatMap(file: StaticString = #file, line: UInt = #line, _ callback: @escaping ((Value) throws -> EventLoopFuture)) rethrows -> EventLoopFuture { - return self.flatMap(file: file, line: line) { (value: Value) -> EventLoopFuture in - do { - return try callback(value) - } catch { - return self.eventLoop.makeFailedFuture(error) - } - } - } -} - -// TODO: Do these proocols actually need to be public? -public protocol CrudSiblingsControllerProtocol { +protocol CrudSiblingsControllerProtocol { associatedtype ParentType: Model & Content where ParentType.IDValue: LosslessStringConvertible associatedtype ChildType: Model & Content where ChildType.IDValue: LosslessStringConvertible associatedtype ThroughType: Model @@ -27,7 +13,7 @@ public protocol CrudSiblingsControllerProtocol { func update(_ req: Request) async throws -> ChildType } -public extension CrudSiblingsControllerProtocol { +extension CrudSiblingsControllerProtocol { func index(_ req: Request) async throws -> ChildType { let parentId = try req.getId(modelType: ParentType.self) let childId = try req.getId(modelType: ChildType.self) @@ -70,9 +56,7 @@ public extension CrudSiblingsControllerProtocol { try await temp.update(on: req.db) return temp } -} -public extension CrudSiblingsControllerProtocol { func create(_ req: Request) async throws -> ChildType { let parentId = try req.getId(modelType: ParentType.self) diff --git a/Sources/CrudRouter/CrudChildrenController.swift b/Sources/CrudRouter/CrudChildrenController.swift index 411f781..b13273a 100644 --- a/Sources/CrudRouter/CrudChildrenController.swift +++ b/Sources/CrudRouter/CrudChildrenController.swift @@ -12,7 +12,7 @@ public struct CrudChildrenController< public var router: RoutesBuilder - public var children: KeyPath> + var children: KeyPath> public let path: [PathComponent] let activeMethods: Set diff --git a/Sources/CrudRouter/CrudSiblingsController.swift b/Sources/CrudRouter/CrudSiblingsController.swift index 7f0b4ad..817dad3 100644 --- a/Sources/CrudRouter/CrudSiblingsController.swift +++ b/Sources/CrudRouter/CrudSiblingsController.swift @@ -9,9 +9,6 @@ public struct CrudSiblingsController< SiblingType.IDValue: LosslessStringConvertible, OriginType.IDValue: LosslessStringConvertible { -// public typealias ParentType = OriginType -// public typealias OriginType = SiblingType -// public typealias ThroughType = ThroughType public typealias TargetType = SiblingType public var siblings: KeyPath> diff --git a/Tests/CrudRouterIntegrationTests/HighRecursiveDepthTests.swift b/Tests/CrudRouterIntegrationTests/HighRecursiveDepthTests.swift index f9824f3..2199c39 100644 --- a/Tests/CrudRouterIntegrationTests/HighRecursiveDepthTests.swift +++ b/Tests/CrudRouterIntegrationTests/HighRecursiveDepthTests.swift @@ -1,29 +1,22 @@ import XCTest -import XCTVapor @testable import CrudRouter import Vapor class HighRecursiveDepthTests: XCTestCase { - - override func setUpWithError() throws { - // Put setup code here. This method is called before the invocation of each test method in the class. - } - - override func tearDownWithError() throws { - // Put teardown code here. This method is called after the invocation of each test method in the class. - } - func testExample() throws { let app = Application() + app.crud(register: Galaxy.self) { router in router.crud(children: \.$planets) { childRouter in - childRouter.crud(siblings: \Planet.$tags) - } - } - - app.crud(register: Planet.self) { (router: CrudController) in - router.crud(parent: \Planet.$galaxy) { (parentRouter: CrudParentController.OriginType, Galaxy>) in -// parentRouter.crud(children: \Galaxy.$planets) + childRouter.crud(siblings: \.$tags) { tagRouter in + tagRouter.crud(siblings: \.$planets) { childRouter in + childRouter.crud(siblings: \.$tags) { tagRouter in + tagRouter.crud(siblings: \.$planets) { planetRouter in + planetRouter.crud(parent: \.$galaxy) + } + } + } + } } } } diff --git a/Tests/CrudRouterIntegrationTests/PublicAPITests.swift b/Tests/CrudRouterIntegrationTests/PublicAPITests.swift new file mode 100644 index 0000000..964a7fb --- /dev/null +++ b/Tests/CrudRouterIntegrationTests/PublicAPITests.swift @@ -0,0 +1,17 @@ +import XCTest +import CrudRouter +import Vapor + +class PublicAPITest: XCTestCase { + func testExample() throws { + let app = Application() + app.crud("foo", register: Planet.self, .only([.delete])) { router in + router.crud(at: "foo", siblings: \.$tags, .only([])) + router.crud(at: "foo", parent: \.$galaxy, .only([])) + } + + app.crud("foo", register: Galaxy.self, .only([.delete])) { router in + router.crud(at: "foo", children: \.$planets, .only([])) + } + } +} diff --git a/Tests/CrudRouterIntegrationTests/ResponseTests/CrudRouteDeleteResponseTests.swift b/Tests/CrudRouterIntegrationTests/ResponseTests/CrudRouteDeleteResponseTests.swift new file mode 100644 index 0000000..27e6efc --- /dev/null +++ b/Tests/CrudRouterIntegrationTests/ResponseTests/CrudRouteDeleteResponseTests.swift @@ -0,0 +1,129 @@ +import FluentSQLiteDriver +import Fluent +import XCTVapor + +final class CrudRouteDeleteResponseTests: XCTestCase { + var app: Application! + + override func setUp() { + super.setUp() + + app = Application(.testing) + try! configure(app) + + try! app.autoRevert().wait() + try! app.autoMigrate().wait() + } + + override func tearDown() { + super.tearDown() + + app.shutdown() + } + + func testDeleteBase() throws { + app.crud(register: Galaxy.self, .only([.delete])) + + do { + let allGalaxies = try app.db.query(Galaxy.self).all().wait() + + XCTAssertEqual(allGalaxies.count, 1) + try app.testable().test(.DELETE, "/galaxy/\(BaseGalaxySeeding.milkyWayId)") { (resp) in + XCTAssert(resp.status == .ok) + + let allGalaxies = try app.db.query(Galaxy.self).all().wait() + + XCTAssertEqual(allGalaxies.count, 0) + } + } catch { + XCTFail("Probably couldn't decode to public galaxy: \(error.localizedDescription)") + } + } + + func testDeleteChild() throws { + app.crud(register: Galaxy.self, .only([])) { (controller) in + controller.crud(children: \.$planets, .only([.delete])) + } + + do { + let galaxy = try Galaxy.find(BaseGalaxySeeding.milkyWayId, on: app.db).wait() + let children = try galaxy!.$planets.get(on: app.db).wait() + + XCTAssertEqual(children.count, 1) + + try app.testable().test(.DELETE, "/galaxy/\(BaseGalaxySeeding.milkyWayId)/planet/\(ChildSeeding.earthId)") { (resp) in + XCTAssert(resp.status == .ok) + + let galaxy = try Galaxy.find(BaseGalaxySeeding.milkyWayId, on: app.db).wait() + let children = try galaxy!.$planets.get(on: app.db).wait() + + XCTAssertEqual(children.count, 0) + } + } catch { + XCTFail("Probably couldn't decode to public galaxy: \(error.localizedDescription)") + } + } + + func testDeleteSiblingTags() async throws { + app.crud(register: Planet.self, .only([])) { controller in + controller.crud(siblings: \.$tags, .only([.delete])) + } + + do { + let planet = try Planet.find(ChildSeeding.earthId, on: app.db).wait() + let tagSiblings = try planet!.$tags.get(on: app.db).wait() + + XCTAssertEqual(tagSiblings.count, 1) + + try app.testable().test(.DELETE, "/planet/\(ChildSeeding.earthId)/tag/\(SiblingSeeding.lifeSupportingId)") { (resp) in + XCTAssertEqual(resp.status, .ok) + + let planet = try Planet.find(ChildSeeding.earthId, on: app.db).wait() + let tagSiblings = try planet!.$tags.get(on: app.db).wait() + + XCTAssertEqual(tagSiblings.count, 0) + } + } catch { + XCTFail("Probably couldn't decode to public galaxy: \(error.localizedDescription)") + } + } + + func testDeleteSiblingPlanets() async throws { + app.crud(register: Tag.self, .only([])) { controller in + controller.crud(siblings: \.$planets, .only([.delete])) + } + + do { + let tag = try Tag.find(SiblingSeeding.lifeSupportingId, on: app.db).wait() + let planetSiblings = try tag!.$planets.get(on: app.db).wait() + + XCTAssertEqual(planetSiblings.count, 1) + + try app.testable().test(.DELETE, "/tag/\(SiblingSeeding.lifeSupportingId)/planet/\(ChildSeeding.earthId)") { (resp) in + XCTAssertEqual(resp.status, .ok) + + let tag = try Tag.find(SiblingSeeding.lifeSupportingId, on: app.db).wait() + let planetSiblings = try tag!.$planets.get(on: app.db).wait() + + XCTAssertEqual(planetSiblings.count, 0) + } + } catch { + XCTFail("Probably couldn't decode to public galaxy: \(error.localizedDescription)") + } + } + + private func configure(_ app: Application) throws { + // Configure SQLite database + app.databases.use(.sqlite(), as: .sqlite) + + // Configure migrations + app.migrations.add(GalaxyMigration()) + app.migrations.add(PlanetMigration()) + app.migrations.add(PlanetTagMigration()) + app.migrations.add(TagMigration()) + + app.migrations.add(BaseGalaxySeeding()) + app.migrations.add(ChildSeeding()) + app.migrations.add(SiblingSeeding()) + } +} diff --git a/Tests/CrudRouterIntegrationTests/ResponseTests/CrudRouteGetResponseTests.swift b/Tests/CrudRouterIntegrationTests/ResponseTests/CrudRouteGetResponseTests.swift index a02a94c..1745919 100644 --- a/Tests/CrudRouterIntegrationTests/ResponseTests/CrudRouteGetResponseTests.swift +++ b/Tests/CrudRouterIntegrationTests/ResponseTests/CrudRouteGetResponseTests.swift @@ -156,9 +156,6 @@ final class CrudRouteGetResponseTests: XCTestCase { } private func configure(_ app: Application) throws { - // Serves files from `Public/` directory - // app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory)) - // Configure SQLite database app.databases.use(.sqlite(), as: .sqlite) diff --git a/Tests/CrudRouterIntegrationTests/ResponseTests/CrudRoutePostResponseTests.swift b/Tests/CrudRouterIntegrationTests/ResponseTests/CrudRoutePostResponseTests.swift index c54fc08..5665f34 100644 --- a/Tests/CrudRouterIntegrationTests/ResponseTests/CrudRoutePostResponseTests.swift +++ b/Tests/CrudRouterIntegrationTests/ResponseTests/CrudRoutePostResponseTests.swift @@ -253,9 +253,6 @@ final class CrudRoutePostResponseTests: XCTestCase { } private func configure(_ app: Application) throws { - // Serves files from `Public/` directory - // app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory)) - // Configure SQLite database app.databases.use(.sqlite(), as: .sqlite) diff --git a/Tests/CrudRouterIntegrationTests/ResponseTests/CrudRoutePutResponseTests.swift b/Tests/CrudRouterIntegrationTests/ResponseTests/CrudRoutePutResponseTests.swift index d0e56c3..957ee1a 100644 --- a/Tests/CrudRouterIntegrationTests/ResponseTests/CrudRoutePutResponseTests.swift +++ b/Tests/CrudRouterIntegrationTests/ResponseTests/CrudRoutePutResponseTests.swift @@ -191,9 +191,6 @@ final class CrudRoutePutResponseTests: XCTestCase { } private func configure(_ app: Application) throws { - // Serves files from `Public/` directory - // app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory)) - // Configure SQLite database app.databases.use(.sqlite(), as: .sqlite) diff --git a/Tests/CrudRouterIntegrationTests/TestMigrations/SiblingSeeding.swift b/Tests/CrudRouterIntegrationTests/TestMigrations/SiblingSeeding.swift index 9da88e0..af31404 100644 --- a/Tests/CrudRouterIntegrationTests/TestMigrations/SiblingSeeding.swift +++ b/Tests/CrudRouterIntegrationTests/TestMigrations/SiblingSeeding.swift @@ -7,7 +7,7 @@ struct SiblingSeeding: Migration { return tag .save(on: database) .transform(to: ()) - .map { tag.$planets.attach(ChildSeeding.planets[0], on: database) } + .flatMap { tag.$planets.attach(ChildSeeding.planets[0], on: database).transform(to: ()) } }.flatten(on: database.eventLoop) } diff --git a/Tests/CrudRouterIntegrationTests/TestModels/Galaxy.swift b/Tests/CrudRouterIntegrationTests/TestModels/Galaxy.swift index 931e949..d90af00 100644 --- a/Tests/CrudRouterIntegrationTests/TestModels/Galaxy.swift +++ b/Tests/CrudRouterIntegrationTests/TestModels/Galaxy.swift @@ -1,27 +1,25 @@ import Vapor import FluentKit -import CrudRouter -import Foundation -public final class Galaxy: Model, Content { - public static let schema = "galaxies" +final class Galaxy: Model, Content { + static let schema = "galaxies" - public static var migration: Migration { + static var migration: Migration { return GalaxyMigration() } @ID(key: .id) - public var id: UUID? + var id: UUID? @Field(key: "name") - public var name: String + var name: String @Children(for: \.$galaxy) - public var planets: [Planet] + var planets: [Planet] - public init() { } + init() { } - public init(id: UUID? = nil, name: String) { + init(id: UUID? = nil, name: String) { self.id = id self.name = name } diff --git a/Tests/CrudRouterIntegrationTests/TestModels/Planet.swift b/Tests/CrudRouterIntegrationTests/TestModels/Planet.swift index 1010d7a..0219115 100644 --- a/Tests/CrudRouterIntegrationTests/TestModels/Planet.swift +++ b/Tests/CrudRouterIntegrationTests/TestModels/Planet.swift @@ -1,32 +1,32 @@ -import FluentSQLiteDriver +import FluentKit import Vapor -public final class Planet: Model, Content { - public static let schema = "planets" +final class Planet: Model, Content { + static let schema = "planets" @ID(key: .id) - public var id: UUID? + var id: UUID? @Field(key: "name") - public var name: String + var name: String @Parent(key: "galaxy_id") - public var galaxy: Galaxy + var galaxy: Galaxy @Siblings(through: PlanetTag.self, from: \.$planet, to: \.$tag) - public var tags: [Tag] + var tags: [Tag] - public init() { } + init() { } - public init(id: UUID? = nil, name: String, galaxyID: Galaxy.IDValue) { + init(id: UUID? = nil, name: String, galaxyID: Galaxy.IDValue) { self.id = id self.name = name self.$galaxy.id = galaxyID } } -public struct PlanetMigration: Migration { - public func prepare(on database: Database) -> EventLoopFuture { +struct PlanetMigration: Migration { + func prepare(on database: Database) -> EventLoopFuture { return database.schema("planets") .field("id", .uuid, .identifier(auto: true)) .field("name", .string, .required) @@ -34,7 +34,7 @@ public struct PlanetMigration: Migration { .create() } - public func revert(on database: Database) -> EventLoopFuture { + func revert(on database: Database) -> EventLoopFuture { return database.schema("planets").delete() } } diff --git a/Tests/CrudRouterIntegrationTests/TestModels/PlanetTag.swift b/Tests/CrudRouterIntegrationTests/TestModels/PlanetTag.swift index b642f31..c9c5693 100644 --- a/Tests/CrudRouterIntegrationTests/TestModels/PlanetTag.swift +++ b/Tests/CrudRouterIntegrationTests/TestModels/PlanetTag.swift @@ -1,4 +1,4 @@ -import FluentSQLiteDriver +import FluentKit import Foundation final class PlanetTag: Model { diff --git a/Tests/CrudRouterIntegrationTests/TestModels/Tag.swift b/Tests/CrudRouterIntegrationTests/TestModels/Tag.swift index 6440418..0420f2d 100644 --- a/Tests/CrudRouterIntegrationTests/TestModels/Tag.swift +++ b/Tests/CrudRouterIntegrationTests/TestModels/Tag.swift @@ -1,35 +1,35 @@ -import FluentSQLiteDriver +import FluentKit import Vapor -public final class Tag: Model, Content { - public static let schema = "tags" +final class Tag: Model, Content { + static let schema = "tags" @ID(key: .id) - public var id: UUID? + var id: UUID? @Field(key: "name") - public var name: String + var name: String @Siblings(through: PlanetTag.self, from: \.$tag, to: \.$planet) - public var planets: [Planet] + var planets: [Planet] - public init() { } + init() { } - public init(id: UUID? = nil, name: String) { + init(id: UUID? = nil, name: String) { self.id = id self.name = name } } -public struct TagMigration: Migration { - public func prepare(on database: Database) -> EventLoopFuture { +struct TagMigration: Migration { + func prepare(on database: Database) -> EventLoopFuture { return database.schema("tags") .field("id", .uuid, .identifier(auto: true)) .field("name", .string, .required) .create() } - public func revert(on database: Database) -> EventLoopFuture { + func revert(on database: Database) -> EventLoopFuture { return database.schema("tags").delete() } } diff --git a/Tests/CrudRouterIntegrationTests/XCTestManifests.swift b/Tests/CrudRouterIntegrationTests/XCTestManifests.swift deleted file mode 100644 index eca492b..0000000 --- a/Tests/CrudRouterIntegrationTests/XCTestManifests.swift +++ /dev/null @@ -1,9 +0,0 @@ -import XCTest - -#if !os(macOS) -public func allTests() -> [XCTestCaseEntry] { - return [ - testCase(CrudRouterTests.allTests), - ] -} -#endif \ No newline at end of file diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift deleted file mode 100644 index 29573f4..0000000 --- a/Tests/LinuxMain.swift +++ /dev/null @@ -1,7 +0,0 @@ -import XCTest - -import CrudRouterTests - -var tests = [XCTestCaseEntry]() -tests += CrudRouterTests.allTests() -XCTMain(tests) \ No newline at end of file From 483db8bbdb2a9e9d8897569e2d1882b881016480 Mon Sep 17 00:00:00 2001 From: twof Date: Thu, 25 Nov 2021 23:43:28 -0800 Subject: [PATCH 37/38] return responses with the correct status codes --- .../CrudChildrenControllerProtocol.swift | 31 ++++++------ .../CrudControllerProtocol.swift | 31 ++++++------ .../CrudParentControllerProtocol.swift | 13 ++--- .../CrudSiblingsControllerProtocol.swift | 49 ++++++++++--------- .../CrudRouteDeleteResponseTests.swift | 8 +-- .../CrudRoutePostResponseTests.swift | 16 +++--- .../TestMigrations/BaseGalaxySeeding.swift | 0 .../TestMigrations/ChildSeeding.swift | 0 .../TestMigrations/SiblingSeeding.swift | 0 .../{ => TestSetup}/TestModels/Galaxy.swift | 0 .../{ => TestSetup}/TestModels/Planet.swift | 0 .../TestModels/PlanetTag.swift | 0 .../{ => TestSetup}/TestModels/Tag.swift | 0 13 files changed, 77 insertions(+), 71 deletions(-) rename Tests/CrudRouterIntegrationTests/{ => TestSetup}/TestMigrations/BaseGalaxySeeding.swift (100%) rename Tests/CrudRouterIntegrationTests/{ => TestSetup}/TestMigrations/ChildSeeding.swift (100%) rename Tests/CrudRouterIntegrationTests/{ => TestSetup}/TestMigrations/SiblingSeeding.swift (100%) rename Tests/CrudRouterIntegrationTests/{ => TestSetup}/TestModels/Galaxy.swift (100%) rename Tests/CrudRouterIntegrationTests/{ => TestSetup}/TestModels/Planet.swift (100%) rename Tests/CrudRouterIntegrationTests/{ => TestSetup}/TestModels/PlanetTag.swift (100%) rename Tests/CrudRouterIntegrationTests/{ => TestSetup}/TestModels/Tag.swift (100%) diff --git a/Sources/CrudRouter/ControllerProtocols/CrudChildrenControllerProtocol.swift b/Sources/CrudRouter/ControllerProtocols/CrudChildrenControllerProtocol.swift index c6d873a..f569482 100644 --- a/Sources/CrudRouter/ControllerProtocols/CrudChildrenControllerProtocol.swift +++ b/Sources/CrudRouter/ControllerProtocols/CrudChildrenControllerProtocol.swift @@ -7,15 +7,15 @@ protocol CrudChildrenControllerProtocol { var children: KeyPath> { get } - func index(_ req: Request) async throws -> ChildType - func indexAll(_ req: Request) async throws -> [ChildType] - func create(_ req: Request) async throws -> ChildType - func update(_ req: Request) async throws -> ChildType - func delete(_ req: Request) async throws -> HTTPStatus + func index(_ req: Request) async throws -> Response + func indexAll(_ req: Request) async throws -> Response + func create(_ req: Request) async throws -> Response + func update(_ req: Request) async throws -> Response + func delete(_ req: Request) async throws -> Response } extension CrudChildrenControllerProtocol { - func index(_ req: Request) async throws -> ChildType { + func index(_ req: Request) async throws -> Response { let parentId = try req.getId(modelType: ParentType.self) let childId = try req.getId(modelType: ChildType.self) @@ -26,20 +26,21 @@ extension CrudChildrenControllerProtocol { throw Abort(.notFound) } - return child + return try await child.encodeResponse(status: .ok, for: req) } - func indexAll(_ req: Request) async throws -> [ChildType] { + func indexAll(_ req: Request) async throws -> Response { let parentId = try req.getId(modelType: ParentType.self) guard let parent = try await ParentType.find(parentId, on: req.db) else { throw Abort(.notFound) } - return try await parent[keyPath: self.children].query(on: req.db).all() + let children = try await parent[keyPath: self.children].query(on: req.db).all() + return try await children.encodeResponse(status: .ok, for: req) } - func create(_ req: Request) async throws -> ChildType { + func create(_ req: Request) async throws -> Response { let parentId = try req.getId(modelType: ParentType.self) guard let parent = try await ParentType.find(parentId, on: req.db) else { @@ -49,10 +50,10 @@ extension CrudChildrenControllerProtocol { let child = try req.content.decode(ChildType.self) try await parent[keyPath: self.children].create(child, on: req.db) - return child + return try await child.encodeResponse(status: .created, for: req) } - func update(_ req: Request) async throws -> ChildType { + func update(_ req: Request) async throws -> Response { let parentId = try req.getId(modelType: ParentType.self) let childId = try req.getId(modelType: ChildType.self) @@ -68,10 +69,10 @@ extension CrudChildrenControllerProtocol { temp._$id.exists = true temp.id = childId try await temp.update(on: req.db) - return temp + return try await temp.encodeResponse(status: .ok, for: req) } - func delete(_ req: Request) async throws -> HTTPStatus { + func delete(_ req: Request) async throws -> Response { let parentId = try req.getId(modelType: ParentType.self) let childId = try req.getId(modelType: ChildType.self) @@ -83,6 +84,6 @@ extension CrudChildrenControllerProtocol { } try await child.delete(on: req.db) - return HTTPStatus.ok + return try await HTTPStatus.noContent.encodeResponse(for: req) } } diff --git a/Sources/CrudRouter/ControllerProtocols/CrudControllerProtocol.swift b/Sources/CrudRouter/ControllerProtocols/CrudControllerProtocol.swift index d42d9db..d03e815 100644 --- a/Sources/CrudRouter/ControllerProtocols/CrudControllerProtocol.swift +++ b/Sources/CrudRouter/ControllerProtocols/CrudControllerProtocol.swift @@ -4,33 +4,34 @@ import Fluent protocol CrudControllerProtocol { associatedtype ModelType: Model, Content where ModelType.IDValue: LosslessStringConvertible - func indexAll(_ req: Request) async throws -> [ModelType] - func index(_ req: Request) async throws -> ModelType - func update(_ req: Request) async throws -> ModelType - func create(_ req: Request) async throws -> ModelType - func delete(_ req: Request) async throws -> HTTPStatus + func indexAll(_ req: Request) async throws -> Response + func index(_ req: Request) async throws -> Response + func update(_ req: Request) async throws -> Response + func create(_ req: Request) async throws -> Response + func delete(_ req: Request) async throws -> Response } extension CrudControllerProtocol { - func indexAll(_ req: Request) async throws -> [ModelType] { - return try await ModelType.query(on: req.db).all() + func indexAll(_ req: Request) async throws -> Response { + let model = try await ModelType.query(on: req.db).all() + return try await model.encodeResponse(status: .ok, for: req) } - func index(_ req: Request) async throws -> ModelType { + func index(_ req: Request) async throws -> Response { let id = try req.getId(modelType: ModelType.self) guard let model = try await ModelType.find(id, on: req.db) else { throw Abort(.notFound) } - return model + return try await model.encodeResponse(status: .ok, for: req) } - func create(_ req: Request) async throws -> ModelType { + func create(_ req: Request) async throws -> Response { let model = try req.content.decode(ModelType.self) try await model.create(on: req.db) - return model + return try await model.encodeResponse(status: .created, for: req) } - func update(_ req: Request) async throws -> ModelType { + func update(_ req: Request) async throws -> Response { let id = try req.getId(modelType: ModelType.self) let model = try req.content.decode(ModelType.self) @@ -42,15 +43,15 @@ extension CrudControllerProtocol { temp._$id.exists = true temp.id = existingModel.id try await temp.update(on: req.db) - return temp + return try await temp.encodeResponse(status: .ok, for: req) } - func delete(_ req: Request) async throws -> HTTPStatus { + func delete(_ req: Request) async throws -> Response { let id = try req.getId(modelType: ModelType.self) guard let model = try await ModelType.find(id, on: req.db) else { throw Abort(.notFound) } try await model.delete(on: req.db) - return HTTPStatus.ok + return try await HTTPStatus.noContent.encodeResponse(for: req) } } diff --git a/Sources/CrudRouter/ControllerProtocols/CrudParentControllerProtocol.swift b/Sources/CrudRouter/ControllerProtocols/CrudParentControllerProtocol.swift index 234fd1b..3e97775 100644 --- a/Sources/CrudRouter/ControllerProtocols/CrudParentControllerProtocol.swift +++ b/Sources/CrudRouter/ControllerProtocols/CrudParentControllerProtocol.swift @@ -7,22 +7,23 @@ protocol CrudParentControllerProtocol { var relation: KeyPath> { get } - func index(_ req: Request) async throws -> ParentType - func update(_ req: Request) async throws -> ParentType + func index(_ req: Request) async throws -> Response + func update(_ req: Request) async throws -> Response } extension CrudParentControllerProtocol { - func index(_ req: Request) async throws -> ParentType { + func index(_ req: Request) async throws -> Response { let childId = try req.getId(modelType: ChildType.self) guard let child = try await ChildType.find(childId, on: req.db) else { throw Abort(.notFound) } - return try await child[keyPath: self.relation].get(on: req.db) + let body = try await child[keyPath: self.relation].get(on: req.db) + return try await body.encodeResponse(status: .ok, for: req) } - func update(_ req: Request) async throws -> ParentType { + func update(_ req: Request) async throws -> Response { let childId = try req.getId(modelType: ChildType.self) let newParent = try req.content.decode(ParentType.self) @@ -36,6 +37,6 @@ extension CrudParentControllerProtocol { temp.id = oldParent.id temp._$id.exists = true try await temp.update(on: req.db) - return temp + return try await temp.encodeResponse(status: .ok, for: req) } } diff --git a/Sources/CrudRouter/ControllerProtocols/CrudSiblingsControllerProtocol.swift b/Sources/CrudRouter/ControllerProtocols/CrudSiblingsControllerProtocol.swift index 9452ff4..ff43694 100644 --- a/Sources/CrudRouter/ControllerProtocols/CrudSiblingsControllerProtocol.swift +++ b/Sources/CrudRouter/ControllerProtocols/CrudSiblingsControllerProtocol.swift @@ -8,13 +8,15 @@ protocol CrudSiblingsControllerProtocol { var siblings: KeyPath> { get } - func index(_ req: Request) async throws -> ChildType - func indexAll(_ req: Request) async throws -> [ChildType] - func update(_ req: Request) async throws -> ChildType + func index(_ req: Request) async throws -> Response + func indexAll(_ req: Request) async throws -> Response + func create(_ req: Request) async throws -> Response + func update(_ req: Request) async throws -> Response + func delete(_ req: Request) async throws -> Response } extension CrudSiblingsControllerProtocol { - func index(_ req: Request) async throws -> ChildType { + func index(_ req: Request) async throws -> Response { let parentId = try req.getId(modelType: ParentType.self) let childId = try req.getId(modelType: ChildType.self) @@ -25,20 +27,33 @@ extension CrudSiblingsControllerProtocol { throw Abort(.notFound) } - return child + return try await child.encodeResponse(status: .ok, for: req) } - func indexAll(_ req: Request) async throws -> [ChildType] { + func indexAll(_ req: Request) async throws -> Response { let parentId = try req.getId(modelType: ParentType.self) guard let parent = try await ParentType.find(parentId, on: req.db) else { throw Abort(.notFound) } - return try await parent[keyPath: self.siblings].query(on: req.db).all() + let siblings = try await parent[keyPath: self.siblings].query(on: req.db).all() + return try await siblings.encodeResponse(status: .ok, for: req) } - func update(_ req: Request) async throws -> ChildType { + func create(_ req: Request) async throws -> Response { + let parentId = try req.getId(modelType: ParentType.self) + + guard let parent = try await ParentType.find(parentId, on: req.db) else { + throw Abort(.notFound) + } + + let newChild = try req.content.decode(ChildType.self) + try await parent[keyPath: self.siblings].attach(newChild, on: req.db) + return try await newChild.encodeResponse(status: .created, for: req) + } + + func update(_ req: Request) async throws -> Response { let parentId = try req.getId(modelType: ParentType.self) let childId = try req.getId(modelType: ChildType.self) @@ -54,22 +69,10 @@ extension CrudSiblingsControllerProtocol { temp._$id.exists = true temp.id = childId try await temp.update(on: req.db) - return temp - } - - func create(_ req: Request) async throws -> ChildType { - let parentId = try req.getId(modelType: ParentType.self) - - guard let parent = try await ParentType.find(parentId, on: req.db) else { - throw Abort(.notFound) - } - - let newChild = try req.content.decode(ChildType.self) - try await parent[keyPath: self.siblings].attach(newChild, on: req.db) - return newChild + return try await temp.encodeResponse(status: .ok, for: req) } - func delete(_ req: Request) async throws -> HTTPStatus { + func delete(_ req: Request) async throws -> Response { let parentId = try req.getId(modelType: ParentType.self) guard let parent = try await ParentType.find(parentId, on: req.db) else { @@ -84,6 +87,6 @@ extension CrudSiblingsControllerProtocol { try await siblingsRelation.detach(child, on: req.db) try await child.delete(on: req.db) - return HTTPStatus.ok + return try await HTTPStatus.noContent.encodeResponse(for: req) } } diff --git a/Tests/CrudRouterIntegrationTests/ResponseTests/CrudRouteDeleteResponseTests.swift b/Tests/CrudRouterIntegrationTests/ResponseTests/CrudRouteDeleteResponseTests.swift index 27e6efc..8816b5e 100644 --- a/Tests/CrudRouterIntegrationTests/ResponseTests/CrudRouteDeleteResponseTests.swift +++ b/Tests/CrudRouterIntegrationTests/ResponseTests/CrudRouteDeleteResponseTests.swift @@ -29,7 +29,7 @@ final class CrudRouteDeleteResponseTests: XCTestCase { XCTAssertEqual(allGalaxies.count, 1) try app.testable().test(.DELETE, "/galaxy/\(BaseGalaxySeeding.milkyWayId)") { (resp) in - XCTAssert(resp.status == .ok) + XCTAssert(resp.status == .noContent) let allGalaxies = try app.db.query(Galaxy.self).all().wait() @@ -52,7 +52,7 @@ final class CrudRouteDeleteResponseTests: XCTestCase { XCTAssertEqual(children.count, 1) try app.testable().test(.DELETE, "/galaxy/\(BaseGalaxySeeding.milkyWayId)/planet/\(ChildSeeding.earthId)") { (resp) in - XCTAssert(resp.status == .ok) + XCTAssert(resp.status == .noContent) let galaxy = try Galaxy.find(BaseGalaxySeeding.milkyWayId, on: app.db).wait() let children = try galaxy!.$planets.get(on: app.db).wait() @@ -76,7 +76,7 @@ final class CrudRouteDeleteResponseTests: XCTestCase { XCTAssertEqual(tagSiblings.count, 1) try app.testable().test(.DELETE, "/planet/\(ChildSeeding.earthId)/tag/\(SiblingSeeding.lifeSupportingId)") { (resp) in - XCTAssertEqual(resp.status, .ok) + XCTAssertEqual(resp.status, .noContent) let planet = try Planet.find(ChildSeeding.earthId, on: app.db).wait() let tagSiblings = try planet!.$tags.get(on: app.db).wait() @@ -100,7 +100,7 @@ final class CrudRouteDeleteResponseTests: XCTestCase { XCTAssertEqual(planetSiblings.count, 1) try app.testable().test(.DELETE, "/tag/\(SiblingSeeding.lifeSupportingId)/planet/\(ChildSeeding.earthId)") { (resp) in - XCTAssertEqual(resp.status, .ok) + XCTAssertEqual(resp.status, .noContent) let tag = try Tag.find(SiblingSeeding.lifeSupportingId, on: app.db).wait() let planetSiblings = try tag!.$planets.get(on: app.db).wait() diff --git a/Tests/CrudRouterIntegrationTests/ResponseTests/CrudRoutePostResponseTests.swift b/Tests/CrudRouterIntegrationTests/ResponseTests/CrudRoutePostResponseTests.swift index 5665f34..b032e3b 100644 --- a/Tests/CrudRouterIntegrationTests/ResponseTests/CrudRoutePostResponseTests.swift +++ b/Tests/CrudRouterIntegrationTests/ResponseTests/CrudRoutePostResponseTests.swift @@ -30,7 +30,7 @@ final class CrudRoutePostResponseTests: XCTestCase { try app.testable().test(.POST, "/galaxy", beforeRequest: { req in try req.content.encode(newGalaxy) }) { (resp) in - XCTAssertEqual(resp.status, .ok) + XCTAssertEqual(resp.status, .created) let decoded = try resp.content.decode(Galaxy.self) @@ -63,7 +63,7 @@ final class CrudRoutePostResponseTests: XCTestCase { try app.testable().test(.POST, "/galaxy", beforeRequest: { req in try req.content.encode(newGalaxy) }) { (resp) in - XCTAssertEqual(resp.status, .ok) + XCTAssertEqual(resp.status, .created) let decoded = try resp.content.decode(Galaxy.self) @@ -86,7 +86,7 @@ final class CrudRoutePostResponseTests: XCTestCase { try app.testable().test(.POST, "/galaxy/\(BaseGalaxySeeding.milkyWayId)/planet", beforeRequest: { req in try req.content.encode(newPlanet) }) { (resp) in - XCTAssertEqual(resp.status, .ok) + XCTAssertEqual(resp.status, .created) let decoded = try resp.content.decode(Planet.self) @@ -122,7 +122,7 @@ final class CrudRoutePostResponseTests: XCTestCase { try app.testable().test(.POST, "/planet", beforeRequest: { req in try req.content.encode(newPlanet) }) { (resp) in - XCTAssertEqual(resp.status, .ok) + XCTAssertEqual(resp.status, .created) let decoded = try resp.content.decode(Planet.self) @@ -170,7 +170,7 @@ final class CrudRoutePostResponseTests: XCTestCase { try app.testable().test(.POST, "/planet", beforeRequest: { req in try req.content.encode(newPlanet) }) { (resp) in - XCTAssertEqual(resp.status, .ok) + XCTAssertEqual(resp.status, .created) let decoded = try resp.content.decode(Planet.self) @@ -194,7 +194,7 @@ final class CrudRoutePostResponseTests: XCTestCase { try app.testable().test(.POST, "/tag", beforeRequest: { req in try req.content.encode(newTag) }) { (resp) in - XCTAssertEqual(resp.status, .ok) + XCTAssertEqual(resp.status, .created) let decoded = try resp.content.decode(Tag.self) @@ -217,7 +217,7 @@ final class CrudRoutePostResponseTests: XCTestCase { try app.testable().test(.POST, "/planet/\(newPlanetId)/tag", beforeRequest: { req in try req.content.encode(otherNewTag) }) { (resp) in - XCTAssertEqual(resp.status, .ok) + XCTAssertEqual(resp.status, .created) let decoded = try resp.content.decode(Tag.self) @@ -240,7 +240,7 @@ final class CrudRoutePostResponseTests: XCTestCase { try app.testable().test(.POST, "/tag/\(newTagId)/planet", beforeRequest: { req in try req.content.encode(otherNewPlanet) }) { (resp) in - XCTAssertEqual(resp.status, .ok) + XCTAssertEqual(resp.status, .created) let decoded = try resp.content.decode(Planet.self) diff --git a/Tests/CrudRouterIntegrationTests/TestMigrations/BaseGalaxySeeding.swift b/Tests/CrudRouterIntegrationTests/TestSetup/TestMigrations/BaseGalaxySeeding.swift similarity index 100% rename from Tests/CrudRouterIntegrationTests/TestMigrations/BaseGalaxySeeding.swift rename to Tests/CrudRouterIntegrationTests/TestSetup/TestMigrations/BaseGalaxySeeding.swift diff --git a/Tests/CrudRouterIntegrationTests/TestMigrations/ChildSeeding.swift b/Tests/CrudRouterIntegrationTests/TestSetup/TestMigrations/ChildSeeding.swift similarity index 100% rename from Tests/CrudRouterIntegrationTests/TestMigrations/ChildSeeding.swift rename to Tests/CrudRouterIntegrationTests/TestSetup/TestMigrations/ChildSeeding.swift diff --git a/Tests/CrudRouterIntegrationTests/TestMigrations/SiblingSeeding.swift b/Tests/CrudRouterIntegrationTests/TestSetup/TestMigrations/SiblingSeeding.swift similarity index 100% rename from Tests/CrudRouterIntegrationTests/TestMigrations/SiblingSeeding.swift rename to Tests/CrudRouterIntegrationTests/TestSetup/TestMigrations/SiblingSeeding.swift diff --git a/Tests/CrudRouterIntegrationTests/TestModels/Galaxy.swift b/Tests/CrudRouterIntegrationTests/TestSetup/TestModels/Galaxy.swift similarity index 100% rename from Tests/CrudRouterIntegrationTests/TestModels/Galaxy.swift rename to Tests/CrudRouterIntegrationTests/TestSetup/TestModels/Galaxy.swift diff --git a/Tests/CrudRouterIntegrationTests/TestModels/Planet.swift b/Tests/CrudRouterIntegrationTests/TestSetup/TestModels/Planet.swift similarity index 100% rename from Tests/CrudRouterIntegrationTests/TestModels/Planet.swift rename to Tests/CrudRouterIntegrationTests/TestSetup/TestModels/Planet.swift diff --git a/Tests/CrudRouterIntegrationTests/TestModels/PlanetTag.swift b/Tests/CrudRouterIntegrationTests/TestSetup/TestModels/PlanetTag.swift similarity index 100% rename from Tests/CrudRouterIntegrationTests/TestModels/PlanetTag.swift rename to Tests/CrudRouterIntegrationTests/TestSetup/TestModels/PlanetTag.swift diff --git a/Tests/CrudRouterIntegrationTests/TestModels/Tag.swift b/Tests/CrudRouterIntegrationTests/TestSetup/TestModels/Tag.swift similarity index 100% rename from Tests/CrudRouterIntegrationTests/TestModels/Tag.swift rename to Tests/CrudRouterIntegrationTests/TestSetup/TestModels/Tag.swift From bf7509b1799714c02a6b69798c4d484037a93eba Mon Sep 17 00:00:00 2001 From: twof Date: Fri, 26 Nov 2021 16:32:48 -0800 Subject: [PATCH 38/38] documented and cleaned up --- README.md | 1 - .../CrudParentControllerProtocol.swift | 2 +- .../ControllerProtocols/Crudable.swift | 365 ++++++++++++++++-- .../CrudRouter/CrudChildrenController.swift | 4 +- Sources/CrudRouter/CrudController.swift | 1 + Sources/CrudRouter/CrudParentController.swift | 1 + .../CrudRouter/CrudSiblingsController.swift | 7 +- Sources/CrudRouter/Extensions/Array.swift | 4 +- .../CrudRouter/Extensions/Router+CRUD.swift | 134 ++++++- Sources/CrudRouter/Extensions/String.swift | 8 +- .../RouterMethods/ChildrenRouterMethod.swift | 2 +- .../RouterMethods/ParentRouterMethod.swift | 2 +- .../RouterMethods/RouterMethod.swift | 2 +- .../RouterMethods/SiblingRouterMethod.swift | 3 +- 14 files changed, 482 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index 2fc6836..cef0bc5 100644 --- a/README.md +++ b/README.md @@ -122,7 +122,6 @@ GET /todo/:id/tag/:id ### Future features - query parameter support - PATCH support -- more fine grained response statuses - automatically expose relations (blocked by lack of Swift reflection support) - documentation for all public functions - generate models and rest routes via console command diff --git a/Sources/CrudRouter/ControllerProtocols/CrudParentControllerProtocol.swift b/Sources/CrudRouter/ControllerProtocols/CrudParentControllerProtocol.swift index 3e97775..f28497a 100644 --- a/Sources/CrudRouter/ControllerProtocols/CrudParentControllerProtocol.swift +++ b/Sources/CrudRouter/ControllerProtocols/CrudParentControllerProtocol.swift @@ -2,7 +2,7 @@ import Vapor import FluentKit protocol CrudParentControllerProtocol { - associatedtype ParentType: Model & Content where ParentType.IDValue: LosslessStringConvertible + associatedtype ParentType: Model & Content associatedtype ChildType: Model & Content where ChildType.IDValue: LosslessStringConvertible var relation: KeyPath> { get } diff --git a/Sources/CrudRouter/ControllerProtocols/Crudable.swift b/Sources/CrudRouter/ControllerProtocols/Crudable.swift index 3f50dce..781e720 100644 --- a/Sources/CrudRouter/ControllerProtocols/Crudable.swift +++ b/Sources/CrudRouter/ControllerProtocols/Crudable.swift @@ -1,14 +1,12 @@ import Vapor import Fluent -public protocol ControllerProtocol { +public protocol Crudable { + associatedtype OriginType: Model, Content + associatedtype TargetType: Model, Content where TargetType.IDValue: LosslessStringConvertible + var path: [PathComponent] { get } var router: RoutesBuilder { get } -} - -public protocol Crudable: ControllerProtocol { - associatedtype OriginType: Model, Content where OriginType.IDValue: LosslessStringConvertible - associatedtype TargetType: Model, Content where TargetType.IDValue: LosslessStringConvertible func crud( at path: PathComponent..., @@ -16,8 +14,15 @@ public protocol Crudable: ControllerProtocol { _ either: OnlyExceptEither, relationConfiguration: ((CrudParentController) -> Void)? ) where - ParentType: Model & Content, - ParentType.IDValue: LosslessStringConvertible + ParentType: Model & Content + + func crud( + at path: [PathComponent], + parent relation: KeyPath>, + _ either: OnlyExceptEither, + relationConfiguration: ((CrudParentController) -> Void)? + ) where + ParentType: Model & Content func crud( at path: PathComponent..., @@ -27,40 +32,97 @@ public protocol Crudable: ControllerProtocol { ) where ChildType: Model & Content + func crud( + at path: [PathComponent], + children relation: KeyPath>, + _ either: OnlyExceptEither, + relationConfiguration: ((CrudChildrenController) -> Void)? + ) where + ChildType: Model & Content + func crud( at path: PathComponent..., siblings relation: KeyPath>, - _ either: OnlyExceptEither, + _ either: OnlyExceptEither, + relationConfiguration: ((CrudSiblingsController) -> Void)? + ) where + SiblingType: Content, + ThroughType: Model + + func crud( + at path: [PathComponent], + siblings relation: KeyPath>, + _ either: OnlyExceptEither, relationConfiguration: ((CrudSiblingsController) -> Void)? ) where SiblingType: Content, - SiblingType.IDValue: LosslessStringConvertible, ThroughType: Model } extension Crudable { + /// Creates CRUD endpoints for the suplied `relation`. + /// + /// By default, the routes will be created that + /// - Get the parent + /// - Update the parent + /// + /// For example + /// ```swift + /// router.crud(register: Todo.self) { todoRouter in + /// todoRouter.crud("owner", parent: \.$owner) + /// } + /// ``` + /// Will create the following routes. + /// + /// ``` + /// GET /todo/:id/owner + /// PUT /todo/:id/owner + /// ``` + /// + /// - Parameter path: Overrides the path instead of using the default path string. + /// - Parameter relation: Parent relation on the router's model type. + /// - Parameter either: Users can select a subset of endpoints to generate with `.only()` or exclude a subset of endpoints with `.except()` + /// - Parameter relationConfiguration: Closure that can be used to configure endpoints for children, parents or siblings of `type`. + /// + /// For example + /// ``` + /// app.crud("foo", register: Planet.self) { router in + /// router.crud(at: "foo", parent: \.$galaxy) { galaxyRouter in + /// galaxyRouter.crud(children: \.$planets) + /// } + /// } + /// ``` public func crud( - at path: PathComponent..., + at path: [PathComponent], parent relation: KeyPath>, - _ either: OnlyExceptEither = .only([.read, .update]), + _ either: OnlyExceptEither = .only(ParentRouterMethod.allCases), relationConfiguration: ((CrudParentController) -> Void)?=nil ) where - ParentType: Model & Content, - ParentType.IDValue: LosslessStringConvertible + ParentType: Model & Content { let baseIdPath = self.path.appending(.parameter("\(OriginType.schema)ID")) let adjustedPath = path.adjustedPath(for: ParentType.self) let fullPath = baseIdPath + adjustedPath - let allMethods: Set = Set([.read, .update]) let controller: CrudParentController switch either { case .only(let methods): - controller = CrudParentController(relation: relation, path: fullPath, router: self.router, activeMethods: Set(methods)) + controller = CrudParentController( + relation: relation, + path: fullPath, + router: self.router, + activeMethods: Set(methods) + ) case .except(let methods): - controller = CrudParentController(relation: relation, path: fullPath, router: self.router, activeMethods: allMethods.subtracting(Set(methods))) + let allMethods = Set(ParentRouterMethod.allCases) + controller = CrudParentController( + relation: relation, + path: fullPath, + router: self.router, + activeMethods: allMethods.subtracting(Set(methods)) + ) } do { try controller.boot(routes: self.router) } catch { fatalError("I have no reason to expect boot to throw") } @@ -68,28 +130,119 @@ extension Crudable { relationConfiguration?(controller) } - public func crud( + /// Creates CRUD endpoints for the suplied `relation`. + /// + /// By default, the routes will be created that + /// - Get the parent + /// - Update the parent + /// + /// For example + /// ```swift + /// router.crud(register: Todo.self) { todoRouter in + /// todoRouter.crud("owner", parent: \.$owner) + /// } + /// ``` + /// Will create the following routes. + /// + /// ``` + /// GET /todo/:id/owner + /// PUT /todo/:id/owner + /// ``` + /// + /// - Parameter path: Overrides the path instead of using the default path string. + /// - Parameter relation: Parent relation on the router's model type. + /// - Parameter either: Users can select a subset of endpoints to generate with `.only()` or exclude a subset of endpoints with `.except()` + /// - Parameter relationConfiguration: Closure that can be used to configure endpoints for children, parents or siblings of `type`. + /// + /// For example + /// ``` + /// app.crud("foo", register: Planet.self) { router in + /// router.crud(at: "foo", parent: \.$galaxy) { galaxyRouter in + /// galaxyRouter.crud(children: \.$planets) + /// } + /// } + /// ``` + public func crud( at path: PathComponent..., + parent relation: KeyPath>, + _ either: OnlyExceptEither = .only(ParentRouterMethod.allCases), + relationConfiguration: ((CrudParentController) -> Void)?=nil + ) where + ParentType: Model & Content + { + crud(at: path, parent: relation, either, relationConfiguration: relationConfiguration) + } + + /// Creates CRUD endpoints for the suplied `relation`. + /// + /// By default, the routes will be created that + /// + /// - Get an individual child with the provided ID + /// - Get all children + /// - Create a new child + /// - Update a child with the provided ID + /// - Delete a child with the provided ID + /// + /// For example + /// ```swift + /// router.crud(register: Todo.self) { todoRouter in + /// todoRouter.crud("subtasks", children: \.$subtasks) + /// } + /// ``` + /// Will create the following routes. + /// + /// ``` + /// GET /todo/:id/subtasks + /// GET /todo/:id/subtasks/:id + /// POST /todo/:id/subtasks + /// PUT /todo/:id/subtasks/:id + /// DELETE /todo/:id/subtasks/:id + /// ``` + /// + /// - Parameter path: Overrides the path instead of using the default path string. + /// - Parameter relation: Children relation on the router's model type. + /// - Parameter either: Users can select a subset of endpoints to generate with `.only()` or exclude a subset of endpoints with `.except()` + /// - Parameter relationConfiguration: Closure that can be used to configure endpoints for children, parents or siblings of `type`. + /// + /// For example + /// ``` + /// app.crud("foo", register: Galaxy.self) { router in + /// router.crud(at: "foo", children: \.$planets) { planetRouter in + /// planetRouter.crud(parent: \.$galaxy) + /// } + /// } + /// ``` + public func crud( + at path: [PathComponent], children relation: KeyPath>, - _ either: OnlyExceptEither = .only([.read, .readAll, .create, .update, .delete]), + _ either: OnlyExceptEither = .only(ChildrenRouterMethod.allCases), relationConfiguration: ((CrudChildrenController) -> Void)?=nil ) where - ChildType: Model & Content, - ChildType.IDValue: LosslessStringConvertible + ChildType: Model & Content { let baseIdPath = self.path.appending(.parameter("\(OriginType.schema)ID")) let adjustedPath = path.adjustedPath(for: ChildType.self) let fullPath = baseIdPath + adjustedPath - let allMethods: Set = Set([.create, .read, .readAll, .update, .delete]) let controller: CrudChildrenController switch either { case .only(let methods): - controller = CrudChildrenController(childrenRelation: relation, path: fullPath, router: self.router, activeMethods: Set(methods)) + controller = CrudChildrenController( + childrenRelation: relation, + path: fullPath, + router: self.router, + activeMethods: Set(methods) + ) case .except(let methods): - controller = CrudChildrenController(childrenRelation: relation, path: fullPath, router: self.router, activeMethods: allMethods.subtracting(Set(methods))) + let allMethods = Set(ChildrenRouterMethod.allCases) + controller = CrudChildrenController( + childrenRelation: relation, + path: fullPath, + router: self.router, + activeMethods: allMethods.subtracting(Set(methods)) + ) } do { try controller.boot(routes: self.router) } catch { fatalError("I have no reason to expect boot to throw") } @@ -97,14 +250,102 @@ extension Crudable { relationConfiguration?(controller) } - public func crud( + /// Creates CRUD endpoints for the suplied `relation`. + /// + /// By default, the routes will be created that + /// + /// - Get an individual child with the provided ID + /// - Get all children + /// - Create a new child + /// - Update a child with the provided ID + /// - Delete a child with the provided ID + /// + /// For example + /// ```swift + /// router.crud(register: Todo.self) { todoRouter in + /// todoRouter.crud("subtasks", children: \.$subtasks) + /// } + /// ``` + /// Will create the following routes. + /// + /// ``` + /// GET /todo/:id/subtasks + /// GET /todo/:id/subtasks/:id + /// POST /todo/:id/subtasks + /// PUT /todo/:id/subtasks/:id + /// DELETE /todo/:id/subtasks/:id + /// ``` + /// + /// - Parameter path: Overrides the path instead of using the default path string. + /// - Parameter relation: Children relation on the router's model type. + /// - Parameter either: Users can select a subset of endpoints to generate with `.only()` or exclude a subset of endpoints with `.except()` + /// - Parameter relationConfiguration: Closure that can be used to configure endpoints for children, parents or siblings of `type`. + /// + /// For example + /// ``` + /// app.crud("foo", register: Galaxy.self) { router in + /// router.crud(at: "foo", children: \.$planets) { planetRouter in + /// planetRouter.crud(parent: \.$galaxy) + /// } + /// } + /// ``` + public func crud( at path: PathComponent..., + children relation: KeyPath>, + _ either: OnlyExceptEither = .only(ChildrenRouterMethod.allCases), + relationConfiguration: ((CrudChildrenController) -> Void)?=nil + ) where + ChildType: Model & Content + { + crud(at: path, children: relation, either, relationConfiguration: relationConfiguration) + } + + /// Creates CRUD endpoints for the suplied `relation`. + /// + /// By default, the routes will be created that + /// + /// - Get an individual sibling with the provided ID + /// - Get all siblings + /// - Create a new sibling + /// - Update a sibling with the provided ID + /// - Delete a sibling with the provided ID + /// + /// For example + /// ```swift + /// router.crud(register: Todo.self) { todoRouter in + /// todoRouter.crud("tags", siblings: \.$tags) + /// } + /// ``` + /// Will create the following routes. + /// + /// ``` + /// GET /todo/:id/tags + /// GET /todo/:id/tags/:id + /// POST /todo/:id/tags + /// PUT /todo/:id/tags/:id + /// DELETE /todo/:id/tags/:id + /// ``` + /// + /// - Parameter path: Overrides the path instead of using the default path string. + /// - Parameter relation: Sibling relation on the router's model type. + /// - Parameter either: Users can select a subset of endpoints to generate with `.only()` or exclude a subset of endpoints with `.except()` + /// - Parameter relationConfiguration: Closure that can be used to configure endpoints for children, parents or siblings of `type`. + /// + /// For example + /// ``` + /// app.crud("foo", register: Planet.self) { router in + /// router.crud(at: "foo", siblings: \.$tags) { tagRouter in + /// tagRouter.crud(siblings: \.$planets) + /// } + /// } + /// ``` + public func crud( + at path: [PathComponent], siblings relation: KeyPath>, - _ either: OnlyExceptEither = .only([.read, .readAll, .create, .update, .delete]), + _ either: OnlyExceptEither = .only(SiblingRouterMethod.allCases), relationConfiguration: ((CrudSiblingsController) -> Void)?=nil ) where SiblingType: Content, - SiblingType.IDValue: LosslessStringConvertible, ThroughType: Model { let baseIdPath = self.path.appending(.parameter("\(OriginType.schema)ID")) @@ -112,19 +353,79 @@ extension Crudable { let fullPath = baseIdPath + adjustedPath - let allMethods: Set = Set([.read, .readAll, .create, .update, .delete]) let controller: CrudSiblingsController switch either { case .only(let methods): - controller = CrudSiblingsController(siblingRelation: relation, path: fullPath, router: - self.router, activeMethods: Set(methods)) + controller = CrudSiblingsController( + siblingRelation: relation, + path: fullPath, + router: self.router, + activeMethods: Set(methods) + ) case .except(let methods): - controller = CrudSiblingsController(siblingRelation: relation, path: fullPath, router: self.router, activeMethods: allMethods.subtracting(Set(methods))) + let allMethods = Set(SiblingRouterMethod.allCases) + controller = CrudSiblingsController( + siblingRelation: relation, + path: fullPath, + router: self.router, + activeMethods: allMethods.subtracting(Set(methods)) + ) } - do { try controller.boot(routes: self.router) } catch { fatalError("I have no reason to expect boot to throw") } + do { try controller.boot(routes: self.router) } catch { fatalError("I have no reason to expect boot to throw") } relationConfiguration?(controller) } + + /// Creates CRUD endpoints for the suplied `relation`. + /// + /// By default, the routes will be created that + /// + /// - Get an individual sibling with the provided ID + /// - Get all siblings + /// - Create a new sibling + /// - Update a sibling with the provided ID + /// - Delete a sibling with the provided ID + /// + /// For example + /// ```swift + /// router.crud(register: Todo.self) { todoRouter in + /// todoRouter.crud("tags", siblings: \.$tags) + /// } + /// ``` + /// Will create the following routes. + /// + /// ``` + /// GET /todo/:id/tags + /// GET /todo/:id/tags/:id + /// POST /todo/:id/tags + /// PUT /todo/:id/tags/:id + /// DELETE /todo/:id/tags/:id + /// ``` + /// + /// - Parameter path: Overrides the path instead of using the default path string. + /// - Parameter relation: Sibling relation on the router's model type. + /// - Parameter either: Users can select a subset of endpoints to generate with `.only()` or exclude a subset of endpoints with `.except()` + /// - Parameter relationConfiguration: Closure that can be used to configure endpoints for children, parents or siblings of `type`. + /// + /// For example + /// ``` + /// app.crud("foo", register: Planet.self) { router in + /// router.crud(at: "foo", siblings: \.$tags) { tagRouter in + /// tagRouter.crud(siblings: \.$planets) + /// } + /// } + /// ``` + public func crud( + at path: PathComponent..., + siblings relation: KeyPath>, + _ either: OnlyExceptEither = .only(SiblingRouterMethod.allCases), + relationConfiguration: ((CrudSiblingsController) -> Void)?=nil + ) where + SiblingType: Content, + ThroughType: Model + { + crud(at: path, siblings: relation, either, relationConfiguration: relationConfiguration) + } } diff --git a/Sources/CrudRouter/CrudChildrenController.swift b/Sources/CrudRouter/CrudChildrenController.swift index b13273a..6f8ea6f 100644 --- a/Sources/CrudRouter/CrudChildrenController.swift +++ b/Sources/CrudRouter/CrudChildrenController.swift @@ -10,10 +10,10 @@ public struct CrudChildrenController< { public typealias TargetType = ChildType - public var router: RoutesBuilder + public let router: RoutesBuilder + public let path: [PathComponent] var children: KeyPath> - public let path: [PathComponent] let activeMethods: Set init( diff --git a/Sources/CrudRouter/CrudController.swift b/Sources/CrudRouter/CrudController.swift index bc9f39e..555a70c 100644 --- a/Sources/CrudRouter/CrudController.swift +++ b/Sources/CrudRouter/CrudController.swift @@ -9,6 +9,7 @@ public struct CrudController< public let path: [PathComponent] public let router: RoutesBuilder + let activeMethods: Set init( diff --git a/Sources/CrudRouter/CrudParentController.swift b/Sources/CrudRouter/CrudParentController.swift index 150031a..b619288 100644 --- a/Sources/CrudRouter/CrudParentController.swift +++ b/Sources/CrudRouter/CrudParentController.swift @@ -13,6 +13,7 @@ public struct CrudParentController< public let relation: KeyPath> public let path: [PathComponent] public let router: RoutesBuilder + let activeMethods: Set init( diff --git a/Sources/CrudRouter/CrudSiblingsController.swift b/Sources/CrudRouter/CrudSiblingsController.swift index 817dad3..eb1c1c2 100644 --- a/Sources/CrudRouter/CrudSiblingsController.swift +++ b/Sources/CrudRouter/CrudSiblingsController.swift @@ -11,16 +11,17 @@ public struct CrudSiblingsController< { public typealias TargetType = SiblingType - public var siblings: KeyPath> + public let siblings: KeyPath> public let path: [PathComponent] public let router: RoutesBuilder - let activeMethods: Set + + let activeMethods: Set init( siblingRelation: KeyPath>, path: [PathComponent], router: RoutesBuilder, - activeMethods: Set + activeMethods: Set ) { self.siblings = siblingRelation self.path = path diff --git a/Sources/CrudRouter/Extensions/Array.swift b/Sources/CrudRouter/Extensions/Array.swift index d01ba9d..e0c8de4 100644 --- a/Sources/CrudRouter/Extensions/Array.swift +++ b/Sources/CrudRouter/Extensions/Array.swift @@ -1,6 +1,6 @@ import Vapor -public extension Array { +extension Array { func appending(_ element: Element) -> Array { var temp = self temp.append(element) @@ -11,7 +11,7 @@ public extension Array { extension Array where Element == PathComponent { func adjustedPath(for type: T.Type) -> [PathComponent] { return self.count == 0 - ? [.constant(String(describing: T.self).snakeCased()!)] + ? [.constant(String(describing: T.self).snakeCased())] : self } } diff --git a/Sources/CrudRouter/Extensions/Router+CRUD.swift b/Sources/CrudRouter/Extensions/Router+CRUD.swift index e71630e..c8ec11e 100644 --- a/Sources/CrudRouter/Extensions/Router+CRUD.swift +++ b/Sources/CrudRouter/Extensions/Router+CRUD.swift @@ -2,24 +2,150 @@ import Vapor import Fluent public extension RoutesBuilder { + + /// Creates CRUD endpoints for the supplied `type`. + /// + /// By default, the routes will be created that + /// - Get an individual model with the provided ID + /// - Get all models + /// - Create a new model + /// - Update a model with the provided ID + /// - Delete a module with the provided ID + /// + /// For example + /// ```swift + /// router.crud(register: Todo.self) + /// ``` + /// Will create the following routes. + /// + /// ``` + /// GET /todo // returns all Todos + /// GET /todo/:id // returns the Todo with :id + /// POST /todo // create new Todo with provided body + /// PUT /todo/:id // update Todo with :id + /// DELETE /todo/:id // delete Todo with :id + /// ``` + /// + /// Generated paths default to using lower snake case so for example, if you were to do + /// + /// ```swift + /// router.crud(register: SchoolTeacher.self) + /// ``` + /// you'd get routes like + /// + /// ``` + /// GET /school_teacher + /// GET /school_teacher/:id + /// POST /school_teacher + /// PUT /school_teacher/:id + /// DELETE /school_teacher/:id + /// ``` + /// + /// - Parameter path: Overrides the path instead of using the default path string. + /// - Parameter type: Model to generate endpoints for + /// - Parameter either: Users can select a subset of endpoints to generate with `.only()` or exclude a subset of endpoints with `.except()` + /// - Parameter relationConfiguration: Closure that can be used to configure endpoints for children, parents or siblings of `type`. + /// + /// For example + /// ``` + /// app.crud("foo", register: Planet.self) { router in + /// router.crud(at: "foo", siblings: \.$tags) + /// router.crud(at: "foo", parent: \.$galaxy) + /// } + /// + /// app.crud("foo", register: Galaxy.self, .only([.delete])) { router in + /// router.crud(at: "foo", children: \.$planets) + /// } + /// ``` func crud( - _ path: PathComponent..., + _ path: [PathComponent]=[], register type: ModelType.Type, _ either: OnlyExceptEither = .only([.read, .readAll, .create, .update, .delete]), relationConfiguration: ((CrudController) -> ())?=nil ) { - let allMethods: Set = Set([.read, .readAll, .create, .update, .delete]) let controller: CrudController switch either { case .only(let methods): - controller = CrudController(path: path, router: self, activeMethods: Set(methods)) + controller = CrudController( + path: path, + router: self, + activeMethods: Set(methods) + ) case .except(let methods): - controller = CrudController(path: path, router: self, activeMethods: allMethods.subtracting(Set(methods))) + let allMethods = Set(RouterMethod.allCases) + controller = CrudController( + path: path, + router: self, + activeMethods: allMethods.subtracting(Set(methods)) + ) } do { try controller.boot(routes: self) } catch { fatalError("I have no reason to expect boot to throw") } relationConfiguration?(controller) } + + /// Creates CRUD endpoints for the suplied `type`. + /// + /// By default, the routes will be created that + /// - Get an individual model with the provided ID + /// - Get all models + /// - Create a new model + /// - Update a model with the provided ID + /// - Delete a module with the provided ID + /// + /// For example + /// ```swift + /// router.crud(register: Todo.self) + /// ``` + /// Will create the following routes. + /// + /// ``` + /// GET /todo // returns all Todos + /// GET /todo/:id // returns the Todo with :id + /// POST /todo // create new Todo with provided body + /// PUT /todo/:id // update Todo with :id + /// DELETE /todo/:id // delete Todo with :id + /// ``` + /// + /// Generated paths default to using lower snake case so for example, if you were to do + /// + /// ```swift + /// router.crud(register: SchoolTeacher.self) + /// ``` + /// you'd get routes like + /// + /// ``` + /// GET /school_teacher + /// GET /school_teacher/:id + /// POST /school_teacher + /// PUT /school_teacher/:id + /// DELETE /school_teacher/:id + /// ``` + /// + /// - Parameter path: Overrides the path instead of using the default path string. + /// - Parameter type: Model to generate endpoints for + /// - Parameter either: Users can select a subset of endpoints to generate with `.only()` or exclude a subset of endpoints with `.except()` + /// - Parameter relationConfiguration: Closure that can be used to configure endpoints for children, parents or siblings of `type`. + /// + /// For example + /// ``` + /// app.crud("foo", register: Planet.self) { router in + /// router.crud(at: "foo", siblings: \.$tags) + /// router.crud(at: "foo", parent: \.$galaxy) + /// } + /// + /// app.crud("foo", register: Galaxy.self, .only([.delete])) { router in + /// router.crud(at: "foo", children: \.$planets) + /// } + /// ``` + func crud( + _ path: PathComponent..., + register type: ModelType.Type, + _ either: OnlyExceptEither = .only([.read, .readAll, .create, .update, .delete]), + relationConfiguration: ((CrudController) -> ())?=nil + ) { + crud(path, register: type, either, relationConfiguration: relationConfiguration) + } } diff --git a/Sources/CrudRouter/Extensions/String.swift b/Sources/CrudRouter/Extensions/String.swift index 6aa3279..8d5ca62 100644 --- a/Sources/CrudRouter/Extensions/String.swift +++ b/Sources/CrudRouter/Extensions/String.swift @@ -1,11 +1,11 @@ import Foundation -public extension String { - func snakeCased() -> String? { +extension String { + func snakeCased() -> String { let pattern = "([a-z0-9])([A-Z])" - let regex = try? NSRegularExpression(pattern: pattern, options: []) + let regex = try! NSRegularExpression(pattern: pattern, options: []) let range = NSRange(location: 0, length: self.count) - return regex?.stringByReplacingMatches(in: self, options: [], range: range, withTemplate: "$1_$2").lowercased() + return regex.stringByReplacingMatches(in: self, options: [], range: range, withTemplate: "$1_$2").lowercased() } } diff --git a/Sources/CrudRouter/RouterMethods/ChildrenRouterMethod.swift b/Sources/CrudRouter/RouterMethods/ChildrenRouterMethod.swift index 5ad3e25..0141276 100644 --- a/Sources/CrudRouter/RouterMethods/ChildrenRouterMethod.swift +++ b/Sources/CrudRouter/RouterMethods/ChildrenRouterMethod.swift @@ -1,6 +1,6 @@ import Vapor -public enum ChildrenRouterMethod { +public enum ChildrenRouterMethod: CaseIterable { case read case readAll case create diff --git a/Sources/CrudRouter/RouterMethods/ParentRouterMethod.swift b/Sources/CrudRouter/RouterMethods/ParentRouterMethod.swift index c882f6c..d5dea6a 100644 --- a/Sources/CrudRouter/RouterMethods/ParentRouterMethod.swift +++ b/Sources/CrudRouter/RouterMethods/ParentRouterMethod.swift @@ -1,6 +1,6 @@ import Vapor -public enum ParentRouterMethod { +public enum ParentRouterMethod: CaseIterable { case read case update diff --git a/Sources/CrudRouter/RouterMethods/RouterMethod.swift b/Sources/CrudRouter/RouterMethods/RouterMethod.swift index 9c8e4ac..cb17751 100644 --- a/Sources/CrudRouter/RouterMethods/RouterMethod.swift +++ b/Sources/CrudRouter/RouterMethods/RouterMethod.swift @@ -1,6 +1,6 @@ import Vapor -public enum RouterMethod { +public enum RouterMethod: CaseIterable { case read case readAll case create diff --git a/Sources/CrudRouter/RouterMethods/SiblingRouterMethod.swift b/Sources/CrudRouter/RouterMethods/SiblingRouterMethod.swift index a2e9158..44f5f45 100644 --- a/Sources/CrudRouter/RouterMethods/SiblingRouterMethod.swift +++ b/Sources/CrudRouter/RouterMethods/SiblingRouterMethod.swift @@ -1,7 +1,6 @@ import Vapor -import Fluent -public enum ModifiableSiblingRouterMethod { +public enum SiblingRouterMethod: CaseIterable { case read case readAll case create