playground

package
v3.24.89 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 18, 2026 License: MIT Imports: 17 Imported by: 0

README

Embedding the expression playground

playground serves the API behind the language playground: evaluate an expression, fetch the function catalogue, fetch the sample documents. It runs everything through the same entry points a gomplate caller uses, so what an author sees in the editor is what production does.

A host embeds it to give its own authors a playground over its own language. Everything a host registers on top of gomplate — mission-control's catalog.query, gitops.source — flows through Options, so the catalogue, the highlighting and the evaluator all agree with what that binary can actually run.

Mounting

handler, err := playground.NewHandler(playground.Options{
    Timeout: 5 * time.Second,
})
if err != nil {
    return err
}
mux.Handle("/playground/", http.StripPrefix("/playground", handler.Mux()))

Mux() is an http.Handler, so an echo host wraps it:

group.Any("/playground/*", echo.WrapHandler(
    http.StripPrefix("/playground", handler.Mux()),
))

Routes: POST /api/eval, GET /api/spec, GET /api/examples, GET /api/health.

⚠️ It carries no authorization

/api/eval runs arbitrary expressions with whatever Options grants them. In a host whose functions reach a database or a repository, that is arbitrary execution against real data — a catalog.query an author can write is a catalog.query anyone reaching the endpoint can write.

Mount it inside an already-authenticated route group, under the same authorization you would put any other query endpoint behind. The package deliberately does not offer a half-measure of its own.

Supplying your own functions

Both fields are factories rather than plain slices, matching how hosts already register — duty keeps map[string]func(Context) cel.EnvOption because a function like catalog.query closes over the database handle it queries through.

playground.Options{
    CelEnvs: func(ctx context.Context) []cel.EnvOption {
        opts := make([]cel.EnvOption, 0, len(duty.CelEnvFuncs))
        for _, f := range duty.CelEnvFuncs {
            opts = append(opts, f(dutyContext(ctx)))
        }
        return opts
    },
    Functions: func(ctx context.Context) map[string]any {
        out := map[string]any{}
        for name, f := range duty.TemplateFuncs {
            out[name] = f(dutyContext(ctx))
        }
        return out
    },
}

CelEnvs reaches three places at once, which is the point:

  • the evaluator, through gomplate.Template.CelEnvs;
  • the compile check that gives errors a source position, through gomplate.CompileEnvOptions — miss it there and a host's own function reports "undeclared reference" before the evaluator ever sees it;
  • the catalogue at GET /api/spec, through genmonarch.ExtractCEL, which reads a live cel.Env rather than a maintained list. A cel.Function("catalog.query", cel.Overload(...)) shows up there with its typed overloads, and the editor highlights and completes it without any change to the grammar.

Functions is exposed to both CEL and go templates, subject to gomplate's existing constraint: a CEL-visible entry must be a func() any. Anything with real arguments belongs in CelEnvs.

The spec is extracted once, at NewHandler, against context.Background(). The factories are per-request because a function's binding closes over a request; the declarations it registers — names, overloads, types — are the same every time.

Sample data

playground.Options{
    Examples: []playground.Example{{
        Name:     "Unhealthy config items",
        Language: playground.LanguageCEL,
        Source:   `catalog.query("health=unhealthy").size() > 0`,
        Input:    "…",
    }},
}

Served from GET /api/examples, always as an array.

Bounding an evaluation

Options.Timeout bounds the response, not the work. gomplate honours no context deadline while evaluating — there is no cel.ContextEval and no deadline check in RunTemplateContext — so a runaway expression keeps its goroutine after the caller has been answered.

That is still the right trade for a shared endpoint, where a hung request is the worse failure. But it is not cancellation, and a host that expects to see CPU released on timeout will be disappointed.

Known limits

  • Your functions will have thin documentation. The catalogue reads decl.Description(); declare cel.FunctionDocs and overload examples to get prose and examples in hovers. Worth knowing: gomplate's own gencel-generated functions do not set them either, so this is a shared gap rather than a tax on hosts.
  • Go-template functions extract less well than CEL ones. gomplate's own readable signatures come from parsing its source with go/packages; a host's map[string]any closure yields reflection types only, with no parameter names.
  • The conformance corpus does not cover host vocabulary. It round-trips snippets from gomplate's docs through the real lexers. Hosts inherit the grammar guarantees, but need their own snippets for the same guard on their own functions.

Documentation

Overview

Package playground evaluates expressions for the language playground, using the same entry points a gomplate caller uses so what the playground shows is what production does.

