oapi-codegen icon indicating copy to clipboard operation
oapi-codegen copied to clipboard

Generating different structs for different error codes

Open VladislavYakonyuk opened this issue 3 months ago • 5 comments

We use server code generation with the strict-server: true parameter. During generation, we obtain the following structures for the response

GetHealthcheck200JSONResponse{
   Timestamp: time.Now().UnixMilli(),
}

GetHealthcheck500JSONResponse{
   Timestamp: time.Now().UnixMilli(),
}

And this happens with absolutely any response, a separate structure is created for each error code specified in the API contract. We find this inconvenient, but couldn't figure out how to make a generic structure or our own wrapper. Could you suggest how to be in this situation?

VladislavYakonyuk avatar Sep 11 '25 10:09 VladislavYakonyuk

Having the same gripe with the generation. I have the following example

  /v1/stuff/{stuffName}:
    get:
      description: Get details about a specific stuff
      security:
        - Bearer: []
      parameters:
        - name: stuffName
          in: path
          required: true
          schema:
            type: string
            minLength: 1
          description: Name of the stuff to retrieve details for
      responses:
        "200":
          description: Successfully retrieved stuff details
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StuffDetails"
        "400":
          $ref: "#/components/responses/BadRequestError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "404":
          $ref: "#/components/responses/NotFoundError"
        "500":
          $ref: "#/components/responses/InternalServerError"

I get the following response object:

type GetV1StuffStuffNameResponseObject interface {
	VisitGetV1StuffStuffNameNameResponse(w http.ResponseWriter) error
}

But the InternalServerErrorJSONResponse structure generated for the #/components/responses/InternalServerError component, doesn't satisfy interface GetV1StuffStuffNameResponseObject. If the struct InternalServerErrorJSONResponse would get the necessary methods for satisfying all the interfaces for each endpoint response, we could return in our method directly our generic responses (example: InternalServerErrorJSONResponse)

Does it make sense? If they would extend the generation to add methods to satisfy all the custom response object interfaces, it would greatly simplify error handling.

nustiueudinastea avatar Sep 12 '25 15:09 nustiueudinastea

I resolved this by creating a custom template for strict-interface.gotmpl, that generates:

type LoginUser401JSONResponse UnauthorizedErrorJSONResponse

instead of

type LoginUser401JSONResponse struct{ UnauthorizedErrorJSONResponse }

After this PR https://github.com/oapi-codegen/oapi-codegen/pull/2110 gets merged, we will have support for converting strings to numbers, which helps conditionally handle the generation of response structs based on status codes in the temple.

Something like this works for me:

{{if gt ($statusCode | atoi) 399 -}}
    type {{$receiverTypeName}} {{$ref}}{{.NameTagOrContentType}}Response
{{else -}}
    type {{$receiverTypeName}} struct{ {{$ref}}{{.NameTagOrContentType}}Response }
{{end}}

Or u can completely remove the error structs (ur choice), but since I had a common utility function to generate error response interfaces, the above solution works for me.

EDIT:

The above dint work for me. I am still using a custom template, and this time I've completely removed all error structs and interface impl for those structs and instead returning nil, err in my handlers. I have a custom error handler that handles these errors and convert it to the correct error response json.

phoenisx avatar Oct 22 '25 18:10 phoenisx

The way I found around the duplicated error everywhere was to errors.as against an expanded error interface

type RespError interface {
	error

	WriteResponse(w http.ResponseWriter) error
	Status() int
}

If this is satisfied, the type would generate a custom writer for it.

I also added a helper to convert any data I wanted into an error that implemented RespError, WrapError(err error, code int, data any) error which would set both the code and object that would get output (the object being encoded into json, tho renaming to WrapErrorJSON and adding a variant WrapErrorXML would be easy to do).

Because it was an interface, that also made it easier to use across multiple generated libraries too. Didn't have to deal with complexity putting my "common error types" in one package, and using them in another anymore.

deefdragon avatar Oct 23 '25 04:10 deefdragon

@phoenisx mind sharing your custom template ?

ianmuhia avatar Nov 15 '25 11:11 ianmuhia

You can find it here: https://gist.github.com/phoenisx/6a2e9aefc2e5f0fa3822688b37db4eef

Though I must say I have a different approach than how oapi handles errors here. Instead of returning an Error object as a response, I throw a custom error type, which I use to parse before returning a 4xx/5xx error. This way I can keep using error interface and also have the benefit of using strict handlers.

I think the overall architecture could still be improved, but I've left it as is for now, as I was wasting a lot of time architecting, then actually working on the APIs.

phoenisx avatar Nov 16 '25 07:11 phoenisx