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

huangapple go评论93阅读模式
英文:

Generated OpenAPI golang client doesn't seem to handle dates correctly?

问题

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

Kotlin API:

@GetMapping
fun listTransactions() : ResponseEntity<List<Transaction>> {
    val transactions = ArrayList<Transaction>()
    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?
)

生成的Schema:

{
	"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 <nil>}
Response from `TransactionRouterApi.ListTransactions`: [{0x140000aa218 0001-01-01 00:00:00 +0000 UTC <nil> <nil> <nil> <nil> <nil>}]

我是否做错了什么导致反序列化失败?还是这是一个客户端的错误(似乎不太可能,但谁知道呢)。

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

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

英文:

I'm working on an API that provides a list of transactions. I'm writing it in Java/Kotlin Spring, but prefer golang for CLIs, so I'm generating a golang client for it. The API works well in the Swagger UI.

Kotlin API:

@GetMapping
fun listTransactions() : ResponseEntity&lt;List&lt;Transaction&gt;&gt; {
val transactions = ArrayList&lt;Transaction&gt;()
transactionRepository.findAll().mapTo(transactions) { fromEntity(it) }
return ResponseEntity.ok(transactions)
}

Kotlin Object:

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?
)

Generated Schema:

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

Golang Client Object:

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

All of this seems correct enough. However, when I use the OpenAPI client, it seems like deserialization isn't working correctly:

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

Am I doing something incorrectly that results in deserialization failing? Or is this a client bug (seems doubtful, but who knows).

I've looked at the generation arguments I used and the schema available at my endpoint, and they both appear correct.

Script executed: openapi-generator-cli generate -g go -i http://localhost:8080/v3/api-docs

答案1

得分: 1

两个选项:

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

Two options:

  1. Create a custom type and implement the Unmarshaler interface for the date fields.
  2. Return valid RFC 3339 date/times from the API.

huangapple
  • 本文由 发表于 2022年11月1日 03:31:52
  • 转载请务必保留本文链接:https://go.coder-hub.com/74268283.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定