oapi-codegen
oapi-codegen copied to clipboard
oneOf response struct
responses:
'200':
description: Order result
content:
application/json:
schema:
oneOf:
- $ref: '#/components/schemas/orderResponseAck'
- $ref: '#/components/schemas/orderResponseResult'
- $ref: '#/components/schemas/orderResponseFull'
genrating into
type PostFapiV1Order200JSONResponse struct {
union json.RawMessage
}
How should I fill and return this struct in strict server handler? Field union unexported.
From a project I've done this in before, I get the following generated:
// AsJSONPatchRequestAddReplaceTest returns the union data inside the PatchRequest_Item as a JSONPatchRequestAddReplaceTest
func (t PatchRequest_Item) AsJSONPatchRequestAddReplaceTest() (JSONPatchRequestAddReplaceTest, error) {
var body JSONPatchRequestAddReplaceTest
err := json.Unmarshal(t.union, &body)
return body, err
}
// FromJSONPatchRequestAddReplaceTest overwrites any union data inside the PatchRequest_Item as the provided JSONPatchRequestAddReplaceTest
func (t *PatchRequest_Item) FromJSONPatchRequestAddReplaceTest(v JSONPatchRequestAddReplaceTest) error {
b, err := json.Marshal(v)
t.union = b
return err
}
// MergeJSONPatchRequestAddReplaceTest performs a merge with any union data inside the PatchRequest_Item, using the provided JSONPatchRequestAddReplaceTest
func (t *PatchRequest_Item) MergeJSONPatchRequestAddReplaceTest(v JSONPatchRequestAddReplaceTest) error {
b, err := json.Marshal(v)
if err != nil {
return err
}
merged, err := runtime.JsonMerge(b, t.union)
t.union = merged
return err
}
You should be able to use these to set the type based on your orderResponseAck
, etc.
It's unfortunately not a super great solution (I wrote it 😅) but as we don't have union types in Go, this was the option we went for
From a project I've done this in before, I get the following generated:
// AsJSONPatchRequestAddReplaceTest returns the union data inside the PatchRequest_Item as a JSONPatchRequestAddReplaceTest func (t PatchRequest_Item) AsJSONPatchRequestAddReplaceTest() (JSONPatchRequestAddReplaceTest, error) { var body JSONPatchRequestAddReplaceTest err := json.Unmarshal(t.union, &body) return body, err } // FromJSONPatchRequestAddReplaceTest overwrites any union data inside the PatchRequest_Item as the provided JSONPatchRequestAddReplaceTest func (t *PatchRequest_Item) FromJSONPatchRequestAddReplaceTest(v JSONPatchRequestAddReplaceTest) error { b, err := json.Marshal(v) t.union = b return err } // MergeJSONPatchRequestAddReplaceTest performs a merge with any union data inside the PatchRequest_Item, using the provided JSONPatchRequestAddReplaceTest func (t *PatchRequest_Item) MergeJSONPatchRequestAddReplaceTest(v JSONPatchRequestAddReplaceTest) error { b, err := json.Marshal(v) if err != nil { return err } merged, err := runtime.JsonMerge(b, t.union) t.union = merged return err }
You should be able to use these to set the type based on your
orderResponseAck
, etc.It's unfortunately not a super great solution (I wrote it 😅) but as we don't have union types in Go, this was the option we went for
i cant access union field because I place generated code in separete package. So I need to write additional functions in this package that will fill union field with data?
No, you're not expected to manually interact with the union
field, you should use the helper methods that are generated - hopefully PostFapiV1Order200JSONResponse
should have other methods generated for it.
If not (and you're running latest oapi-codegen
version) then it may be you need to move the type to /components/schemas
to get the generation working, but that'll provide you helper methods like As...
, From...
and Merge...
to make it possible
Merge
openapi: 3.0.3
info:
description: test server
version: 1.0.0
paths:
/fapi/v1/order:
post:
summary: New Order
responses:
'200':
description: Order result
content:
application/json:
schema:
oneOf:
- $ref: '#/components/schemas/orderResponseAck'
- $ref: '#/components/schemas/orderResponseResult'
- $ref: '#/components/schemas/orderResponseFull'
components:
schemas:
orderResponseAck:
type: object
properties:
symbol:
type: string
orderResponseResult:
type: object
properties:
orderId:
type: integer
format: int64
example: 28
orderResponseFull:
type: object
properties:
orderListId:
type: integer
format: int64
example: -1
//go:generate oapi-codegen --package api -o ./api/server.gen.go --generate chi-server,strict-server,spec,types ./api.yaml
here is test spec and generate command. I do not have any methods in PostFapiV1Order200JSONResponse
Merge
openapi: 3.0.3 info: description: test server version: 1.0.0 paths: /fapi/v1/order: post: summary: New Order responses: '200': description: Order result content: application/json: schema: oneOf: - $ref: '#/components/schemas/orderResponseAck' - $ref: '#/components/schemas/orderResponseResult' - $ref: '#/components/schemas/orderResponseFull' components: schemas: orderResponseAck: type: object properties: symbol: type: string orderResponseResult: type: object properties: orderId: type: integer format: int64 example: 28 orderResponseFull: type: object properties: orderListId: type: integer format: int64 example: -1
//go:generate oapi-codegen --package api -o ./api/server.gen.go --generate chi-server,strict-server,spec,types ./api.yaml
here is test spec and generate command. I do not have any methods in PostFapiV1Order200JSONResponse
Hi! Did you find a solution?
Merge
openapi: 3.0.3 info: description: test server version: 1.0.0 paths: /fapi/v1/order: post: summary: New Order responses: '200': description: Order result content: application/json: schema: oneOf: - $ref: '#/components/schemas/orderResponseAck' - $ref: '#/components/schemas/orderResponseResult' - $ref: '#/components/schemas/orderResponseFull' components: schemas: orderResponseAck: type: object properties: symbol: type: string orderResponseResult: type: object properties: orderId: type: integer format: int64 example: 28 orderResponseFull: type: object properties: orderListId: type: integer format: int64 example: -1
//go:generate oapi-codegen --package api -o ./api/server.gen.go --generate chi-server,strict-server,spec,types ./api.yaml
here is test spec and generate command. I do not have any methods in PostFapiV1Order200JSONResponse
Hi! Did you find a solution?
No :(
Same here :(
Ive tried to make schema object as such
openapi: 3.0.3
info:
description: test server
version: 1.0.0
paths:
/tickets/{ticket_id}/accept:
post:
operationId: acceptTicket
security:
- RoleAuth:
- admin
summary: Accept a requested or reapplied Account Ticket
parameters:
- $ref: '#/components/parameters/ticketIdParam'
tags:
- Account Tickets
responses:
'200':
description: Returns Account if the ticket was Reapply otherwise the AccountTicket
content:
application/json:
schema:
$ref: '#/components/schemas/AcceptTicketResponse'
components:
schemas:
AcceptTicketResponse:
oneOf:
- $ref: '#/components/schemas/Account'
- $ref: '#/components/schemas/AccountTicket'
Account:
properties:
id:
type: integer
AccountTicket:
properties:
ticket_id:
type: integer
It generates ok code, but the decoder for VisitAcceptTicket does not decode the union but the type itself, returning an empty object.
Generated code:
type AcceptTicket200JSONResponse AcceptTicketResponse
// AcceptTicketResponse defines model for AcceptTicketResponse.
type AcceptTicketResponse struct {
union json.RawMessage
}
// AcceptTicketResponse0 defines model for .
type AcceptTicketResponse0 struct {
Name string `json:"name"`
}
// AcceptTicketResponse1 defines model for .
type AcceptTicketResponse1 struct {
Name2 string `json:"name2"`
}
// AsAcceptTicketResponse0 returns the union data inside the AcceptTicketResponse as a AcceptTicketResponse0
func (t AcceptTicketResponse) AsAcceptTicketResponse0() (AcceptTicketResponse0, error) {
var body AcceptTicketResponse0
err := json.Unmarshal(t.union, &body)
return body, err
}
// FromAcceptTicketResponse0 overwrites any union data inside the AcceptTicketResponse as the provided AcceptTicketResponse0
func (t *AcceptTicketResponse) FromAcceptTicketResponse0(v AcceptTicketResponse0) error {
b, err := json.Marshal(v)
t.union = b
return err
}
// MergeAcceptTicketResponse0 performs a merge with any union data inside the AcceptTicketResponse, using the provided AcceptTicketResponse0
func (t *AcceptTicketResponse) MergeAcceptTicketResponse0(v AcceptTicketResponse0) error {
b, err := json.Marshal(v)
if err != nil {
return err
}
merged, err := runtime.JsonMerge(t.union, b)
t.union = merged
return err
}
// AsAcceptTicketResponse1 returns the union data inside the AcceptTicketResponse as a AcceptTicketResponse1
func (t AcceptTicketResponse) AsAcceptTicketResponse1() (AcceptTicketResponse1, error) {
var body AcceptTicketResponse1
err := json.Unmarshal(t.union, &body)
return body, err
}
// FromAcceptTicketResponse1 overwrites any union data inside the AcceptTicketResponse as the provided AcceptTicketResponse1
func (t *AcceptTicketResponse) FromAcceptTicketResponse1(v AcceptTicketResponse1) error {
b, err := json.Marshal(v)
t.union = b
return err
}
// MergeAcceptTicketResponse1 performs a merge with any union data inside the AcceptTicketResponse, using the provided AcceptTicketResponse1
func (t *AcceptTicketResponse) MergeAcceptTicketResponse1(v AcceptTicketResponse1) error {
b, err := json.Marshal(v)
if err != nil {
return err
}
merged, err := runtime.JsonMerge(t.union, b)
t.union = merged
return err
}
and:
func (response AcceptTicket200JSONResponse) VisitAcceptTicketResponse(w http.ResponseWriter) error {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(200)
return json.NewEncoder(w).Encode(response)
}
but only works if:
func (response AcceptTicket200JSONResponse) VisitAcceptTicketResponse(w http.ResponseWriter) error {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(200)
return json.NewEncoder(w).Encode(response.union)
}
It seems to be a problem when you have a content of response of type schema oneOf
:
Foo:
schema:
oneOf:
- ref$: 'obj1'
- ref$: 'obj2'
Generates responses as:
type Foo200JSONResponse FooResponse
type FooResponse {
union json.RawMessage
}
which decodes as {}
when:
func (response Foo200JSONResponse) VisitAcceptTicketResponse(w http.ResponseWriter) error {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(200)
return json.NewEncoder(w).Encode(response)
}
is called.
It would work if we would encode response.union
and not response
itself, as the encoding the response
returns {}
and not the content of the union.
Proposal that if the response has oneOf
schema, then encode the response.union
and not response
as the 200 object is a type and not a struct.
func (response Foo200JSONResponse) VisitAcceptTicketResponse(w http.ResponseWriter) error {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(200)
return json.NewEncoder(w).Encode(response.union)
}
The same situation happens when you are using oneOf in request too
This can be solved in the same way as proto, interfaces and type switches.
Proto makes each of the oneof
structs implement a noop interface, then provides getters and setters. Upon decoding you'd require that people provide a discriminator and that would allow you to generate each struct with a type switch.
I modified template strict-interface.tmpl
to insert union helpers:
From this line https://github.com/deepmap/oapi-codegen/blob/master/pkg/codegen/templates/strict/strict-interface.tmpl#L52, changes to:
{{else if and (not $hasHeaders) ($fixedStatusCode) (.IsSupported) -}}
type {{$receiverTypeName}} {{if eq .NameTag "Multipart"}}func(writer *multipart.Writer)error{{else if .IsSupported}}{{if and .Schema.IsRef (not .Schema.IsExternalRef)}}={{end}} {{.Schema.TypeDecl}}{{else}}io.Reader{{end}}
{{/* HACK: generate union helpers for response types */ -}}
{{$discriminator := .Schema.Discriminator}}
{{$properties := .Schema.Properties -}}
{{range .Schema.UnionElements}}
{{$element := . -}}
// As{{ .Method }} returns the union data inside the {{$receiverTypeName}} as a {{.}}
func (t {{$receiverTypeName}}) As{{ .Method }}() ({{.}}, error) {
var body {{.}}
err := json.Unmarshal(t.union, &body)
return body, err
}
// From{{ .Method }} overwrites any union data inside the {{$receiverTypeName}} as the provided {{.}}
func (t *{{$receiverTypeName}}) From{{ .Method }} (v {{.}}) error {
{{if $discriminator -}}
{{range $value, $type := $discriminator.Mapping -}}
{{if eq $type $element -}}
{{$hasProperty := false -}}
{{range $properties -}}
{{if eq .GoFieldName $discriminator.PropertyName -}}
t.{{$discriminator.PropertyName}} = "{{$value}}"
{{$hasProperty = true -}}
{{end -}}
{{end -}}
{{if not $hasProperty}}v.{{$discriminator.PropertyName}} = "{{$value}}"{{end}}
{{end -}}
{{end -}}
{{end -}}
b, err := json.Marshal(v)
t.union = b
return err
}
// Merge{{ .Method }} performs a merge with any union data inside the {{$receiverTypeName}}, using the provided {{.}}
func (t *{{$receiverTypeName}}) Merge{{ .Method }} (v {{.}}) error {
{{if $discriminator -}}
{{range $value, $type := $discriminator.Mapping -}}
{{if eq $type $element -}}
{{$hasProperty := false -}}
{{range $properties -}}
{{if eq .GoFieldName $discriminator.PropertyName -}}
t.{{$discriminator.PropertyName}} = "{{$value}}"
{{$hasProperty = true -}}
{{end -}}
{{end -}}
{{if not $hasProperty}}v.{{$discriminator.PropertyName}} = "{{$value}}"{{end}}
{{end -}}
{{end -}}
{{end -}}
b, err := json.Marshal(v)
if err != nil {
return err
}
merged, err := runtime.JSONMerge(t.union, b)
t.union = merged
return err
}
{{end}}
Or in other way, you can describe the schema like this:
responses:
'200':
description: Order result
content:
application/json:
schema:
oneOf:
- $ref: '#/components/schemas/orderResponse'
components:
securitySchemes: {}
schemas:
orderResponse:
oneOf:
- $ref: '#/components/schemas/orderResponseAck'
- $ref: '#/components/schemas/orderResponseResult'
- $ref: '#/components/schemas/orderResponseFull'
This will generate response object with union, and OrderResponse
with union and helpers.
Then just cast OrderResponse to response object before returning from handler.