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

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

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

问题

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

Kotlin API:

  1. @GetMapping
  2. fun listTransactions() : ResponseEntity<List<Transaction>> {
  3. val transactions = ArrayList<Transaction>()
  4. transactionRepository.findAll().mapTo(transactions) { fromEntity(it) }
  5. return ResponseEntity.ok(transactions)
  6. }

Kotlin对象:

  1. data class Transaction(
  2. val id: Long,
  3. val transactionDate: Date, // Java SQL date
  4. val postedDate: Date?, // Java SQL date
  5. val amount: BigDecimal,
  6. val category: Category,
  7. val merchant: Merchant,
  8. val merchantDescription: String?
  9. )

生成的Schema:

  1. {
  2. "openapi": "3.0.1",
  3. "info": {
  4. "title": "BFI Swagger Title",
  5. "description": "BFI description",
  6. "version": "0.1"
  7. },
  8. "servers": [{
  9. "url": "http://localhost:8080",
  10. "description": "Generated server url"
  11. }],
  12. "paths": {
  13. "/transaction": {
  14. "get": {
  15. "tags": ["transaction-router"],
  16. "operationId": "listTransactions",
  17. "responses": {
  18. "200": {
  19. "description": "OK",
  20. "content": {
  21. "*/*": {
  22. "schema": {
  23. "type": "array",
  24. "items": {
  25. "$ref": "#/components/schemas/Transaction"
  26. }
  27. }
  28. }
  29. }
  30. }
  31. }
  32. },
  33. "post": {
  34. "tags": ["transaction-router"],
  35. "operationId": "createTransaction",
  36. "requestBody": {
  37. "content": {
  38. "application/json": {
  39. "schema": {
  40. "$ref": "#/components/schemas/CreateTransactionRequest"
  41. }
  42. }
  43. },
  44. "required": true
  45. },
  46. "responses": {
  47. "200": {
  48. "description": "OK",
  49. "content": {
  50. "*/*": {
  51. "schema": {
  52. "$ref": "#/components/schemas/Transaction"
  53. }
  54. }
  55. }
  56. }
  57. }
  58. }
  59. },
  60. "/hello": {
  61. "get": {
  62. "tags": ["category-router"],
  63. "summary": "Hello there!",
  64. "operationId": "hello",
  65. "responses": {
  66. "200": {
  67. "description": "OK",
  68. "content": {
  69. "application/json": {
  70. "schema": {
  71. "$ref": "#/components/schemas/Category"
  72. }
  73. }
  74. }
  75. }
  76. }
  77. }
  78. }
  79. },
  80. "components": {
  81. "schemas": {
  82. "CreateTransactionRequest": {
  83. "type": "object",
  84. "properties": {
  85. "transactionDate": {
  86. "type": "string",
  87. "format": "date-time"
  88. },
  89. "postedDate": {
  90. "type": "string",
  91. "format": "date-time"
  92. },
  93. "amount": {
  94. "type": "number"
  95. },
  96. "categoryId": {
  97. "type": "integer",
  98. "format": "int64"
  99. },
  100. "merchantId": {
  101. "type": "integer",
  102. "format": "int64"
  103. },
  104. "merchantDescription": {
  105. "type": "string"
  106. }
  107. }
  108. },
  109. "Category": {
  110. "type": "object",
  111. "properties": {
  112. "id": {
  113. "type": "integer",
  114. "format": "int64"
  115. },
  116. "name": {
  117. "type": "string"
  118. },
  119. "note": {
  120. "type": "string"
  121. }
  122. }
  123. },
  124. "Merchant": {
  125. "type": "object",
  126. "properties": {
  127. "id": {
  128. "type": "integer",
  129. "format": "int64"
  130. },
  131. "name": {
  132. "type": "string"
  133. },
  134. "note": {
  135. "type": "string"
  136. }
  137. }
  138. },
  139. "Transaction": {
  140. "type": "object",
  141. "properties": {
  142. "id": {
  143. "type": "integer",
  144. "format": "int64"
  145. },
  146. "transactionDate": {
  147. "type": "string",
  148. "format": "date-time"
  149. },
  150. "postedDate": {
  151. "type": "string",
  152. "format": "date-time"
  153. },
  154. "amount": {
  155. "type": "number"
  156. },
  157. "category": {
  158. "$ref": "#/components/schemas/Category"
  159. },
  160. "merchant": {
  161. "$ref": "#/components/schemas/Merchant"
  162. },
  163. "merchantDescription": {
  164. "type": "string"
  165. }
  166. }
  167. }
  168. }
  169. }
  170. }

