Skip to content

Switching to oapi-codegen? #7

@lullius

Description

@lullius

I realized some days ago that we have the OpenAPI specification for the API at:
https://gateway.internxt.com/drive/api-json
(see also https://gateway.internxt.com/drive/)

When I tried running it through oapi-codegen I got a lot of errors related to invalid formats like "bigint" and {placeholders} in paths. I gave the information to chatgpt and got back a script that adds the missing parameters to the paths and replaces the invalid formats.

import json
import re
import sys
from copy import deepcopy

VALID_METHODS = {"get", "post", "put", "patch", "delete", "options", "head", "trace"}

def ensure_path_params(spec):
    """Ensure that all {placeholders} in path strings are declared in parameters."""
    for path, path_item in spec.get("paths", {}).items():
        placeholders = re.findall(r"\{([^}]+)\}", path)
        if not placeholders:
            continue

        # Path-level parameters
        path_params = path_item.get("parameters", [])
        declared = {p["name"] for p in path_params if p.get("in") == "path"}

        for ph in placeholders:
            if ph not in declared:
                path_params.append({
                    "name": ph,
                    "in": "path",
                    "required": True,
                    "schema": {"type": "string"}
                })
        if path_params:
            path_item["parameters"] = path_params

        # Operation-level parameters
        for method, operation in path_item.items():
            if method.lower() not in VALID_METHODS:
                continue
            op_params = operation.get("parameters", [])
            declared_op = {p["name"] for p in op_params if p.get("in") == "path"}
            for ph in placeholders:
                if ph not in declared_op:
                    op_params.append({
                        "name": ph,
                        "in": "path",
                        "required": True,
                        "schema": {"type": "string"}
                    })
            if op_params:
                operation["parameters"] = op_params

    return spec

def fix_invalid_formats(schema):
    """Recursively walk the schema and fix invalid formats like bigint."""
    if isinstance(schema, dict):
        if schema.get("format") == "bigint":
            # If it's "integer" or "number", convert to int64
            schema["type"] = "integer"
            schema["format"] = "int64"
        for v in schema.values():
            fix_invalid_formats(v)
    elif isinstance(schema, list):
        for item in schema:
            fix_invalid_formats(item)

def main():
    if len(sys.argv) != 3:
        print("Usage: python fix_openapi.py input.json output.json")
        sys.exit(1)

    infile, outfile = sys.argv[1], sys.argv[2]

    with open(infile) as f:
        spec = json.load(f)

    fixed = ensure_path_params(deepcopy(spec))
    fix_invalid_formats(fixed)

    with open(outfile, "w") as f:
        json.dump(fixed, f, indent=2)

    print(f"✅ Fixed spec written to {outfile}")

if __name__ == "__main__":
    main()

After running the OpenAPI json through this script:

python fix-spec.py openapi.json openapi-fixed.json

oapi-codegen can create a go client like so:

go get -tool github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen@latest
go tool oapi-codegen -generate "client,types,models" openapi-fixed.json > oapi-codegen/client.gen.go

I wish I had known this earlier. I think generating the client like this and adding a custom AuthTransport with a roundtripper that sets relevant headers will be easier to maintain. oapi-codegen creates all the structs and funcs needed and also does all the marshalling and unmarshalling for us. The AuthTransport can look something like this:

type AuthTransport struct {
	token string
	rt     http.RoundTripper
}

func (t *AuthTransport) RoundTrip(req *http.Request) (*http.Response, error) {
	clone := req.Clone(req.Context())
	clone.Header.Set("Authorization", "Bearer " + t.token)
	return t.rt.RoundTrip(clone)
}

And the client can then be created like this:

c, err := openapi.NewClientWithResponses(APIURL, openapi.WithHTTPClient(&http.Client{
	Transport: &AuthTransport{
		token: newToken,
		rt:     http.DefaultTransport,
	},
}),
)

We would still need all the encryption/decryption and upload/download code though. What do you think about using oapen-codegen for this project?

I may take a break from coding for a while, but I'm watching this repo and will try to help with issues if they pop up.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions