petstore

The petstore.example microservice is the canonical worked output of the OpenAPI-import skill. It is generated, byte-for-byte, by pointing the skill at the public Swagger Petstore v3 OpenAPI document:

https://petstore3.swagger.io/api/v3/openapi.json

If you’re considering using the skill on your own remote API, this example is the concrete answer to “what does the skill actually produce?” — three endpoints chosen to cover both shapes the generator emits (typed function endpoints + raw web-relay endpoints), with every layer of the generated code visible alongside the prompts that produced it.

The full upstream API has 19 operations; only three are imported here. The durable record of the remote API lives in openapispecs.json so additional endpoints can be added incrementally later without re-fetching the document. That incremental shape is itself part of what the skill is designed to demonstrate.

The example is intentionally not wired into main/main.go in the fabric repo so unrelated example builds don’t depend on a live remote API. Add petstore.NewService() to your app explicitly if you want to exercise it against the public Petstore endpoint.

Imported Endpoints

EndpointRouteShape
AddPetPOST /petFunction: JSON Pet body, JSON Pet response
GetPetByIdGET /pet/{petId}Function: int64 path argument, JSON response
UploadFilePOST /pet/{petId}/uploadImageWeb: binary octet-stream relay

Depends On

  • httpegress.core — every outbound HTTPS call to petstore3.swagger.io goes through the egress proxy.

Configuration

Two configs are generated from the OpenAPI document’s server list + security scheme:

petstore.example:
  RemoteBaseURL: https://petstore3.swagger.io/api/v3   # default; override per environment
  BearerToken:   <your-oauth2-token>                   # secret; goes in config.local.yaml

BearerToken is the OAuth2 credential presented to the remote API on every call. It is a secret and belongs in config.local.yaml. RemoteBaseURL defaults to the public Petstore endpoint but is overridable so the same generated code can target a staging deployment of the same API.

Code Walkthrough

Source: exampleservices/petstore/service.go.

The Service struct holds no state — every call is independent:

type Service struct {
    *Intermediate // IMPORTANT: Do not remove
}

OnStartup and OnShutdown are empty.

The generated code has three layers:

  • A typed delegating handler per imported endpoint. Body is one line: build the URL, call the appropriate helper, return the result.
  • Two shared request helpers (makeFunctionRequest, makeWebRequest) that own the actual outbound call: JSON encode/decode for typed endpoints, byte-transparent body relay for web endpoints.
  • Two small auth/URL helpers (remoteURL, authenticate) called by both request helpers.

AddPet

func (svc *Service) AddPet(ctx context.Context, httpRequestBody *petstoreapi.Pet) (httpResponseBody *petstoreapi.Pet, httpStatusCode int, err error) {
    httpStatusCode, err = svc.makeFunctionRequest(ctx, "POST", svc.remoteURL("/pet"), httpRequestBody, &httpResponseBody)
    return httpResponseBody, httpStatusCode, errors.Trace(err)
}

A typed function endpoint. The Go signature mirrors the OpenAPI operation exactly: a *Pet request body in, a *Pet response body and HTTP status code out. The skill recognized application/json on both sides of the operation and emitted typed JSON marshalling automatically. The remote status code is returned to the caller as-is rather than translated — there is no error reshaping, no retry, no caching baked into the generated code. Those are cross-cutting concerns that belong in the caller, not in the API’s representation.

GetPetById

func (svc *Service) GetPetById(ctx context.Context, petId int64) (httpResponseBody *petstoreapi.Pet, httpStatusCode int, err error) {
    u, err := url.Parse(svc.remoteURL("/pet/" + url.PathEscape(fmt.Sprint(petId))))
    if err != nil {
        return nil, 0, errors.Trace(err)
    }
    httpStatusCode, err = svc.makeFunctionRequest(ctx, "GET", u.String(), nil, &httpResponseBody)
    return httpResponseBody, httpStatusCode, errors.Trace(err)
}

Demonstrates a path argument ({petId} in the OpenAPI route). The skill translates path placeholders into typed Go arguments — petId int64 here, derived from the OpenAPI schema’s format: int64. The handler URL-escapes the value before splicing it into the remote URL so a hostile input cannot inject path segments.

UploadFile

func (svc *Service) UploadFile(w http.ResponseWriter, r *http.Request) (err error) {
    u := svc.remoteURL("/pet/" + url.PathEscape(r.PathValue("petId")) + "/uploadImage")
    return svc.makeWebRequest(w, r, "POST", u)
}

Demonstrates a web endpoint (non-JSON body, raw relay). The operation accepts an application/octet-stream body — an image upload — so the skill emitted a web handler that takes the raw http.ResponseWriter and *http.Request and delegates to makeWebRequest, which copies the request body and headers to the remote and streams the response back byte-for-byte. There is no parsing of the request body in the middle; the microservice is a transparent pipe.

Shared Helpers

func (svc *Service) makeFunctionRequest(ctx context.Context, method, rawURL string, in, out any) (status int, err error) {
    var body io.Reader
    if in != nil {
        b, err := json.Marshal(in)
        if err != nil {
            return 0, errors.Trace(err)
        }
        body = bytes.NewReader(b)
    }
    req, err := http.NewRequest(method, rawURL, body)
    if err != nil {
        return 0, errors.Trace(err)
    }
    if in != nil {
        req.Header.Set("Content-Type", "application/json")
    }
    svc.authenticate(req)
    resp, err := httpegressapi.NewClient(svc).Do(ctx, req)
    if err != nil {
        return 0, errors.Trace(err)
    }
    defer resp.Body.Close()
    if out != nil {
        err = json.NewDecoder(resp.Body).Decode(out)
        if err != nil {
            return resp.StatusCode, errors.Trace(err)
        }
    }
    return resp.StatusCode, nil
}

Three things to notice:

  • Outbound HTTP goes through httpegressapi, never through net/http directly. The skill wires this in automatically (adding httpegress.NewService() to the app if it isn’t already there). That’s what gives the delegating microservice tracing, time-budget honoring, and a test mock for free.
  • svc.authenticate(req) injects the bearer token on every outbound request. The generated code reads the credential from config via the typed BearerToken() accessor and never exposes it to upstream callers.
  • The remote status code is returned, not translated. A 404 Not Found from the upstream Petstore comes back to the caller as httpStatusCode = 404, not converted into a Go error. This preserves the byte-transparent delegation contract.

makeWebRequest is the same shape for raw web bodies, using httpx.Copy(w, resp) to transfer the response body without copying its bytes.

See Also

  • OpenAPI-Delegating Microservices — the skill that generated this microservice. Walks through the prompt, the openapispecs.json durable record, incremental endpoint import, and the closed-by-default requiredClaims posture.
  • HTTP Egress core service — the proxy that owns the actual outbound call.
  • Browser example — a hand-written egress consumer, useful contrast against the generated style here.