Reputation: 151
I've created a Vapor Fluent model that has the following properties, several of which are optional:
final class Skill: Model, Content {
static let schema = "skills"
/// Tells fluent that this property matches the id column
@ID(key: .id)
var id: UUID?
/// Skill name
@Field(key: "name")
var name: String
@Enum(key: "discipline")
var discipline: Discipline
@Enum(key: "apparatus")
var apparatus: Apparatus
@Field(key: "difficulty_value") // Also tried @OptionalField and ran into the same issue
var difficultyValue: DifficultyValue?
@Field(key: "vault_difficulty_value")
var vaultDifficultyValue: Float?
@Field(key: "description")
var description: String
@Field(key: "cop_number")
var copNumber: Float?
@Field(key: "vault_cop_number")
var vaultCopNumber: Float?
@Field(key: "group_number")
var groupNumber: Int
@Field(key: "named_after")
var namedAfter: String?
/// These two columns are here for internal purposes
@Timestamp(key: "created_at", on: .create)
var createdAt: Date?
@Timestamp(key: "updated_at", on: .update)
var updatedAt: Date?
}
I was able to successfully execute a POST request to create an object of this type by decoding the request through a DTO. Here's the DTO:
struct SkillDTO: Content {
var name: String
var discipline: Discipline
var apparatus: Apparatus
var difficultyValue: DifficultyValue?
var vaultDifficultyValue: Float?
var description: String
var copNumber: Float?
var vaultCopNumber: Float?
var groupNumber: Int
var namedAfter: String?
enum CodingKeys: String, CodingKey {
case name, discipline, apparatus, description
case difficultyValue = "difficulty_value"
case vaultDifficultyValue = "vault_difficulty_value"
case copNumber = "cop_number"
case vaultCopNumber = "vault_cop_number"
case groupNumber = "group_number"
case namedAfter = "named_after"
}
func toModel() -> Skill {
let model = Skill()
model.name = self.name
model.discipline = self.discipline
model.apparatus = self.apparatus
if let difficultyValue = self.difficultyValue {
model.difficultyValue = difficultyValue
}
if let vaultDifficultyValue = self.vaultDifficultyValue {
model.vaultDifficultyValue = vaultDifficultyValue
}
model.description = self.description
if let copNumber = self.copNumber {
model.copNumber = copNumber
}
if let vaultCopNumber = self.vaultCopNumber {
model.vaultCopNumber = vaultCopNumber
}
model.groupNumber = self.groupNumber
if let namedAfter = self.namedAfter {
model.namedAfter = namedAfter
}
return model
}
}
And here is the function that is executed upon POST:
func create(req: Request) throws -> EventLoopFuture<HTTPStatus> {
let skill = try req.content.decode(SkillDTO.self).toModel()
return skill.save(on: req.db).transform(to: .ok)
}
However, when trying to fetch all rows from the table using this function
func index(req: Request) async throws -> [Skill] {
try await Skill.query(on: req.db).all()
}
I run into a PostgresDecodingError which states that there is a typeMismatch between a column in the db and a field in my schema. Here is the full error message:
invalid field: 'cop_number', type: Optional<Float>, error: PostgresDecodingError(code: typeMismatch, columnName: "skills_cop_number", columnIndex: 7, targetType: Swift.Optional<Swift.Float>, postgresType: NUMERIC, postgresFormat: binary, postgresData: ByteBuffer
For more context, this is the db migration that I ran to create the table:
return database.schema("skills")
.id()
.field("name", .string, .required)
.field("discipline", .string, .required)
.field("apparatus", .string, .required)
.field("difficulty_value", .string)
.field("vault_difficulty_value", .float)
.field("description", .string, .required)
.field("cop_number", .sql(raw: "NUMERIC(4,3)"))
.field("vault_cop_number", .sql(raw: "NUMERIC(3,2)"))
.field("group_number", .int, .required)
.field("named_after", .string)
.field("created_at", .datetime)
.field("updated_at", .datetime)
.create()
What can I do, or what should I change to fix this typeMismatch issue to successfully execute the GET request?
Upvotes: 1
Views: 89
Reputation: 151
There seems to be some issues when trying to go between NUMERIC
and Optional<Float>
. Changing it to Optional<Decimal>
seems to have solved the issue to a certain degree.
Upvotes: 1