cassenav
cassenav

Reputation: 151

How do I Decode Postgres NUMERIC to Vapor Fluent Optional<Float>?

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

Answers (1)

cassenav
cassenav

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

Related Questions