88 lines
2.5 KiB
Go
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
|
|
}
|
|
}
|