生成的 OpenAPI golang 客户端似乎无法正确处理日期?

分享于2022年11月15日 go kotlin openapi 问答
【问题标题】:Generated OpenAPI golang client doesn't seem to handle dates correctly?生成的 OpenAPI golang 客户端似乎无法正确处理日期?
【发布时间】:2022-11-03 11:22:24
【问题描述】:

我正在开发一个提供交​​易列表的 API。我用 Java/Kotlin Spring 编写它,但更喜欢 golang 用于 CLI,所以我正在为它生成一个 golang 客户端。该 API 在 Swagger UI 中运行良好。

科特林 API:

@GetMapping
fun listTransactions() : ResponseEntity> {
    val transactions = ArrayList()
    transactionRepository.findAll().mapTo(transactions) { fromEntity(it) }
    return ResponseEntity.ok(transactions)
}

Kotlin 对象:

data class Transaction(
        val id: Long,
        val transactionDate: Date, // Java SQL date
        val postedDate: Date?, // Java SQL date
        val amount: BigDecimal,
        val category: Category,
        val merchant: Merchant,
        val merchantDescription: String?
)

生成的架构:

{
    "openapi": "3.0.1",
    "info": {
        "title": "BFI Swagger Title",
        "description": "BFI description",
        "version": "0.1"
    },
    "servers": [{
        "url": "http://localhost:8080",
        "description": "Generated server url"
    }],
    "paths": {
        "/transaction": {
            "get": {
                "tags": ["transaction-router"],
                "operationId": "listTransactions",
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "*/*": {
                                "schema": {
                                    "type": "array",
                                    "items": {
                                        "$ref": "#/components/schemas/Transaction"
                                    }
                                }
                            }
                        }
                    }
                }
            },
            "post": {
                "tags": ["transaction-router"],
                "operationId": "createTransaction",
                "requestBody": {
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/CreateTransactionRequest"
                            }
                        }
                    },
                    "required": true
                },
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "*/*": {
                                "schema": {
                                    "$ref": "#/components/schemas/Transaction"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/hello": {
            "get": {
                "tags": ["category-router"],
                "summary": "Hello there!",
                "operationId": "hello",
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Category"
                                }
                            }
                        }
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "CreateTransactionRequest": {
                "type": "object",
                "properties": {
                    "transactionDate": {
                        "type": "string",
                        "format": "date-time"
                    },
                    "postedDate": {
                        "type": "string",
                        "format": "date-time"
                    },
                    "amount": {
                        "type": "number"
                    },
                    "categoryId": {
                        "type": "integer",
                        "format": "int64"
                    },
                    "merchantId": {
                        "type": "integer",
                        "format": "int64"
                    },
                    "merchantDescription": {
                        "type": "string"
                    }
                }
            },
            "Category": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "format": "int64"
                    },
                    "name": {
                        "type": "string"
                    },
                    "note": {
                        "type": "string"
                    }
                }
            },
            "Merchant": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "format": "int64"
                    },
                    "name": {
                        "type": "string"
                    },
                    "note": {
                        "type": "string"
                    }
                }
            },
            "Transaction": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "format": "int64"
                    },
                    "transactionDate": {
                        "type": "string",
                        "format": "date-time"
                    },
                    "postedDate": {
                        "type": "string",
                        "format": "date-time"
                    },
                    "amount": {
                        "type": "number"
                    },
                    "category": {
                        "$ref": "#/components/schemas/Category"
                    },
                    "merchant": {
                        "$ref": "#/components/schemas/Merchant"
                    },
                    "merchantDescription": {
                        "type": "string"
                    }
                }
            }
        }
    }
}

Golang 客户端对象:

type Transaction struct {
   Id *int64 `json:"id,omitempty"`
   TransactionDate *time.Time `json:"transactionDate,omitempty"`
   PostedDate *time.Time `json:"postedDate,omitempty"`
   Amount *float32 `json:"amount,omitempty"`
   Category *Category `json:"category,omitempty"`
   Merchant *Merchant `json:"merchant,omitempty"`
   MerchantDescription *string `json:"merchantDescription,omitempty"`
}

所有这些似乎都足够正确。但是,当我使用 OpenAPI 客户端时,反序列化似乎无法正常工作:

Error when calling `TransactionRouterApi.ListTransactions``: parsing time "\"2022-10-28\"" as "\"2006-01-02T15:04:05Z07:00\"": cannot parse "\"" as "T"
Full HTTP response: &{200  200 HTTP/1.1 1 1 map[Content-Type:[application/json] Date:[Sat, 29 Oct 2022 04:03:31 GMT]] {[{"id":1,"transactionDate":"2022-10-28","postedDate":"2022-10-28","amount":0.00,"category":{"id":1,"name":"test","note":"test"},"merchant":{"id":1,"name":"test","note":"test"},"merchantDescription":null},{"id":2,"transactionDate":"2022-10-28","postedDate":"2022-10-28","amount":0.00,"category":{"id":1,"name":"test","note":"test"},"merchant":{"id":1,"name":"test","note":"test"},"merchantDescription":null},{"id":3,"transactionDate":"2022-10-28","postedDate":"2022-10-28","amount":0.00,"category":{"id":1,"name":"test","note":"test"},"merchant":{"id":1,"name":"test","note":"test"},"merchantDescription":null}]} -1 [chunked] false false map[] 0x140001daf00 }
Response from `TransactionRouterApi.ListTransactions`: [{0x140000aa218 0001-01-01 00:00:00 +0000 UTC     }]

我是否做错了导致反序列化失败的事情?或者这是一个客户端错误(似乎值得怀疑,但谁知道)。

我查看了我使用的生成参数和端点可用的模式,它们看起来都是正确的。

执行的脚本: openapi-generator-cli generate -g go -i http://localhost:8080/v3/api-docs


【解决方案1】:

两种选择:

  1. 创建自定义类型并为日期字段实现 Unmarshaler 接口。
  2. 从 API 返回有效的 RFC 3339 日期/时间。

【讨论】: