formgen

package module
v0.19.0 Latest Latest
Warning

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

Go to latest
Published: Apr 15, 2026 License: MIT Imports: 10 Imported by: 3

README

formgen

A Go library that turns OpenAPI 3.x operations into ready-to-embed forms. It loads and parses an OpenAPI document, builds a typed form model, then renders HTML (or interactive CLI prompts) through pluggable renderers.

Documentation

Features

  • OpenAPI 3.x → typed FormModel (fields, validations, relationships, metadata/UI hints)
  • JSON Schema Draft 2020-12 adapter with deterministic $ref resolution and form discovery
  • Loaders for file, fs.FS, or HTTP; parser wraps kin-openapi output in stable domain types and merges allOf
  • Pluggable renderers: vanilla (Go templates), Preact (hydrated, embedded assets), and TUI/CLI
  • Orchestrator wiring with renderer registry, widget registry, endpoint overrides, and visibility evaluators
  • UI schema overlays (JSON/YAML) for sections, layout, icons, actions, and component overrides without touching templates
  • Optional i18n: UI schema *Key fields + render-time localization and template helpers
  • Render options: subsets (groups/tags/sections), prefill + provenance/readonly/disabled, hidden fields, server errors, visibility context
  • Block-union support (oneOf + _type) for modular content blocks via JSON Schema (x-formgen.widget=block)
  • Theme integration via go-theme selectors/providers + partial fallbacks; reuse or override embedded templates/assets
  • Transformers (JSON presets or JS runner seam) to mutate form models before decoration
  • Contract/golden tests cover parser, builder, renderers, and CLI paths

Installation

go get github.com/goliatone/go-formgen

Requires Go 1.23+.

Quick Start

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/goliatone/go-formgen"
	"github.com/goliatone/go-formgen/pkg/openapi"
)

func main() {
	ctx := context.Background()

	html, err := formgen.GenerateHTML(
		ctx,
		openapi.SourceFromFile("examples/fixtures/petstore.json"),
		"createPet",
		"vanilla", // or "preact", "tui"
	)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(string(html))
}

How It Works

  1. Loader resolves an OpenAPI document (file, fs.FS, or HTTP).
  2. Parser wraps operations into formgen.Document/Operation types.
  3. Builder emits a typed FormModel (Fields, validation rules, metadata, relationships).
  4. Renderer turns the model into HTML (vanilla/Preact) or CLI prompts (TUI).
  5. Optional UI schema decorators and endpoint overrides enrich metadata and layout hints.

Use the orchestrator when you want all stages wired for you, or inject your own loader/parser/renderer implementations via options.

Renderers

  • vanilla: Server-rendered HTML using Go templates. Accepts WithTemplatesFS/WithTemplatesDir and WithTemplateFuncs for custom bundles/helpers.
  • preact: Hydrate-able markup plus embedded JS/CSS (preact.AssetsFS()); WithAssetURLPrefix rewrites asset URLs for HTTP servers or CDNs.
  • tui: Interactive terminal prompts (JSON/form-url-encoded/pretty output). Run with --renderer tui in the CLI example or register it in the renderer registry.
registry := render.NewRegistry()
registry.MustRegister(vanilla.Must(vanilla.WithTemplatesFS(formgen.EmbeddedTemplates())))
registry.MustRegister(preact.New())

gen := formgen.NewOrchestrator(
	orchestrator.WithRegistry(registry),
	orchestrator.WithDefaultRenderer("vanilla"),
	orchestrator.WithWidgetRegistry(widgets.NewRegistry()), // adapters can RegisterWidget later
)

Programmatic Usage

Compose your own pipeline when you need custom sources, decorators, or render options:

ctx := context.Background()

loader := formgen.NewLoader(openapi.WithDefaultSources())
registry := render.NewRegistry()
registry.MustRegister(vanilla.Must(vanilla.WithTemplatesFS(formgen.EmbeddedTemplates())))

