Skip to content

Public and Internal Specs

MeGaNeKo edited this page Mar 20, 2026 · 1 revision

Public and Internal Specs

Neoma can serve two OpenAPI specifications from the same API. The public spec filters out items marked hidden, while the internal spec includes everything. This lets you expose debug endpoints or internal fields only to internal consumers.

Marking Items as Hidden

Fields and Parameters

Add hidden:"true" to any struct field. The field still works at runtime but is excluded from the public spec.

type ListInput struct {
    Page  int  `query:"page" default:"1" doc:"Page number"`
    Debug bool `query:"debug" hidden:"true" doc:"Enable debug output"`
}

type Item struct {
    ID         int    `json:"id" doc:"Item ID"`
    InternalID string `json:"internal_id" hidden:"true" doc:"Internal tracking ID"`
}

Operations

Set Hidden: true on the operation. The endpoint is still routed and callable, but does not appear in the public spec.

neoma.Register(api, core.Operation{
    OperationID: "debug-info",
    Method:      http.MethodGet,
    Path:        "/debug",
    Hidden:      true,
}, handler)

HiddenParameters

The HiddenParameters field on core.Operation holds parameters that are excluded from the public spec but injected into the internal spec. The framework populates this automatically from hidden:"true" input fields.

Enabling the Internal Spec

config := neoma.DefaultConfig("My API", "1.0.0")

config.InternalSpec = core.InternalSpecConfig{
    Enabled:    true,
    Path:       "/internal/openapi",
    DocsPath:   "/internal/docs",    // Defaults to "/internal/docs" if empty
    Middlewares: core.Middlewares{},  // Optional: protect with auth middleware
}

InternalSpecConfig fields:

Field Type Description
Enabled bool Activate internal spec serving
Path string Base path for internal spec (.json and .yaml suffixes are added)
DocsPath string Path for the internal docs UI (defaults to /internal/docs)
Middlewares core.Middlewares Middleware applied to all internal spec and docs endpoints

Served Endpoints

Public (always active):

Endpoint Description
GET /openapi.json Public OpenAPI JSON
GET /openapi.yaml Public OpenAPI YAML
GET /public/docs Public docs UI

Internal (when enabled):

Endpoint Description
GET /internal/openapi.json Internal OpenAPI JSON (includes hidden items)
GET /internal/openapi.yaml Internal OpenAPI YAML (includes hidden items)
GET /internal/docs Internal docs UI

Complete Example

package main

import (
    "context"
    "fmt"
    "log"
    "net/http"

    "github.com/MeGaNeKoS/neoma/adapters/neomachi/v5"
    "github.com/MeGaNeKoS/neoma/core"
    "github.com/MeGaNeKoS/neoma/neoma"
    "github.com/go-chi/chi/v5"
)

type ListInput struct {
    Page  int  `query:"page" default:"1"`
    Debug bool `query:"debug" hidden:"true" doc:"Internal debug flag"`
}

type ListOutput struct {
    Body struct {
        Items     []string `json:"items"`
        DebugInfo string   `json:"debug_info" hidden:"true"`
    }
}

func main() {
    router := chi.NewMux()
    adapter := neomachi.NewAdapter(router)
    config := neoma.DefaultConfig("Dual Spec API", "1.0.0")

    config.InternalSpec = core.InternalSpecConfig{
        Enabled: true,
        Path:    "/internal/openapi",
    }

    api := neoma.NewAPI(config, adapter)

    // Public endpoint with hidden field + param
    neoma.Get(api, "/items",
        func(ctx context.Context, input *ListInput) (*ListOutput, error) {
            resp := &ListOutput{}
            resp.Body.Items = []string{"a", "b"}
            if input.Debug {
                resp.Body.DebugInfo = "debug data"
            }
            return resp, nil
        },
    )

    // Hidden endpoint (internal only)
    neoma.Register(api, core.Operation{
        OperationID: "health-check",
        Method:      http.MethodGet,
        Path:        "/health",
        Hidden:      true,
    }, func(ctx context.Context, _ *struct{}) (*struct{ Body struct{ OK bool } }, error) {
        resp := &struct{ Body struct{ OK bool } }{}
        resp.Body.OK = true
        return resp, nil
    })

    fmt.Println("Public docs:   http://localhost:8080/public/docs")
    fmt.Println("Internal docs: http://localhost:8080/internal/docs")
    log.Fatal(http.ListenAndServe(":8080", router))
}

In this example, GET /items appears in both specs, but the debug query parameter and debug_info response field only appear in the internal spec. GET /health only appears in the internal spec.

Clone this wiki locally