feat: Updates to use swift-validations for database.
All checks were successful
CI / Linux Tests (push) Successful in 6m28s
All checks were successful
CI / Linux Tests (push) Successful in 6m28s
This commit is contained in:
48
Sources/DatabaseClient/Internal/Array+validator.swift
Normal file
48
Sources/DatabaseClient/Internal/Array+validator.swift
Normal file
@@ -0,0 +1,48 @@
|
||||
import Validations
|
||||
|
||||
extension Validator {
|
||||
|
||||
static func validate<Child: Validatable>(
|
||||
_ toChild: KeyPath<Value, [Child]>
|
||||
)
|
||||
-> Self
|
||||
{
|
||||
self.mapValue({ $0[keyPath: toChild] }, with: ArrayValidator())
|
||||
}
|
||||
|
||||
static func validate<Child: Validatable>(
|
||||
_ toChild: KeyPath<Value, [Child]?>
|
||||
)
|
||||
-> Self
|
||||
{
|
||||
self.mapValue({ $0[keyPath: toChild] }, with: ArrayValidator().optional())
|
||||
}
|
||||
}
|
||||
|
||||
extension Array where Element: Validatable {
|
||||
static func validator() -> some Validation<Self> {
|
||||
ArrayValidator<Element>()
|
||||
}
|
||||
}
|
||||
|
||||
struct ArrayValidator<Element>: Validation where Element: Validatable {
|
||||
func validate(_ value: [Element]) throws {
|
||||
for item in value {
|
||||
try item.validate()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ForEachValidator<T, E>: Validation where T: Validation, T.Value == E {
|
||||
let validator: T
|
||||
|
||||
init(@ValidationBuilder<E> builder: () -> T) {
|
||||
self.validator = builder()
|
||||
}
|
||||
|
||||
func validate(_ value: [E]) throws {
|
||||
for item in value {
|
||||
try validator.validate(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import Fluent
|
||||
import Foundation
|
||||
import ManualDCore
|
||||
import SQLKit
|
||||
import Validations
|
||||
|
||||
extension DatabaseClient.ComponentLosses: TestDependencyKey {
|
||||
public static let testValue = Self()
|
||||
@@ -13,8 +14,8 @@ extension DatabaseClient.ComponentLosses {
|
||||
public static func live(database: any Database) -> Self {
|
||||
.init(
|
||||
create: { request in
|
||||
let model = try request.toModel()
|
||||
try await model.save(on: database)
|
||||
let model = request.toModel()
|
||||
try await model.validateAndSave(on: database)
|
||||
return try model.toDTO()
|
||||
},
|
||||
delete: { id in
|
||||
@@ -35,13 +36,13 @@ extension DatabaseClient.ComponentLosses {
|
||||
try await ComponentLossModel.find(id, on: database).map { try $0.toDTO() }
|
||||
},
|
||||
update: { id, updates in
|
||||
try updates.validate()
|
||||
// try updates.validate()
|
||||
guard let model = try await ComponentLossModel.find(id, on: database) else {
|
||||
throw NotFoundError()
|
||||
}
|
||||
model.applyUpdates(updates)
|
||||
if model.hasChanges {
|
||||
try await model.save(on: database)
|
||||
try await model.validateAndSave(on: database)
|
||||
}
|
||||
return try model.toDTO()
|
||||
}
|
||||
@@ -51,40 +52,9 @@ extension DatabaseClient.ComponentLosses {
|
||||
|
||||
extension ComponentPressureLoss.Create {
|
||||
|
||||
func toModel() throws(ValidationError) -> ComponentLossModel {
|
||||
try validate()
|
||||
func toModel() -> ComponentLossModel {
|
||||
return .init(name: name, value: value, projectID: projectID)
|
||||
}
|
||||
|
||||
func validate() throws(ValidationError) {
|
||||
guard !name.isEmpty else {
|
||||
throw ValidationError("Component loss name should not be empty.")
|
||||
}
|
||||
guard value > 0 else {
|
||||
throw ValidationError("Component loss value should be greater than 0.")
|
||||
}
|
||||
guard value < 1.0 else {
|
||||
throw ValidationError("Component loss value should be less than 1.0.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ComponentPressureLoss.Update {
|
||||
func validate() throws(ValidationError) {
|
||||
if let name {
|
||||
guard !name.isEmpty else {
|
||||
throw ValidationError("Component loss name should not be empty.")
|
||||
}
|
||||
}
|
||||
if let value {
|
||||
guard value > 0 else {
|
||||
throw ValidationError("Component loss value should be greater than 0.")
|
||||
}
|
||||
guard value < 1.0 else {
|
||||
throw ValidationError("Component loss value should be less than 1.0.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ComponentPressureLoss {
|
||||
@@ -171,3 +141,19 @@ final class ComponentLossModel: Model, @unchecked Sendable {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ComponentLossModel: Validatable {
|
||||
|
||||
var body: some Validation<ComponentLossModel> {
|
||||
Validator.accumulating {
|
||||
Validator.validate(\.name, with: .notEmpty())
|
||||
.errorLabel("Name", inline: true)
|
||||
|
||||
Validator.validate(\.value) {
|
||||
Double.greaterThan(0.0)
|
||||
Double.lessThanOrEquals(1.0)
|
||||
}
|
||||
.errorLabel("Value", inline: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import DependenciesMacros
|
||||
import Fluent
|
||||
import Foundation
|
||||
import ManualDCore
|
||||
import Validations
|
||||
|
||||
extension DatabaseClient.Equipment: TestDependencyKey {
|
||||
public static let testValue = Self()
|
||||
@@ -10,8 +11,8 @@ extension DatabaseClient.Equipment: TestDependencyKey {
|
||||
public static func live(database: any Database) -> Self {
|
||||
.init(
|
||||
create: { request in
|
||||
let model = try request.toModel()
|
||||
try await model.save(on: database)
|
||||
let model = request.toModel()
|
||||
try await model.validateAndSave(on: database)
|
||||
return try model.toDTO()
|
||||
},
|
||||
delete: { id in
|
||||
@@ -37,10 +38,9 @@ extension DatabaseClient.Equipment: TestDependencyKey {
|
||||
guard let model = try await EquipmentModel.find(id, on: database) else {
|
||||
throw NotFoundError()
|
||||
}
|
||||
try updates.validate()
|
||||
model.applyUpdates(updates)
|
||||
if model.hasChanges {
|
||||
try await model.save(on: database)
|
||||
try await model.validateAndSave(on: database)
|
||||
}
|
||||
return try model.toDTO()
|
||||
}
|
||||
@@ -50,8 +50,7 @@ extension DatabaseClient.Equipment: TestDependencyKey {
|
||||
|
||||
extension EquipmentInfo.Create {
|
||||
|
||||
func toModel() throws(ValidationError) -> EquipmentModel {
|
||||
try validate()
|
||||
func toModel() -> EquipmentModel {
|
||||
return .init(
|
||||
staticPressure: staticPressure,
|
||||
heatingCFM: heatingCFM,
|
||||
@@ -60,44 +59,6 @@ extension EquipmentInfo.Create {
|
||||
)
|
||||
}
|
||||
|
||||
func validate() throws(ValidationError) {
|
||||
guard staticPressure >= 0 else {
|
||||
throw ValidationError("Equipment info static pressure should be greater than 0.")
|
||||
}
|
||||
guard staticPressure <= 1.0 else {
|
||||
throw ValidationError("Equipment info static pressure should be less than 1.0.")
|
||||
}
|
||||
guard heatingCFM >= 0 else {
|
||||
throw ValidationError("Equipment info heating CFM should be greater than 0.")
|
||||
}
|
||||
guard coolingCFM >= 0 else {
|
||||
throw ValidationError("Equipment info heating CFM should be greater than 0.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension EquipmentInfo.Update {
|
||||
var hasUpdates: Bool {
|
||||
staticPressure != nil || heatingCFM != nil || coolingCFM != nil
|
||||
}
|
||||
|
||||
func validate() throws(ValidationError) {
|
||||
if let staticPressure {
|
||||
guard staticPressure >= 0 else {
|
||||
throw ValidationError("Equipment info static pressure should be greater than 0.")
|
||||
}
|
||||
}
|
||||
if let heatingCFM {
|
||||
guard heatingCFM >= 0 else {
|
||||
throw ValidationError("Equipment info heating CFM should be greater than 0.")
|
||||
}
|
||||
}
|
||||
if let coolingCFM {
|
||||
guard coolingCFM >= 0 else {
|
||||
throw ValidationError("Equipment info heating CFM should be greater than 0.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension EquipmentInfo {
|
||||
@@ -197,3 +158,22 @@ final class EquipmentModel: Model, @unchecked Sendable {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension EquipmentModel: Validatable {
|
||||
|
||||
var body: some Validation<EquipmentModel> {
|
||||
Validator.accumulating {
|
||||
Validator.validate(\.staticPressure) {
|
||||
Double.greaterThan(0.0)
|
||||
Double.lessThan(1.0)
|
||||
}
|
||||
.errorLabel("Static Pressure", inline: true)
|
||||
|
||||
Validator.validate(\.heatingCFM, with: .greaterThan(0))
|
||||
.errorLabel("Heating CFM", inline: true)
|
||||
|
||||
Validator.validate(\.coolingCFM, with: .greaterThan(0))
|
||||
.errorLabel("Cooling CFM", inline: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import DependenciesMacros
|
||||
import Fluent
|
||||
import Foundation
|
||||
import ManualDCore
|
||||
import Validations
|
||||
|
||||
extension DatabaseClient.EquivalentLengths: TestDependencyKey {
|
||||
public static let testValue = Self()
|
||||
@@ -11,7 +12,7 @@ extension DatabaseClient.EquivalentLengths: TestDependencyKey {
|
||||
.init(
|
||||
create: { request in
|
||||
let model = try request.toModel()
|
||||
try await model.save(on: database)
|
||||
try await model.validateAndSave(on: database)
|
||||
return try model.toDTO()
|
||||
},
|
||||
delete: { id in
|
||||
@@ -53,7 +54,7 @@ extension DatabaseClient.EquivalentLengths: TestDependencyKey {
|
||||
}
|
||||
try model.applyUpdates(updates)
|
||||
if model.hasChanges {
|
||||
try await model.save(on: database)
|
||||
try await model.validateAndSave(on: database)
|
||||
}
|
||||
return try model.toDTO()
|
||||
}
|
||||
@@ -64,7 +65,9 @@ extension DatabaseClient.EquivalentLengths: TestDependencyKey {
|
||||
extension EquivalentLength.Create {
|
||||
|
||||
func toModel() throws -> EffectiveLengthModel {
|
||||
try validate()
|
||||
if groups.count > 0 {
|
||||
try [EquivalentLength.FittingGroup].validator().validate(groups)
|
||||
}
|
||||
return try .init(
|
||||
name: name,
|
||||
type: type.rawValue,
|
||||
@@ -73,12 +76,6 @@ extension EquivalentLength.Create {
|
||||
projectID: projectID
|
||||
)
|
||||
}
|
||||
|
||||
func validate() throws(ValidationError) {
|
||||
guard !name.isEmpty else {
|
||||
throw ValidationError("Effective length name can not be empty.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension EquivalentLength {
|
||||
@@ -184,7 +181,51 @@ final class EffectiveLengthModel: Model, @unchecked Sendable {
|
||||
self.straightLengths = straightLengths
|
||||
}
|
||||
if let groups = updates.groups {
|
||||
if groups.count > 0 {
|
||||
try [EquivalentLength.FittingGroup].validator().validate(groups)
|
||||
}
|
||||
self.groups = try JSONEncoder().encode(groups)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension EffectiveLengthModel: Validatable {
|
||||
|
||||
var body: some Validation<EffectiveLengthModel> {
|
||||
Validator.accumulating {
|
||||
Validator.validate(\.name, with: .notEmpty())
|
||||
.errorLabel("Name", inline: true)
|
||||
|
||||
Validator.validate(
|
||||
\.straightLengths,
|
||||
with: [Int].empty().or(
|
||||
ForEachValidator {
|
||||
Int.greaterThan(0)
|
||||
})
|
||||
)
|
||||
.errorLabel("Straight Lengths", inline: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension EquivalentLength.FittingGroup: Validatable {
|
||||
|
||||
public var body: some Validation<Self> {
|
||||
Validator.accumulating {
|
||||
Validator.validate(\.group) {
|
||||
Int.greaterThanOrEquals(1)
|
||||
Int.lessThanOrEquals(12)
|
||||
}
|
||||
.errorLabel("Group", inline: true)
|
||||
|
||||
Validator.validate(\.letter, with: .regex(matching: "[a-zA-Z]"))
|
||||
.errorLabel("Letter", inline: true)
|
||||
|
||||
Validator.validate(\.value, with: .greaterThan(0))
|
||||
.errorLabel("Value", inline: true)
|
||||
|
||||
Validator.validate(\.quantity, with: .greaterThanOrEquals(1))
|
||||
.errorLabel("Quantity", inline: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
10
Sources/DatabaseClient/Internal/Model+validateAndSave.swift
Normal file
10
Sources/DatabaseClient/Internal/Model+validateAndSave.swift
Normal file
@@ -0,0 +1,10 @@
|
||||
import Fluent
|
||||
import Validations
|
||||
|
||||
extension Model where Self: Validations.Validatable {
|
||||
|
||||
func validateAndSave(on database: any Database) async throws {
|
||||
try self.validate()
|
||||
try await self.save(on: database)
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import DependenciesMacros
|
||||
import Fluent
|
||||
import Foundation
|
||||
import ManualDCore
|
||||
import Validations
|
||||
|
||||
extension DatabaseClient.Projects: TestDependencyKey {
|
||||
public static let testValue = Self()
|
||||
@@ -10,8 +11,8 @@ extension DatabaseClient.Projects: TestDependencyKey {
|
||||
public static func live(database: any Database) -> Self {
|
||||
.init(
|
||||
create: { userID, request in
|
||||
let model = try request.toModel(userID: userID)
|
||||
try await model.save(on: database)
|
||||
let model = request.toModel(userID: userID)
|
||||
try await model.validateAndSave(on: database)
|
||||
return try model.toDTO()
|
||||
},
|
||||
delete: { id in
|
||||
@@ -81,10 +82,9 @@ extension DatabaseClient.Projects: TestDependencyKey {
|
||||
guard let model = try await ProjectModel.find(id, on: database) else {
|
||||
throw NotFoundError()
|
||||
}
|
||||
try updates.validate()
|
||||
model.applyUpdates(updates)
|
||||
if model.hasChanges {
|
||||
try await model.save(on: database)
|
||||
try await model.validateAndSave(on: database)
|
||||
}
|
||||
return try model.toDTO()
|
||||
}
|
||||
@@ -94,8 +94,7 @@ extension DatabaseClient.Projects: TestDependencyKey {
|
||||
|
||||
extension Project.Create {
|
||||
|
||||
func toModel(userID: User.ID) throws -> ProjectModel {
|
||||
try validate()
|
||||
func toModel(userID: User.ID) -> ProjectModel {
|
||||
return .init(
|
||||
name: name,
|
||||
streetAddress: streetAddress,
|
||||
@@ -106,70 +105,6 @@ extension Project.Create {
|
||||
)
|
||||
}
|
||||
|
||||
func validate() throws(ValidationError) {
|
||||
guard !name.isEmpty else {
|
||||
throw ValidationError("Project name should not be empty.")
|
||||
}
|
||||
guard !streetAddress.isEmpty else {
|
||||
throw ValidationError("Project street address should not be empty.")
|
||||
}
|
||||
guard !city.isEmpty else {
|
||||
throw ValidationError("Project city should not be empty.")
|
||||
}
|
||||
guard !state.isEmpty else {
|
||||
throw ValidationError("Project state should not be empty.")
|
||||
}
|
||||
guard !zipCode.isEmpty else {
|
||||
throw ValidationError("Project zipCode should not be empty.")
|
||||
}
|
||||
if let sensibleHeatRatio {
|
||||
guard sensibleHeatRatio >= 0 else {
|
||||
throw ValidationError("Project sensible heat ratio should be greater than 0.")
|
||||
}
|
||||
guard sensibleHeatRatio <= 1 else {
|
||||
throw ValidationError("Project sensible heat ratio should be less than 1.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Project.Update {
|
||||
|
||||
func validate() throws(ValidationError) {
|
||||
if let name {
|
||||
guard !name.isEmpty else {
|
||||
throw ValidationError("Project name should not be empty.")
|
||||
}
|
||||
}
|
||||
if let streetAddress {
|
||||
guard !streetAddress.isEmpty else {
|
||||
throw ValidationError("Project street address should not be empty.")
|
||||
}
|
||||
}
|
||||
if let city {
|
||||
guard !city.isEmpty else {
|
||||
throw ValidationError("Project city should not be empty.")
|
||||
}
|
||||
}
|
||||
if let state {
|
||||
guard !state.isEmpty else {
|
||||
throw ValidationError("Project state should not be empty.")
|
||||
}
|
||||
}
|
||||
if let zipCode {
|
||||
guard !zipCode.isEmpty else {
|
||||
throw ValidationError("Project zipCode should not be empty.")
|
||||
}
|
||||
}
|
||||
if let sensibleHeatRatio {
|
||||
guard sensibleHeatRatio >= 0 else {
|
||||
throw ValidationError("Project sensible heat ratio should be greater than 0.")
|
||||
}
|
||||
guard sensibleHeatRatio <= 1 else {
|
||||
throw ValidationError("Project sensible heat ratio should be less than 1.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Project {
|
||||
@@ -342,3 +277,35 @@ final class ProjectModel: Model, @unchecked Sendable {
|
||||
return model
|
||||
}
|
||||
}
|
||||
|
||||
extension ProjectModel: Validatable {
|
||||
|
||||
var body: some Validation<ProjectModel> {
|
||||
Validator.accumulating {
|
||||
Validator.validate(\.name, with: .notEmpty())
|
||||
.errorLabel("Name", inline: true)
|
||||
|
||||
Validator.validate(\.streetAddress, with: .notEmpty())
|
||||
.errorLabel("Address", inline: true)
|
||||
|
||||
Validator.validate(\.city, with: .notEmpty())
|
||||
.errorLabel("City", inline: true)
|
||||
|
||||
Validator.validate(\.state, with: .notEmpty())
|
||||
.errorLabel("State", inline: true)
|
||||
|
||||
Validator.validate(\.zipCode, with: .notEmpty())
|
||||
.errorLabel("Zip", inline: true)
|
||||
|
||||
Validator.validate(\.sensibleHeatRatio) {
|
||||
Validator {
|
||||
Double.greaterThan(0)
|
||||
Double.lessThanOrEquals(1.0)
|
||||
}
|
||||
.optional()
|
||||
}
|
||||
.errorLabel("Sensible Heat Ratio", inline: true)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import DependenciesMacros
|
||||
import Fluent
|
||||
import Foundation
|
||||
import ManualDCore
|
||||
import Validations
|
||||
|
||||
extension DatabaseClient.Rooms: TestDependencyKey {
|
||||
public static let testValue = Self()
|
||||
@@ -11,7 +12,7 @@ extension DatabaseClient.Rooms: TestDependencyKey {
|
||||
.init(
|
||||
create: { request in
|
||||
let model = try request.toModel()
|
||||
try await model.save(on: database)
|
||||
try await model.validateAndSave(on: database)
|
||||
return try model.toDTO()
|
||||
},
|
||||
delete: { id in
|
||||
@@ -31,7 +32,7 @@ extension DatabaseClient.Rooms: TestDependencyKey {
|
||||
model.rectangularSizes = nil
|
||||
}
|
||||
if model.hasChanges {
|
||||
try await model.save(on: database)
|
||||
try await model.validateAndSave(on: database)
|
||||
}
|
||||
return try model.toDTO()
|
||||
},
|
||||
@@ -50,11 +51,9 @@ extension DatabaseClient.Rooms: TestDependencyKey {
|
||||
guard let model = try await RoomModel.find(id, on: database) else {
|
||||
throw NotFoundError()
|
||||
}
|
||||
|
||||
try updates.validate()
|
||||
model.applyUpdates(updates)
|
||||
if model.hasChanges {
|
||||
try await model.save(on: database)
|
||||
try await model.validateAndSave(on: database)
|
||||
}
|
||||
return try model.toDTO()
|
||||
},
|
||||
@@ -77,8 +76,7 @@ extension DatabaseClient.Rooms: TestDependencyKey {
|
||||
|
||||
extension Room.Create {
|
||||
|
||||
func toModel() throws(ValidationError) -> RoomModel {
|
||||
try validate()
|
||||
func toModel() throws -> RoomModel {
|
||||
return .init(
|
||||
name: name,
|
||||
heatingLoad: heatingLoad,
|
||||
@@ -88,57 +86,6 @@ extension Room.Create {
|
||||
projectID: projectID
|
||||
)
|
||||
}
|
||||
|
||||
func validate() throws(ValidationError) {
|
||||
guard !name.isEmpty else {
|
||||
throw ValidationError("Room name should not be empty.")
|
||||
}
|
||||
guard heatingLoad >= 0 else {
|
||||
throw ValidationError("Room heating load should not be less than 0.")
|
||||
}
|
||||
guard coolingTotal >= 0 else {
|
||||
throw ValidationError("Room cooling total should not be less than 0.")
|
||||
}
|
||||
if let coolingSensible {
|
||||
guard coolingSensible >= 0 else {
|
||||
throw ValidationError("Room cooling sensible should not be less than 0.")
|
||||
}
|
||||
}
|
||||
guard registerCount >= 1 else {
|
||||
throw ValidationError("Room cooling sensible should not be less than 1.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Room.Update {
|
||||
|
||||
func validate() throws(ValidationError) {
|
||||
if let name {
|
||||
guard !name.isEmpty else {
|
||||
throw ValidationError("Room name should not be empty.")
|
||||
}
|
||||
}
|
||||
if let heatingLoad {
|
||||
guard heatingLoad >= 0 else {
|
||||
throw ValidationError("Room heating load should not be less than 0.")
|
||||
}
|
||||
}
|
||||
if let coolingTotal {
|
||||
guard coolingTotal >= 0 else {
|
||||
throw ValidationError("Room cooling total should not be less than 0.")
|
||||
}
|
||||
}
|
||||
if let coolingSensible {
|
||||
guard coolingSensible >= 0 else {
|
||||
throw ValidationError("Room cooling sensible should not be less than 0.")
|
||||
}
|
||||
}
|
||||
if let registerCount {
|
||||
guard registerCount >= 1 else {
|
||||
throw ValidationError("Room cooling sensible should not be less than 1.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Room {
|
||||
@@ -169,7 +116,7 @@ extension Room {
|
||||
}
|
||||
}
|
||||
|
||||
final class RoomModel: Model, @unchecked Sendable {
|
||||
final class RoomModel: Model, @unchecked Sendable, Validatable {
|
||||
|
||||
static let schema = "room"
|
||||
|
||||
@@ -267,4 +214,38 @@ final class RoomModel: Model, @unchecked Sendable {
|
||||
|
||||
}
|
||||
|
||||
var body: some Validation<RoomModel> {
|
||||
Validator.accumulating {
|
||||
Validator.validate(\.name, with: .notEmpty())
|
||||
.errorLabel("Name", inline: true)
|
||||
|
||||
Validator.validate(\.heatingLoad, with: .greaterThanOrEquals(0))
|
||||
.errorLabel("Heating Load", inline: true)
|
||||
|
||||
Validator.validate(\.coolingTotal, with: .greaterThanOrEquals(0))
|
||||
.errorLabel("Cooling Total", inline: true)
|
||||
|
||||
Validator.validate(\.coolingSensible, with: Double.greaterThanOrEquals(0).optional())
|
||||
.errorLabel("Cooling Sensible", inline: true)
|
||||
|
||||
Validator.validate(\.registerCount, with: .greaterThanOrEquals(1))
|
||||
.errorLabel("Register Count", inline: true)
|
||||
|
||||
Validator.validate(\.rectangularSizes)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Room.RectangularSize: Validatable {
|
||||
|
||||
public var body: some Validation<Self> {
|
||||
Validator.accumulating {
|
||||
Validator.validate(\.register, with: Int.greaterThanOrEquals(1).optional())
|
||||
.errorLabel("Register", inline: true)
|
||||
|
||||
Validator.validate(\.height, with: Int.greaterThanOrEquals(1))
|
||||
.errorLabel("Height", inline: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import DependenciesMacros
|
||||
import Fluent
|
||||
import Foundation
|
||||
import ManualDCore
|
||||
import Validations
|
||||
|
||||
extension DatabaseClient.TrunkSizes: TestDependencyKey {
|
||||
public static let testValue = Self()
|
||||
@@ -10,12 +11,12 @@ extension DatabaseClient.TrunkSizes: TestDependencyKey {
|
||||
public static func live(database: any Database) -> Self {
|
||||
.init(
|
||||
create: { request in
|
||||
try request.validate()
|
||||
// try request.validate()
|
||||
|
||||
let trunk = request.toModel()
|
||||
var roomProxies = [TrunkSize.RoomProxy]()
|
||||
|
||||
try await trunk.save(on: database)
|
||||
try await trunk.validateAndSave(on: database)
|
||||
|
||||
for (roomID, registers) in request.rooms {
|
||||
guard let room = try await RoomModel.find(roomID, on: database) else {
|
||||
@@ -27,7 +28,7 @@ extension DatabaseClient.TrunkSizes: TestDependencyKey {
|
||||
registers: registers,
|
||||
type: request.type
|
||||
)
|
||||
try await model.save(on: database)
|
||||
try await model.validateAndSave(on: database)
|
||||
roomProxies.append(
|
||||
.init(room: try room.toDTO(), registers: registers)
|
||||
)
|
||||
@@ -80,7 +81,7 @@ extension DatabaseClient.TrunkSizes: TestDependencyKey {
|
||||
else {
|
||||
throw NotFoundError()
|
||||
}
|
||||
try updates.validate()
|
||||
// try updates.validate()
|
||||
try await model.applyUpdates(updates, on: database)
|
||||
return try model.toDTO()
|
||||
}
|
||||
@@ -90,17 +91,6 @@ extension DatabaseClient.TrunkSizes: TestDependencyKey {
|
||||
|
||||
extension TrunkSize.Create {
|
||||
|
||||
func validate() throws(ValidationError) {
|
||||
guard rooms.count > 0 else {
|
||||
throw ValidationError("Trunk size should have associated rooms / registers.")
|
||||
}
|
||||
if let height {
|
||||
guard height > 0 else {
|
||||
throw ValidationError("Trunk size height should be greater than 0.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func toModel() -> TrunkModel {
|
||||
.init(
|
||||
projectID: projectID,
|
||||
@@ -111,21 +101,6 @@ extension TrunkSize.Create {
|
||||
}
|
||||
}
|
||||
|
||||
extension TrunkSize.Update {
|
||||
func validate() throws(ValidationError) {
|
||||
if let rooms {
|
||||
guard rooms.count > 0 else {
|
||||
throw ValidationError("Trunk size should have associated rooms / registers.")
|
||||
}
|
||||
}
|
||||
if let height {
|
||||
guard height > 0 else {
|
||||
throw ValidationError("Trunk size height should be greater than 0.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension TrunkSize {
|
||||
|
||||
struct Migrate: AsyncMigration {
|
||||
@@ -205,7 +180,17 @@ final class TrunkRoomModel: Model, @unchecked Sendable {
|
||||
registers: registers
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
extension TrunkRoomModel: Validatable {
|
||||
var body: some Validation<TrunkRoomModel> {
|
||||
Validator.validate(\.registers) {
|
||||
[Int].notEmpty()
|
||||
ForEachValidator {
|
||||
Int.greaterThanOrEquals(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class TrunkModel: Model, @unchecked Sendable {
|
||||
@@ -276,7 +261,7 @@ final class TrunkModel: Model, @unchecked Sendable {
|
||||
self.name = name
|
||||
}
|
||||
if hasChanges {
|
||||
try await self.save(on: database)
|
||||
try await self.validateAndSave(on: database)
|
||||
}
|
||||
|
||||
guard let updateRooms = updates.rooms else {
|
||||
@@ -297,7 +282,7 @@ final class TrunkModel: Model, @unchecked Sendable {
|
||||
currRoom.registers = registers
|
||||
}
|
||||
if currRoom.hasChanges {
|
||||
try await currRoom.save(on: database)
|
||||
try await currRoom.validateAndSave(on: database)
|
||||
}
|
||||
} else {
|
||||
database.logger.debug("CREATING NEW TrunkRoomModel")
|
||||
@@ -324,6 +309,21 @@ final class TrunkModel: Model, @unchecked Sendable {
|
||||
}
|
||||
}
|
||||
|
||||
extension TrunkModel: Validatable {
|
||||
|
||||
var body: some Validation<TrunkModel> {
|
||||
Validator.accumulating {
|
||||
|
||||
Validator.validate(\.height, with: Int.greaterThan(0).optional())
|
||||
.errorLabel("Height", inline: true)
|
||||
|
||||
Validator.validate(\.name, with: String.notEmpty().optional())
|
||||
.errorLabel("Name", inline: true)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Array where Element == TrunkModel {
|
||||
|
||||
func toDTO() throws -> [TrunkSize] {
|
||||
|
||||
20
Sources/DatabaseClient/Internal/User+validation.swift
Normal file
20
Sources/DatabaseClient/Internal/User+validation.swift
Normal file
@@ -0,0 +1,20 @@
|
||||
import ManualDCore
|
||||
import Validations
|
||||
|
||||
// Declaring this in seperate file because some Vapor imports
|
||||
// have same name's and this was easiest solution.
|
||||
extension User.Create: Validatable {
|
||||
public var body: some Validation<Self> {
|
||||
Validator.accumulating {
|
||||
Validator.validate(\.email, with: .email())
|
||||
.errorLabel("Email", inline: true)
|
||||
|
||||
Validator.validate(\.password.count, with: .greaterThanOrEquals(8))
|
||||
.errorLabel("Password Count", inline: true)
|
||||
|
||||
Validator.validate(\.confirmPassword, with: .equals(password))
|
||||
.mapError(ValidationError("Confirm password does not match."))
|
||||
.errorLabel("Confirm Password", inline: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
import Dependencies
|
||||
import DependenciesMacros
|
||||
import Fluent
|
||||
import Foundation
|
||||
import ManualDCore
|
||||
import Vapor
|
||||
import Validations
|
||||
|
||||
extension DatabaseClient.UserProfiles: TestDependencyKey {
|
||||
|
||||
@@ -11,9 +12,8 @@ extension DatabaseClient.UserProfiles: TestDependencyKey {
|
||||
public static func live(database: any Database) -> Self {
|
||||
.init(
|
||||
create: { profile in
|
||||
try profile.validate()
|
||||
let model = profile.toModel()
|
||||
try await model.save(on: database)
|
||||
try await model.validateAndSave(on: database)
|
||||
return try model.toDTO()
|
||||
},
|
||||
delete: { id in
|
||||
@@ -37,10 +37,9 @@ extension DatabaseClient.UserProfiles: TestDependencyKey {
|
||||
guard let model = try await UserProfileModel.find(id, on: database) else {
|
||||
throw NotFoundError()
|
||||
}
|
||||
try updates.validate()
|
||||
model.applyUpdates(updates)
|
||||
if model.hasChanges {
|
||||
try await model.save(on: database)
|
||||
try await model.validateAndSave(on: database)
|
||||
}
|
||||
return try model.toDTO()
|
||||
}
|
||||
@@ -50,30 +49,6 @@ extension DatabaseClient.UserProfiles: TestDependencyKey {
|
||||
|
||||
extension User.Profile.Create {
|
||||
|
||||
func validate() throws(ValidationError) {
|
||||
guard !firstName.isEmpty else {
|
||||
throw ValidationError("User first name should not be empty.")
|
||||
}
|
||||
guard !lastName.isEmpty else {
|
||||
throw ValidationError("User last name should not be empty.")
|
||||
}
|
||||
guard !companyName.isEmpty else {
|
||||
throw ValidationError("User company name should not be empty.")
|
||||
}
|
||||
guard !streetAddress.isEmpty else {
|
||||
throw ValidationError("User street address should not be empty.")
|
||||
}
|
||||
guard !city.isEmpty else {
|
||||
throw ValidationError("User city should not be empty.")
|
||||
}
|
||||
guard !state.isEmpty else {
|
||||
throw ValidationError("User state should not be empty.")
|
||||
}
|
||||
guard !zipCode.isEmpty else {
|
||||
throw ValidationError("User zip code should not be empty.")
|
||||
}
|
||||
}
|
||||
|
||||
func toModel() -> UserProfileModel {
|
||||
.init(
|
||||
userID: userID,
|
||||
@@ -89,47 +64,6 @@ extension User.Profile.Create {
|
||||
}
|
||||
}
|
||||
|
||||
extension User.Profile.Update {
|
||||
|
||||
func validate() throws(ValidationError) {
|
||||
if let firstName {
|
||||
guard !firstName.isEmpty else {
|
||||
throw ValidationError("User first name should not be empty.")
|
||||
}
|
||||
}
|
||||
if let lastName {
|
||||
guard !lastName.isEmpty else {
|
||||
throw ValidationError("User last name should not be empty.")
|
||||
}
|
||||
}
|
||||
if let companyName {
|
||||
guard !companyName.isEmpty else {
|
||||
throw ValidationError("User company name should not be empty.")
|
||||
}
|
||||
}
|
||||
if let streetAddress {
|
||||
guard !streetAddress.isEmpty else {
|
||||
throw ValidationError("User street address should not be empty.")
|
||||
}
|
||||
}
|
||||
if let city {
|
||||
guard !city.isEmpty else {
|
||||
throw ValidationError("User city should not be empty.")
|
||||
}
|
||||
}
|
||||
if let state {
|
||||
guard !state.isEmpty else {
|
||||
throw ValidationError("User state should not be empty.")
|
||||
}
|
||||
}
|
||||
if let zipCode {
|
||||
guard !zipCode.isEmpty else {
|
||||
throw ValidationError("User zip code should not be empty.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension User.Profile {
|
||||
|
||||
struct Migrate: AsyncMigration {
|
||||
@@ -270,3 +204,31 @@ final class UserProfileModel: Model, @unchecked Sendable {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension UserProfileModel: Validatable {
|
||||
|
||||
var body: some Validation<UserProfileModel> {
|
||||
Validator.accumulating {
|
||||
Validator.validate(\.firstName, with: .notEmpty())
|
||||
.errorLabel("First Name", inline: true)
|
||||
|
||||
Validator.validate(\.lastName, with: .notEmpty())
|
||||
.errorLabel("Last Name", inline: true)
|
||||
|
||||
Validator.validate(\.companyName, with: .notEmpty())
|
||||
.errorLabel("Company", inline: true)
|
||||
|
||||
Validator.validate(\.streetAddress, with: .notEmpty())
|
||||
.errorLabel("Address", inline: true)
|
||||
|
||||
Validator.validate(\.city, with: .notEmpty())
|
||||
.errorLabel("City", inline: true)
|
||||
|
||||
Validator.validate(\.state, with: .notEmpty())
|
||||
.errorLabel("State", inline: true)
|
||||
|
||||
Validator.validate(\.zipCode, with: .notEmpty())
|
||||
.errorLabel("Zip", inline: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import Dependencies
|
||||
import DependenciesMacros
|
||||
import Fluent
|
||||
import Foundation
|
||||
import ManualDCore
|
||||
import Vapor
|
||||
|
||||
@@ -10,6 +11,7 @@ extension DatabaseClient.Users: TestDependencyKey {
|
||||
public static func live(database: any Database) -> Self {
|
||||
.init(
|
||||
create: { request in
|
||||
try request.validate()
|
||||
let model = try request.toModel()
|
||||
try await model.save(on: database)
|
||||
return try model.toDTO()
|
||||
@@ -118,21 +120,8 @@ extension User {
|
||||
extension User.Create {
|
||||
|
||||
func toModel() throws -> UserModel {
|
||||
try validate()
|
||||
return try .init(email: email, passwordHash: User.hashPassword(password))
|
||||
}
|
||||
|
||||
func validate() throws {
|
||||
guard !email.isEmpty else {
|
||||
throw ValidationError("Email should not be empty")
|
||||
}
|
||||
guard password.count > 8 else {
|
||||
throw ValidationError("Password should be more than 8 characters long.")
|
||||
}
|
||||
guard password == confirmPassword else {
|
||||
throw ValidationError("Passwords do not match.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class UserModel: Model, @unchecked Sendable {
|
||||
|
||||
Reference in New Issue
Block a user