gen := formgen.NewOrchestrator(
	orchestrator.WithLoader(loader),
	orchestrator.WithParser(formgen.NewParser()),
	orchestrator.WithModelBuilder(model.NewBuilder()),
	orchestrator.WithRegistry(registry),
	orchestrator.WithEndpointOverrides([]formgen.EndpointOverride{
		{
			OperationID: "createPet",
			FieldPath:   "owner_id",
			Endpoint: formgen.EndpointConfig{
				URL:        "https://api.example.com/owners",
				Method:     "GET",
				LabelField: "name",
				ValueField: "id",
			},
		},
	}),
	orchestrator.WithThemeProvider(myThemeProvider, "default", "light"),
	orchestrator.WithThemeFallbacks(nil),
	orchestrator.WithVisibilityEvaluator(myVisibilityEvaluator),
)

output, err := gen.Generate(ctx, orchestrator.Request{
	Source:      openapi.SourceFromFile("openapi.json"),
	OperationID: "createPet",
	RenderOptions: render.RenderOptions{
		Method: "PATCH",
		Subset: render.FieldSubset{Groups: []string{"notifications"}}, // render a tab/section subset
		Values: map[string]any{
			"name": render.ValueWithProvenance{
				Value:      "Fido",
				Provenance: "tenant default",
				Disabled:   true,
			},
		},
		Errors: map[string][]string{"slug": {"Taken"}}, // surface server errors (go-errors compatible)
	},
})

UI schema files can also be injected (orchestrator.WithUISchemaFS) to control layout, sections, and action bars without editing templates.

Add a transformer when you need to rename fields or inject metadata without changing the OpenAPI source:

jsonPreset, _ := orchestrator.NewJSONPresetTransformerFromFS(os.DirFS("./presets"), "article.json")
gen := formgen.NewOrchestrator(orchestrator.WithSchemaTransformer(jsonPreset))

Examples & CLI

  • go run ./examples/basic – minimal end-to-end HTML generation
  • go run ./examples/multi-renderer – emit outputs for each registered renderer (copies Preact assets)
  • go run ./examples/http – tiny HTTP server serving rendered forms and assets; supports subsets (?groups=notifications), renderer switches, prefill/errors, and theme overrides
  • go run ./cmd/formgen-cli --renderer tui --operation createPet --source examples/fixtures/petstore.json --tui-format json

When serving HTML, remember to register and set a default renderer, and ensure the matching assets/partials are reachable (vanilla embeds templates; Preact assets live in preact.AssetsFS()).

Templates & Assets

  • Reuse formgen.EmbeddedTemplates() for vanilla or supply your own via WithTemplatesFS/Dir.
  • Preact ships embedded assets (preact.AssetsFS()); copy them to your static host or set WithAssetURLPrefix to point at a CDN/handler.
  • Serve the browser runtime bundles (relationships + runtime components like file_uploader and media_picker) from formgen.RuntimeAssetsFS() and mount them at /runtime/ so <script src="/runtime/formgen-relationships.min.js"> works.
  • Component overrides and UI schema metadata (placeholder, helpText, layout.*, icons, actions, behaviors) flow through to renderers for fine grained control.
  • Theme selection is resolved via WithThemeProvider/WithThemeSelector, providing partials/tokens/assets to renderers; set WithThemeFallbacks to ensure template keys always resolve.

Example mount:

mux.Handle("/runtime/",
  http.StripPrefix("/runtime/",
    http.FileServerFS(formgen.RuntimeAssetsFS()),
  ),
)

Runtime usage (vanilla renderer or custom pages):

<script src="/runtime/formgen-relationships.min.js" defer></script>
<script>
  window.addEventListener("DOMContentLoaded", () => {
    window.FormgenRelationships?.initRelationships?.();
  });
</script>

Localization (i18n)

Formgen supports localization without depending on a specific i18n package: supply any implementation of render.Translator (compatible with github.com/goliatone/go-i18n).

