Files
2026-08-11 20:15:06 -04:00

88 lines
2.5 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. Generated files should reference this constant so that their
// expected ABI is visible to readers and tooling.
const ABI = "sando.v1"
// 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.
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
}
}