Golang客户端对象:

  1. type Transaction struct {
  2. Id *int64 `json:"id,omitempty"`
  3. TransactionDate *time.Time `json:"transactionDate,omitempty"`
  4. PostedDate *time.Time `json:"postedDate,omitempty"`
  5. Amount *float32 `json:"amount,omitempty"`
  6. Category *Category `json:"category,omitempty"`
  7. Merchant *Merchant `json:"merchant,omitempty"`
  8. MerchantDescription *string `json:"merchantDescription,omitempty"`
  9. }

所有这些看起来都是正确的。然而,当我使用OpenAPI客户端时,似乎反序列化不正确:

  1. Error when calling `TransactionRouterApi.ListTransactions`: parsing time "\"2022-10-28\"" as "\"2006-01-02T15:04:05Z07:00\"": cannot parse "\"\"" as "T"
  2. 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>}
  3. 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:

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

Kotlin Object:

  1. data class Transaction(
  2. val id: Long,
  3. val transactionDate: Date, // Java SQL date
  4. val postedDate: Date?, // Java SQL date
  5. val amount: BigDecimal,
  6. val category: Category,
  7. val merchant: Merchant,
  8. val merchantDescription: String?
  9. )

Generated Schema:

  1. {
  2. &quot;openapi&quot;: &quot;3.0.1&quot;,
  3. &quot;info&quot;: {
  4. &quot;title&quot;: &quot;BFI Swagger Title&quot;,
  5. &quot;description&quot;: &quot;BFI description&quot;,
  6. &quot;version&quot;: &quot;0.1&quot;
  7. },
  8. &quot;servers&quot;: [{
  9. &quot;url&quot;: &quot;http://localhost:8080&quot;,
  10. &quot;description&quot;: &quot;Generated server url&quot;
  11. }],
  12. &quot;paths&quot;: {
  13. &quot;/transaction&quot;: {
  14. &quot;get&quot;: {
  15. &quot;tags&quot;: [&quot;transaction-router&quot;],
  16. &quot;operationId&quot;: &quot;listTransactions&quot;,
  17. &quot;responses&quot;: {
  18. &quot;200&quot;: {
  19. &quot;description&quot;: &quot;OK&quot;,
  20. &quot;content&quot;: {
  21. &quot;*/*&quot;: {
  22. &quot;schema&quot;: {
  23. &quot;type&quot;: &quot;array&quot;,
  24. &quot;items&quot;: {
  25. &quot;$ref&quot;: &quot;#/components/schemas/Transaction&quot;
  26. }
  27. }
  28. }
  29. }
  30. }
  31. }
  32. },
  33. &quot;post&quot;: {
  34. &quot;tags&quot;: [&quot;transaction-router&quot;],
  35. &quot;operationId&quot;: &quot;createTransaction&quot;,
  36. &quot;requestBody&quot;: {
  37. &quot;content&quot;: {
  38. &quot;application/json&quot;: {
  39. &quot;schema&quot;: {
  40. &quot;$ref&quot;: &quot;#/components/schemas/CreateTransactionRequest&quot;
  41. }
  42. }
  43. },
  44. &quot;required&quot;: true
  45. },
  46. &quot;responses&quot;: {
  47. &quot;200&quot;: {
  48. &quot;description&quot;: &quot;OK&quot;,
  49. &quot;content&quot;: {
  50. &quot;*/*&quot;: {
  51. &quot;schema&quot;: {
  52. &quot;$ref&quot;: &quot;#/components/schemas/Transaction&quot;
  53. }
  54. }
  55. }
  56. }
  57. }
  58. }
  59. },
  60. &quot;/hello&quot;: {
  61. &quot;get&quot;: {
  62. &quot;tags&quot;: [&quot;category-router&quot;],
  63. &quot;summary&quot;: &quot;Hello there!&quot;,
  64. &quot;operationId&quot;: &quot;hello&quot;,
  65. &quot;responses&quot;: {
  66. &quot;200&quot;: {
  67. &quot;description&quot;: &quot;OK&quot;,
  68. &quot;content&quot;: {
  69. &quot;application/json&quot;: {
  70. &quot;schema&quot;: {
  71. &quot;$ref&quot;: &quot;#/components/schemas/Category&quot;
  72. }
  73. }
  74. }
  75. }
  76. }
  77. }
  78. }
  79. },
  80. &quot;components&quot;: {
  81. &quot;schemas&quot;: {
  82. &quot;CreateTransactionRequest&quot;: {
  83. &quot;type&quot;: &quot;object&quot;,
  84. &quot;properties&quot;: {
  85. &quot;transactionDate&quot;: {
  86. &quot;type&quot;: &quot;string&quot;,
  87. &quot;format&quot;: &quot;date-time&quot;
  88. },
  89. &quot;postedDate&quot;: {
  90. &quot;type&quot;: &quot;string&quot;,
  91. &quot;format&quot;: &quot;date-time&quot;
  92. },
  93. &quot;amount&quot;: {
  94. &quot;type&quot;: &quot;number&quot;
  95. },
  96. &quot;categoryId&quot;: {
  97. &quot;type&quot;: &quot;integer&quot;,
  98. &quot;format&quot;: &quot;int64&quot;
  99. },
  100. &quot;merchantId&quot;: {
  101. &quot;type&quot;: &quot;integer&quot;,
  102. &quot;format&quot;: &quot;int64&quot;
  103. },
  104. &quot;merchantDescription&quot;: {
  105. &quot;type&quot;: &quot;string&quot;
  106. }
  107. }
  108. },
  109. &quot;Category&quot;: {
  110. &quot;type&quot;: &quot;object&quot;,
  111. &quot;properties&quot;: {
  112. &quot;id&quot;: {
  113. &quot;type&quot;: &quot;integer&quot;,
  114. &quot;format&quot;: &quot;int64&quot;
  115. },
  116. &quot;name&quot;: {
  117. &quot;type&quot;: &quot;string&quot;
  118. },
  119. &quot;note&quot;: {
  120. &quot;type&quot;: &quot;string&quot;
  121. }
  122. }
  123. },
  124. &quot;Merchant&quot;: {
  125. &quot;type&quot;: &quot;object&quot;,
  126. &quot;properties&quot;: {
  127. &quot;id&quot;: {
  128. &quot;type&quot;: &quot;integer&quot;,
  129. &quot;format&quot;: &quot;int64&quot;
  130. },
  131. &quot;name&quot;: {
  132. &quot;type&quot;: &quot;string&quot;
  133. },
  134. &quot;note&quot;: {
  135. &quot;type&quot;: &quot;string&quot;
  136. }
  137. }
  138. },
  139. &quot;Transaction&quot;: {
  140. &quot;type&quot;: &quot;object&quot;,
  141. &quot;properties&quot;: {
  142. &quot;id&quot;: {
  143. &quot;type&quot;: &quot;integer&quot;,
  144. &quot;format&quot;: &quot;int64&quot;
  145. },
  146. &quot;transactionDate&quot;: {
  147. &quot;type&quot;: &quot;string&quot;,
  148. &quot;format&quot;: &quot;date-time&quot;
  149. },
  150. &quot;postedDate&quot;: {
  151. &quot;type&quot;: &quot;string&quot;,
  152. &quot;format&quot;: &quot;date-time&quot;
  153. },
  154. &quot;amount&quot;: {
  155. &quot;type&quot;: &quot;number&quot;
  156. },
  157. &quot;category&quot;: {
  158. &quot;$ref&quot;: &quot;#/components/schemas/Category&quot;
  159. },
  160. &quot;merchant&quot;: {
  161. &quot;$ref&quot;: &quot;#/components/schemas/Merchant&quot;
  162. },
  163. &quot;merchantDescription&quot;: {
  164. &quot;type&quot;: &quot;string&quot;
  165. }
  166. }
  167. }
  168. }
  169. }
  170. }

Golang Client Object:

  1. type Transaction struct {
  2. Id *int64 `json:&quot;id,omitempty&quot;`
  3. TransactionDate *time.Time `json:&quot;transactionDate,omitempty&quot;`
  4. PostedDate *time.Time `json:&quot;postedDate,omitempty&quot;`
  5. Amount *float32 `json:&quot;amount,omitempty&quot;`
  6. Category *Category `json:&quot;category,omitempty&quot;`
  7. Merchant *Merchant `json:&quot;merchant,omitempty&quot;`
  8. MerchantDescription *string `json:&quot;merchantDescription,omitempty&quot;`
  9. }

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

  1. 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;
  2. 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;}
  3. 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:

确定