How To
  1. Add explicit translation keys in your UI schema while keeping human readable fallbacks:
{
  "operations": {
    "createPet": {
      "form": { "title": "Create Pet", "titleKey": "forms.createPet.title" },
      "sections": [{ "id": "main", "title": "Main", "titleKey": "forms.createPet.sections.main.title" }],
      "fields": { "name": { "label": "Name", "labelKey": "fields.pet.name" } },
      "actions": [{ "label": "Save", "labelKey": "actions.save", "type": "submit" }]
    }
  }
}
  1. Provide locale + translator at render time (missing translations go through OnMissing):
output, err := gen.Generate(ctx, orchestrator.Request{
  Source:      openapi.SourceFromFile("openapi.json"),
  OperationID: "createPet",
  RenderOptions: render.RenderOptions{
    Locale:     "es-MX",
    Translator: myTranslator, // implements: Translate(locale, key string, args ...any) (string, error)
    OnMissing: func(locale, key string, args []any, err error) string {
      return key // or inspect args[0].(map[string]any)["default"]
    },
  },
})
  1. (Optional) Use template level translation helpers in custom templates:
funcs := render.TemplateI18nFuncs(myTranslator, render.TemplateI18nConfig{
  FuncName:  "translate",
  LocaleKey: "locale",
})

renderer, _ := vanilla.New(
  vanilla.WithTemplatesFS(myTemplates),
  vanilla.WithTemplateFuncs(funcs),
)

In templates you can call {{ translate(locale, "forms.createPet.title") }} (formgen also passes render_options.locale).

Styling & Customization

Formgen provides multiple layers of styling customization, from default Tailwind styles to fully custom themes.

Quick Styling Options

Use default styles:

renderer, _ := vanilla.New(
    vanilla.WithTemplatesFS(formgen.EmbeddedTemplates()),
    vanilla.WithDefaultStyles(),  // Bundled Tailwind CSS
)

Inject custom CSS:

renderer, _ := vanilla.New(
    vanilla.WithInlineStyles(`
        form[data-formgen-auto-init] { width: 100%; max-width: none; }
    `),
)

Add external stylesheet:

renderer, _ := vanilla.New(
    vanilla.WithStylesheet("/static/custom-forms.css"),
)

Disable all styles:

renderer, _ := vanilla.New(vanilla.WithoutStyles())
Theme Integration with go-theme

Formgen integrates with go-theme to provide theme management:

Key Features:

  • Multi theme support with runtime switching
  • Theme variants (light/dark, compact/spacious, etc.)
  • Design tokens auto converted to CSS variables
  • Template overrides per theme
  • Asset management with URL resolution

Basic Example:

import theme "github.com/goliatone/go-theme"

manifest := &theme.Manifest{
    Name:    "acme",
    Version: "1.0.0",

    // Design tokens (auto converted to CSS vars)
    Tokens: map[string]string{
        "primary-color":       "#3b82f6",
        "container-max-width": "100%",
        "border-radius":       "0.5rem",
    },

    // Theme variants
    Variants: map[string]theme.Variant{
        "dark": {
            Tokens: map[string]string{
                "primary-color": "#60a5fa",
                "bg-primary":    "#1f2937",
            },
        },
        "two-column": {
            Tokens: map[string]string{
                "grid-columns-desktop": "2",
            },
        },
    },

    // Template overrides
    Templates: map[string]string{
        "forms.input": "themes/acme/input.tmpl",
    },

    // Asset bundle
    Assets: theme.Assets{
        Prefix: "/static/themes/acme",
        Files: map[string]string{
            "logo":       "logo.svg",
            "stylesheet": "acme.css",
        },
    },
}

provider := theme.NewRegistry()
provider.Register(manifest)

gen := formgen.NewOrchestrator(
    orchestrator.WithThemeProvider(provider, "acme", "dark"),
    orchestrator.WithThemeFallbacks(map[string]string{
        "forms.select": "templates/components/select.tmpl",
    }),
)

// Use default theme
output, _ := gen.Generate(ctx, orchestrator.Request{
    OperationID: "createPet",
})

// Override variant per request
output, _ := gen.Generate(ctx, orchestrator.Request{
    OperationID:  "createPet",
    ThemeVariant: "two-column",
})

What gets rendered:

<!-- CSS variables from theme tokens -->
<style data-formgen-theme-vars>
:root {
  --bg-primary: #1f2937;
  --border-radius: 0.5rem;
  --container-max-width: 100%;
  --primary-color: #60a5fa;
}
</style>

<!-- Theme metadata for JavaScript -->
<script id="formgen-theme" type="application/json">
{"name":"acme","variant":"dark","tokens":{...},"cssVars":{...}}
</script>

<form data-formgen-theme="acme" data-formgen-theme-variant="dark">
  <!-- form fields -->
</form>

See the Styling Guide for:

  • Complete theme manifest structure
  • Token merging and precedence
  • Template lookup order
  • Asset URL resolution
  • Custom theme selectors
Responsive Layouts

Control grid layout via UI schema metadata:

{
  "uiHints": {
    "layout.gridColumns": "12"
  },
  "fields": [
    {
      "name": "title",
      "uiHints": { "layout.span": "12" }  // full width
    },
    {
      "name": "email",
      "uiHints": { "layout.span": "6" }   // half width
    }
  ]
}

Or use responsive CSS:

