uiprojector

package module
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Aug 18, 2026 License: Apache-2.0 Imports: 8 Imported by: 0

README

UI Projector

uiprojector is a metadata-driven engine that transforms raw workflow state and business data into a structured UI payload. It uses a Zone-Based Architecture to map transformation logic (Projectors) and layout rules (Blueprints) into named UI slots.

Core Concepts

1. Blueprint (The Layout)

A Blueprint defines the structural rules for a view. It maps named Zones (e.g., "main", "sidebar") to specific components.

  • TemplateID: The identifier for the raw template content.
  • Projector: The strategy used to transform the data (e.g., FORM, MARKDOWN).
  • VisibleWhen: Declarative rules that hide/show zones based on current state or data presence.
2. Facts (The Input)

Facts represent the current context of the business entity being rendered.

  • State: The logical status (e.g., PENDING, APPROVED).
  • Data: A map of raw data plucked into sections via DataKey.
3. Projector (The Strategy)

Projectors are transformation strategies. Built-in projectors include:

  • FORM: Transforms JSON Schema templates into interactive forms.
  • MARKDOWN: Renders Go text/template markdown.
  • RAW: Returns data as-is.
4. Assembler (The Engine)

The Assembler orchestrates the lifecycle:

  1. Validates visibility rules via ShouldRender.
  2. Resolves and fetches templates via a TemplateProvider.
  3. Selects the appropriate Projector.
  4. Plucks specific data via DataKey.
  5. Projects the final content into the designated Zone.

Usage

// 1. Setup dependencies
tp := MyTemplateProvider{} 
projectors := uiprojector.DefaultProjectors()

// 2. Initialize Assembler
asm, err := uiprojector.NewAssembler(tp, projectors)
if err != nil {
    // handle error
}

// 3. Assemble a view
blueprint := &uiprojector.Blueprint{
    Sections: map[string]uiprojector.SectionBlueprint{
        "main": {
            ID: "my-form",
            Projector: "FORM",
            TemplateID: "form-v1",
        },
    },
}
facts := uiprojector.Facts{State: "DRAFT", Data: map[string]any{}}

zones, err := asm.Assemble(ctx, blueprint, facts)
if err != nil {
    // handle error
}

// Access rendered content by zone
mainContent := zones["main"].Content

Architecture Features

  • Zone-Based: Named slots instead of simple lists allow for complex, shell-driven layouts.
  • Stateless Visibility: Visibility logic is decoupled and testable through pure functions.
  • Immutability: The Assembler uses defensive copying for its projector registry to prevent external side effects.
  • Storage Agnostic: Templates can be fetched from S3, local disk, or databases via the TemplateProvider interface.

Extensibility

You can register custom projectors by appending to the slice passed to NewAssembler:

projectors := append(uiprojector.DefaultProjectors(), &ChartProjector{})
asm, err := uiprojector.NewAssembler(tp, projectors)

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ShouldRender

func ShouldRender(section SectionBlueprint, facts Facts) bool

ShouldRender implements generic visibility logic.

Types

type Assembler

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

Assembler transforms a Blueprint and Facts into a list of rendered Sections.

func NewAssembler

func NewAssembler(tp TemplateProvider, projectors []Projector) (*Assembler, error)

NewAssembler builds an Assembler from a TemplateProvider and a slice of Projectors. Each projector's Type() is used as its registration key; duplicate types return an error.

func (*Assembler) Assemble

func (a *Assembler) Assemble(ctx context.Context, blueprint *Blueprint, facts Facts) (map[string]Section, error)

Assemble is the "pure" transformation logic.

type Blueprint

type Blueprint struct {
	ID       string                      `json:"id"`
	Sections map[string]SectionBlueprint `json:"sections"`
}

Blueprint defines the layout and rules for a UI view.

type Facts

type Facts struct {
	State string         `json:"state"` // Logical status (e.g., "PENDING", "COMPLETED")
	Data  map[string]any `json:"data"`  // The snapshot/registry of business data
	// Claims holds AuthZ decisions pre-resolved by the caller before Assemble.
	// Keys are matched case-sensitively (see VisibleWhen.RequireClaim). The
	// caller must populate every claim its blueprint references — including
	// those it wants to deny, which are set explicitly to false. Assemble
	// treats a referenced claim that is absent from this map as a caller error
	// rather than a silent deny, so typos and casing mismatches surface loudly
	// instead of silently hiding a section.
	Claims map[string]bool `json:"claims,omitempty"`
}

Facts represents the current state of a business entity to be rendered.

type FormContent

type FormContent struct {
	Schema   any `json:"schema"`
	UISchema any `json:"uiSchema,omitempty"`
	Data     any `json:"data,omitempty"`
}

FormContent is the payload for a FORM projector.

type FormProjector

type FormProjector struct{}

FormProjector projects raw JSON schema into a FormContent payload.

func NewFormProjector

func NewFormProjector() *FormProjector

func (*FormProjector) Project

func (p *FormProjector) Project(ctx context.Context, templateContent []byte, data any) (Projection, error)

func (*FormProjector) Type

func (p *FormProjector) Type() ProjectorType

type MarkdownProjector

type MarkdownProjector struct{}

MarkdownProjector projects a markdown template using Go's text/template.

func NewMarkdownProjector

func NewMarkdownProjector() *MarkdownProjector

func (*MarkdownProjector) Project

func (p *MarkdownProjector) Project(ctx context.Context, templateContent []byte, data any) (Projection, error)

func (*MarkdownProjector) Type

func (p *MarkdownProjector) Type() ProjectorType

type Projection

type Projection struct {
	Type    SectionType
	Content any
}

Projection is the result of a projector's Project call. Type identifies the render shape the frontend should use; Content is the payload it renders. A single projector may emit different Types per invocation when the choice depends on runtime data (e.g. a payment projector switching between REDIRECT and DESCRIPTION based on the configured payment method).

type Projector

type Projector interface {
	Type() ProjectorType
	Project(ctx context.Context, templateContent []byte, data any) (Projection, error)
}

Projector defines the interface for transforming raw template + data into a UI payload. Type returns the identifier under which the projector is registered; it must match the SectionBlueprint.Projector values used in blueprints. The render type used by the frontend is carried on the Projection returned from Project, not on Type().

func DefaultProjectors

func DefaultProjectors() []Projector

DefaultProjectors returns a fresh slice containing the projectors shipped with this package. The returned slice is owned by the caller and safe to mutate — append, replace, or drop entries before passing it to NewAssembler.

type ProjectorType

type ProjectorType string

ProjectorType identifies a projector implementation. External packages may declare their own ProjectorType constants to register custom projectors.

const (
	ProjectorForm     ProjectorType = "FORM"
	ProjectorMarkdown ProjectorType = "MARKDOWN"
	ProjectorRaw      ProjectorType = "RAW"
)

Built-in projector keys. These are the names returned by each projector's Type() method, and they match the SectionBlueprint.Projector values used in blueprints. Consumers may register additional projectors whose Type() returns any other unique string.

type RawProjector

type RawProjector struct{}

RawProjector returns the data as-is without any transformation.

func NewRawProjector

func NewRawProjector() *RawProjector

func (*RawProjector) Project

func (p *RawProjector) Project(ctx context.Context, templateContent []byte, data any) (Projection, error)

func (*RawProjector) Type

func (p *RawProjector) Type() ProjectorType

type Section

type Section struct {
	Type    SectionType `json:"type"`
	Title   string      `json:"title"`
	Content any         `json:"content"`
}

Section represents a rendered component. Sections are returned in a slot-keyed map; the slot key is the identifier, so Section carries none.

type SectionBlueprint

type SectionBlueprint struct {
	TemplateID  string       `json:"templateId"`
	Title       string       `json:"title"`
	Projector   string       `json:"projector"` // e.g., FORM, MARKDOWN
	DataKey     string       `json:"dataKey"`   // The key in Facts.Data to pluck for this section
	VisibleWhen *VisibleWhen `json:"visibleWhen,omitempty"`
}

SectionBlueprint defines an individual component within a layout. The section's slot key in the surrounding Blueprint.Sections map is the authoritative identifier; SectionBlueprint deliberately has no own ID.

type SectionType

type SectionType string

SectionType identifies the projector used for a section.

const (
	SectionTypeForm     SectionType = "FORM"
	SectionTypeMarkdown SectionType = "MARKDOWN"
	SectionTypeRaw      SectionType = "RAW"
)

Built-in render types emitted by the projectors shipped with this package. These are the SectionType values carried on Projection.Type and copied into Section.Type for the frontend. Custom projectors may emit any other string, and a single projector may emit more than one render type per invocation.

type TemplateProvider

type TemplateProvider interface {
	GetTemplate(ctx context.Context, templateID string) ([]byte, error)
}

TemplateProvider abstracts the resolution of TemplateID to raw bytes.

type VisibleWhen

type VisibleWhen struct {
	States         []string `json:"states,omitempty"`         // Required Facts.State values
	RequireDataKey string   `json:"requireDataKey,omitempty"` // Section only visible if this key exists in data
	// RequireClaim gates the section on a single named claim. The section is
	// visible only if Facts.Claims[RequireClaim] is true. The claim name is
	// matched against Facts.Claims by exact, case-sensitive key lookup:
	// "canApprove", "can_approve", and "CanApprove" are three distinct claims,
	// so the blueprint author and the caller must agree on the exact spelling.
	// Compose any AND/OR/complex logic into a single named claim in the caller.
	RequireClaim string `json:"requireClaim,omitempty"`
}

VisibleWhen defines declarative visibility rules based on Facts.

Jump to

Keyboard shortcuts

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