// 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 } }