@media (min-width: 1024px) {
  form[data-formgen-auto-init] .grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

See the complete Styling & Customization Guide for:

  • Custom template bundles
  • Fluid vs. fixed width containers
  • Responsive two column layouts
  • Theme variants and CSS variables
  • Component level customization
  • Complete working examples

Form Customization

Beyond styling, formgen supports extensive form behavior customization through UI Schemas - JSON/YAML files that configure forms without modifying OpenAPI specs or templates.

Custom Action Buttons

Add submit, reset, cancel, or custom buttons via UI schema:

{
  "operations": {
    "createArticle": {
      "form": {
        "actions": [
          {
            "kind": "secondary",
            "label": "Clear Form",
            "type": "reset"
          },
          {
            "kind": "secondary",
            "label": "Save Draft",
            "type": "button"
          },
          {
            "kind": "primary",
            "label": "Publish",
            "type": "submit"
          }
        ]
      }
    }
  }
}
Sections and Layout

Organize fields into logical sections with custom grid layouts:

{
  "operations": {
    "createPet": {
      "form": {
        "title": "Add New Pet",
        "layout": {
          "gridColumns": 12,
          "gutter": "md"
        }
      },
      "sections": [
        {
          "id": "basic-info",
          "title": "Basic Information",
          "fieldset": true,
          "order": 0
        },
        {
          "id": "health",
          "title": "Health Records",
          "order": 1
        }
      ],
      "fields": {
        "name": {
          "section": "basic-info",
          "grid": { "span": 8 },
          "helpText": "Your pet's name"
        },
        "age": {
          "section": "basic-info",
          "grid": { "span": 4 }
        }
      }
    }
  }
}
Widgets and Components

Use built-in widgets or register custom components:

{
  "fields": {
    "description": {
      "component": "wysiwyg",
      "componentOptions": {
        "toolbar": ["bold", "italic", "link"]
      }
    },
    "category": {
      "component": "custom-select",
      "componentOptions": {
        "endpoint": "/api/categories"
      }
    }
  }
}
Behaviors

Add client-side behaviors like auto slug:

{
  "fields": {
    "title": {
      "label": "Article Title"
    },
    "slug": {
      "helpText": "Auto-generated from title",
      "behaviors": {
        "autoSlug": {
          "source": "title"
        }
      }
    }
  }
}
Loading UI Schemas
//go:embed ui-schemas
var uiSchemas embed.FS

gen := formgen.NewOrchestrator(
    orchestrator.WithUISchemaFS(uiSchemas),
)

// OperationID selects which entry under "operations" is applied.
output, _ := gen.Generate(ctx, orchestrator.Request{
    OperationID: "createArticle",
})

See the complete Form Customization Guide for:

  • Action button configuration (submit, reset, cancel, custom)
  • Section and fieldset organization
  • Field-level customization (labels, help text, grid positioning)
  • Widgets and components (vanilla components + Preact widget hints)
  • Custom component registration
  • Icons and visual enhancements
  • Behaviors (auto slug + custom behavior hooks)
  • Field ordering and presets
  • Three complete working examples (blog, registration, e-commerce)

Testing & Tooling

./taskfile dev:test            # go test ./... with cached modules
./taskfile dev:test:contracts  # contract + integration suites (renderer coverage)
./taskfile dev:test:examples   # compile example binaries with -tags example (vanilla + Preact)
./taskfile dev:ci              # vet + optional golangci-lint (includes example build)
./taskfile dev:goldens         # regenerate vanilla/Preact snapshots via scripts/update_goldens.sh
./scripts/update_goldens.sh    # refresh vanilla/Preact snapshots and rerun example builds

Troubleshooting

  • Stay offline? Omit HTTP loader options and load from files/embedded assets.
  • Template validation failures? Reuse formgen.EmbeddedTemplates() (vanilla) or preact.TemplatesFS().
  • Renderer not found? Ensure it is registered in the render.Registry and set as the default when using the orchestrator helpers.
  • Relationship endpoints missing from the OpenAPI? Provide WithEndpointOverrides with FieldPath/OperationID or embed x-endpoint metadata.
  • Visibility rules present but ignored? Pass a visibility.Evaluator via WithVisibilityEvaluator and feed RenderOptions.VisibilityContext / Values.

License

MIT © Goliatone

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func EmbeddedTemplates

func EmbeddedTemplates() fs.FS

EmbeddedTemplates exposes the built-in vanilla renderer templates so callers can reuse or extend them without importing the renderer package directly.

func GenerateHTML

func GenerateHTML(ctx context.Context, source pkgopenapi.Source, operationID, rendererName string, options ...orchestrator.Option) ([]byte, error)

GenerateHTML loads the OpenAPI source, builds a form model for the requested operation, and renders it using the named renderer. It is the simplest entry point for callers that just want HTML output.

func GenerateHTMLFromDocument

func GenerateHTMLFromDocument(ctx context.Context, doc pkgopenapi.Document, operationID, rendererName string, options ...orchestrator.Option) ([]byte, error)

GenerateHTMLFromDocument renders a form using a pre-loaded document, bypassing the loader stage while still delegating to the orchestrator.

func NewLoader

func NewLoader(options ...pkgopenapi.LoaderOption) pkgopenapi.Loader

NewLoader constructs a loader using the internal implementation while keeping the concrete type hidden from consumers.

func NewOrchestrator

func NewOrchestrator(options ...orchestrator.Option) *orchestrator.Orchestrator

NewOrchestrator exposes the orchestrator constructor from the top-level module, mirroring the quick start guidance in go-form-gen.md:223-258.

func NewParser

func NewParser(options ...pkgopenapi.ParserOption) pkgopenapi.Parser

NewParser constructs a parser backed by the internal implementation.

func RuntimeAssetsFS added in v0.6.0

func RuntimeAssetsFS() fs.FS

RuntimeAssetsFS exposes the prebuilt browser runtime bundles (committed under pkg/runtime/assets) so Go applications can serve them without an npm build step.

Typical mount:

mux.Handle("/runtime/",
  http.StripPrefix("/runtime/",
    http.FileServerFS(formgen.RuntimeAssetsFS()),
  ),
)

func WithEndpointOverrides

func WithEndpointOverrides(overrides []EndpointOverride) orchestrator.Option

WithEndpointOverrides registers endpoint overrides that can be passed to GenerateHTML alongside other orchestrator options.

func WithThemeFallbacks

func WithThemeFallbacks(fallbacks map[string]string) orchestrator.Option

WithThemeFallbacks forwards fallback partials used when deriving renderer configuration from a theme selection.

func WithThemeProvider

func WithThemeProvider(provider theme.ThemeProvider, defaultTheme, defaultVariant string) orchestrator.Option

WithThemeProvider constructs a go-theme selector from a ThemeProvider and registers it with the orchestrator so renderers receive resolved partials, tokens, and assets.

func WithThemeSelector

func WithThemeSelector(selector theme.ThemeSelector) orchestrator.Option

WithThemeSelector passes a go-theme selector through to the orchestrator so theme/variant choices can be resolved ahead of rendering.

Types

type EndpointAuth

type EndpointAuth = orchestrator.EndpointAuth

EndpointAuth describes runtime authentication hints.

type EndpointConfig

type EndpointConfig = orchestrator.EndpointConfig

EndpointConfig mirrors the x-endpoint contract; alias exported via the root package for convenience.

type EndpointMapping

type EndpointMapping = orchestrator.EndpointMapping

EndpointMapping remaps endpoint payload fields.

type EndpointOverride

type EndpointOverride = orchestrator.EndpointOverride

EndpointOverride configures manual endpoint metadata for a form field.

type FieldSubset

type FieldSubset = render.FieldSubset

FieldSubset aliases render.FieldSubset for callers configuring partial rendering by group/tag/section.

type RenderOptions

type RenderOptions = render.RenderOptions

RenderOptions describes per-request overrides that renderers can use to prefill values or surface server-side validation errors.

Directories

Path Synopsis
cmd
formgen-cli command
components
timezones
Package timezones provides deterministic IANA timezone data, search helpers, and a small net/http handler that returns JSON options for form inputs.
Package timezones provides deterministic IANA timezone data, search helpers, and a small net/http handler that returns JSON options for form inputs.
examples
basic command
cli command
multi-renderer command
internal
pkg
model
Package model defines the typed form model consumed by renderers, following the structure documented in go-form-gen.md:97-158.
Package model defines the typed form model consumed by renderers, following the structure documented in go-form-gen.md:97-158.
openapi
Package openapi exposes the public contracts for loader and parser stages, mirroring the wrapper strategy described in go-form-gen.md:25-159 and go-form-gen.md:304-357.
Package openapi exposes the public contracts for loader and parser stages, mirroring the wrapper strategy described in go-form-gen.md:25-159 and go-form-gen.md:304-357.
orchestrator
Package orchestrator wires the loader → parser → model builder → renderer pipeline described in go-form-gen.md:25-258, providing dependency injection friendly helpers for consumers that prefer a single entry point.
Package orchestrator wires the loader → parser → model builder → renderer pipeline described in go-form-gen.md:25-258, providing dependency injection friendly helpers for consumers that prefer a single entry point.
render
Package render hosts renderer interfaces and registry helpers, matching the extensibility goals detailed in go-form-gen.md:137-159 and go-form-gen.md:223-239.
Package render hosts renderer interfaces and registry helpers, matching the extensibility goals detailed in go-form-gen.md:137-159 and go-form-gen.md:223-239.
render/template
Package template defines renderer-agnostic template interfaces and adapters.
Package template defines renderer-agnostic template interfaces and adapters.
schema
Package schema defines format-agnostic schema types and adapter contracts.
Package schema defines format-agnostic schema types and adapter contracts.
uischema
Package uischema loads and applies UI schema decorators that enrich formgen form models with layout metadata, section assignments, and action definitions.
Package uischema loads and applies UI schema decorators that enrich formgen form models with layout metadata, section assignments, and action definitions.
scripts

Jump to

Keyboard shortcuts

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