A host embeds this to give its own authors a playground over its own language: Options carries the CEL options and template functions the host registers, so the catalogue, the highlighting and the evaluator all agree with what that binary can actually run.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type EvalError

type EvalError struct {
	Message string `json:"message"`
	Line    int    `json:"line,omitempty"`
	Column  int    `json:"column,omitempty"`
}

EvalError carries a message and, where the compiler reports one, a source position so the editor can place a marker on the offending token.

type Example

type Example struct {
	Name     string   `json:"name"`
	Language Language `json:"language"`
	Source   string   `json:"source"`
	Input    string   `json:"input"`
}

Example is one sample an author can load into the playground.

type Handler

type Handler struct {
	// contains filtered or unexported fields
}

Handler serves the playground API.

It carries no authentication of its own, deliberately. /api/eval runs arbitrary expressions with whatever Options grants them: in a host whose functions reach a database or a repository, that is arbitrary execution against real data. Mount it behind the same authorization as any other query endpoint. Handler.Mux satisfies http.Handler, so echo hosts wrap it with echo.WrapHandler inside an already-authenticated group.

func NewHandler

func NewHandler(options Options) (*Handler, error)

NewHandler builds the API over a freshly extracted spec, so a running playground reflects the current binary rather than a stale generated file.

The spec is extracted once, against context.Background(): Options.CelEnvs is a per-request factory because a function's *binding* closes over a request's context, but the declarations it registers -- the names, overloads and types the editor completes from -- are the same for every request.

func (*Handler) Evaluate

func (h *Handler) Evaluate(ctx context.Context, req Request) (*Response, error)

Evaluate runs one request. A failed evaluation is a populated Error in the response, not a Go error: the playground always has something to render. A Go error means the request itself was malformed.

func (*Handler) Mux

func (h *Handler) Mux() *http.ServeMux

Mux returns the routes, ready to serve.

type Language

type Language string

Language selects which evaluator to run.

const (
	LanguageCEL        Language = "cel"
	LanguageGoTemplate Language = "gotemplate"
	LanguageJSONPath   Language = "jsonpath"
	LanguageJavaScript Language = "javascript"
)

type Options

type Options struct {
	// CelEnvs are layered onto gomplate's own CEL options, per evaluation.
	CelEnvs func(context.Context) []cel.EnvOption
	// Functions are exposed to both CEL and go templates. Note gomplate's
	// constraint: a CEL-visible entry must be a `func() any`; anything else
	// belongs in CelEnvs.
	Functions func(context.Context) map[string]any
	// Examples are the samples the playground offers to load.
	Examples []Example
	// Timeout bounds one evaluation. Zero means no bound.
	//
	// It bounds the *response*, not the work: gomplate honours no context
	// deadline while evaluating -- there is no cel.ContextEval and no deadline
	// check in RunTemplateContext -- so a runaway expression keeps its
	// goroutine after the caller has been answered. That is still the right
	// trade for a shared endpoint, where a hung request is the worse failure,
	// but it is not cancellation and should not be mistaken for it.
	Timeout time.Duration
}

Options configure a playground for one host.

The two function fields are shaped as factories rather than plain slices to match how hosts already register: duty keeps `map[string]func(Context) cel.EnvOption`, because a function like `catalog.query` closes over the database handle it queries through.

type Request

type Request struct {
	Language Language `json:"language"`
	Source   string   `json:"source"`
	// Input is the evaluation environment, as YAML or JSON. JSON is valid YAML,
	// so one parser covers both.
	Input string `json:"input,omitempty"`
	// LeftDelim and RightDelim override the go-template delimiters. Both must
	// be set together.
	LeftDelim  string `json:"leftDelim,omitempty"`
	RightDelim string `json:"rightDelim,omitempty"`
}

Request is one evaluation.

type Response

type Response struct {
	// Result is the value rendered as a string, as a gomplate caller sees it.
	Result string `json:"result"`
	// Value is the native result, so the playground can show typed JSON rather
	// than a stringified value.
	Value any `json:"value,omitempty"`
	// Type names the Go type of Value, which is what makes CEL's int/uint/
	// double distinction visible.
	Type string `json:"type,omitempty"`
	// Error is set when evaluation failed. Result is empty in that case.
	Error *EvalError `json:"error,omitempty"`
	// DurationMs is wall-clock evaluation time.
	DurationMs float64 `json:"durationMs"`
}

Response is the result of an evaluation.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL