Files
sandwich-hime/sando/component.go
T
gamertan 113c95c21e security: publish hardened v1 initiative snapshot
Publish the reviewed security policy and evidence, exact runtime ABI enforcement, orphan-output and permission safeguards, dead-upstream cleanup, and the evidence-gated v1 launch plan.

This commit is an exact sanitized export from the private development record. Material implementation and review were assisted by OpenAI Codex; Cole Speelman reviewed the changes and accepts human responsibility.

Himesan-Output-Permission: v1.0
Signed-off-by: Cole Speelman <gamertan@noreply.localhost>
2026-08-12 03:54:46 -04:00

99 lines
3.1 KiB
Go

// SPDX-License-Identifier: Apache-2.0
// Package sando is the small production runtime for code generated by Hime-san.
// It contains rendering contracts and context-specific output helpers, but no
// router, HTTP server, middleware, or development tooling.
package sando
import (
"context"
"errors"
"io"
"reflect"
)
// ABI identifies the generated-code contract implemented by this version of
// the runtime. It is descriptive metadata for people and tooling.
const ABI = "sando.v1"
// ABISandoV1 is the compile-time compatibility marker for generated code that
// requires the sando.v1 contract. A future runtime may retain this symbol while
// it remains backward compatible; an incompatible runtime must remove it so
// affected generated packages fail at build time instead of failing subtly at
// render time.
const ABISandoV1 = ABI
// RuntimeABI is a descriptive alias for ABI.
const RuntimeABI = ABI
var (
// ErrNilComponent is returned when Render is asked to render a nil
// component, including a typed nil held in a Component interface.
ErrNilComponent = errors.New("sando: nil component")
// ErrNilContext is returned when a component is rendered with a nil
// context.Context.
ErrNilContext = errors.New("sando: nil context")
// ErrNilWriter is returned when a component is rendered with a nil writer.
ErrNilWriter = errors.New("sando: nil writer")
)
// Component is the complete production rendering contract. Components are
// values rather than HTTP handlers so applications retain ownership of
// buffering, routing, headers, status codes, and error policy.
//
// A Component implemented by handwritten Go is a trusted output capability: it
// can write arbitrary bytes, block, panic, recurse, or change the surrounding
// HTML parser context. Hime-generated components are separately checked for a
// balanced, context-neutral HTML boundary before they implement this contract.
type Component interface {
Render(context.Context, io.Writer) error
}
// ComponentFunc adapts a function to Component.
type ComponentFunc func(context.Context, io.Writer) error
// Render calls f with ctx and w.
func (f ComponentFunc) Render(ctx context.Context, w io.Writer) error {
if f == nil {
return ErrNilComponent
}
if ctx == nil {
return ErrNilContext
}
if isNil(w) {
return ErrNilWriter
}
return f(ctx, w)
}
// Render renders component into w. It reports nil inputs as errors rather than
// panicking, including typed nil component and writer values.
func Render(ctx context.Context, w io.Writer, component Component) error {
if ctx == nil {
return ErrNilContext
}
if isNil(w) {
return ErrNilWriter
}
if isNil(component) {
return ErrNilComponent
}
return component.Render(ctx, w)
}
// isNil recognizes typed nil values stored in interfaces. It is intentionally
// confined to API boundary validation and is not a component registry.
func isNil(value any) bool {
if value == nil {
return true
}
rv := reflect.ValueOf(value)
switch rv.Kind() {
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Pointer, reflect.Slice:
return rv.IsNil()
default:
return false
}
}