templator

package module
v0.0.0-...-0f2701e Latest Latest
Warning

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

Go to latest
Published: Jan 15, 2025 License: MIT Imports: 9 Imported by: 0

README

Templator

codecov

A type-safe HTML template rendering engine for Go.

Note: This is an experimental project in active development. While it's stable enough for use, expect possible API changes. Feedback and contributions are welcome!

Table of Contents

Problem Statement

Go's built-in template package lacks type safety, which can lead to runtime errors when template data doesn't match what the template expects. For example:

// Traditional approach with Go templates
tmpl := template.Must(template.ParseFiles("home.html"))

// This compiles but will fail at runtime if the template expects different fields
tmpl.Execute(w, struct {
    WrongField string
    MissingRequired int
}{})

// No compile-time checks for:
// - Missing required fields
// - Wrong field types
// - Typos in field names

Templator solves this by providing compile-time type checking for your templates:

// Define your template data type
type HomeData struct {
    Title   string
    Content string
}

// Initialize registry with type parameter
reg, _ := templator.NewRegistry[HomeData](fs)

// Get type-safe handler and execute template
home, _ := reg.GetHome()
home.Execute(ctx, w, HomeData{
    Title: "Welcome",
    Content: "Hello",
})

// Won't compile - wrong data structure
home.Execute(ctx, w, struct{
    WrongField string
}{})

Features

  • Type-safe template execution with generics
  • Concurrent-safe template management
  • Custom template functions support
  • Clean and simple API
  • HTML escaping by default

Installation

go install github.com/alesr/templator

Quick Start

package main

import (
    "context"
    "log"
    "os"

    "github.com/alesr/templator"
)

// Define your template data
type HomeData struct {
    Title   string
    Content string
}

func main() {
    // Use the filesystem of your choice
    fs := os.DirFS(".")

    // Initialize registry with your data type
    reg, err := templator.NewRegistry[HomeData](fs)
    if err != nil {
        log.Fatal(err)
    }

    // Get type-safe handler for home template
    home, err := reg.GetHome()
    if err != nil {
        log.Fatal(err)
    }

    // Execute template with proper data
    err = home.Execute(context.Background(), os.Stdout, HomeData{
        Title:   "Welcome",
        Content: "Hello, World!",
    })
    if err != nil {
        log.Fatal(err)
    }
}

Usage Examples

Type-Safe Templates
// Define different data types for different templates
type HomeData struct {
    Title    string
    Content  string
}

type AboutData struct {
    Company  string
    Year     int
}

// Create registries for different template types
homeReg := templator.NewRegistry[HomeData](fs)
aboutReg := templator.NewRegistry[AboutData](fs)

// Get handlers
home, _ := homeReg.GetHome()
about, _ := aboutReg.GetAbout()

// Type safety enforced at compile time
home.Execute(ctx, w, HomeData{...})  // ✅ Compiles
home.Execute(ctx, w, AboutData{...}) // ❌ Compile error
Using Template Functions
// Define your custom functions
funcMap := template.FuncMap{
    "upper": strings.ToUpper,
    "lower": strings.ToLower,
}

// Create registry with functions
reg, err := templator.NewRegistry[PageData](fs, 
    templator.WithTemplateFuncs[PageData](funcMap))

// Use functions in your templates:
// <h1>{{.Title | upper}}</h1>
File System Support
// Embedded FS
//go:embed templates/*
var embedFS embed.FS
reg := templator.NewRegistry[HomeData](embedFS)

// OS File System
reg := templator.NewRegistry[HomeData](os.DirFS("./templates"))

// In-Memory (testing)
fsys := fstest.MapFS{
    "templates/home.html": &fstest.MapFile{
        Data: []byte(`<h1>{{.Title}}</h1>`),
    },
}
reg := templator.NewRegistry[HomeData](fsys)
Field Validation
type ArticleData struct {
    Title    string    // Only these two fields
    Content  string    // are allowed in templates
}

// Enable validation during registry creation
reg := templator.NewRegistry[ArticleData](
    fs,
    templator.WithFieldValidation(ArticleData{}),
)

// Example templates:

// valid.html:
// <h1>{{.Title}}</h1>           // ✅ OK - Title exists in ArticleData
// <p>{{.Content}}</p>           // ✅ OK - Content exists in ArticleData

// invalid.html:
// <h1>{{.Author}}</h1>          // ❌ Error - Author field doesn't exist
// <p>{{.PublishedAt}}</p>       // ❌ Error - PublishedAt field doesn't exist

// Using the templates:
handler, err := reg.Get("valid")    // ✅ Success - all fields exist
if err != nil {
    log.Fatal(err)
}

handler, err := reg.Get("invalid")  // ❌ Error: "template 'invalid' validation error: Author - field 'Author' not found in type ArticleData"

// The validation error provides:
// - Template name
// - Invalid field path
// - Detailed error message
if validErr, ok := err.(*templator.ValidationError); ok {
    fmt.Printf("Template: %s\n", validErr.TemplateName)
    fmt.Printf("Invalid field: %s\n", validErr.FieldPath)
    fmt.Printf("Error: %v\n", validErr.Err)
}

This validation happens when loading the template, not during execution, helping catch field mismatches early in development.

Template Generation

Templates are automatically discovered and type-safe methods are generated:

templates/
├── home.html           -> reg.GetHome()
├── about.html          -> reg.GetAbout()
└── components/
    └── header.html     -> reg.GetComponentsHeader()

# Generate methods
go generate ./...

The generation process creates a templator_methods.go file containing type-safe method handlers for each template. For example:

// Code generated by go generate; DO NOT EDIT.
func (r *Registry[T]) GetHome() (*Handler[T], error) {
    return r.Get("home")
}

func (r *Registry[T]) GetAbout() (*Handler[T], error) {
    return r.Get("about")
}

This file is automatically generated and should not be manually edited.

Configuration

reg, err := templator.NewRegistry[HomeData](
    fs,
    // Custom template directory
    templator.WithTemplatesPath[HomeData]("views"),
    // Enable field validation
    templator.WithFieldValidation(HomeData{}),
)
Development Requirements
  • Go 1.21 or higher

License

MIT

Documentation

Overview

Package templator provides a type-safe template rendering system for Go applications. It offers a simple and concurrent-safe way to manage HTML templates with compile-time type checking for template data.

Example
package main

import (
	"bytes"
	"context"
	"fmt"
	"testing/fstest"

	"github.com/alesr/templator"
)

func main() {
	// Create template files in memory
	fs := fstest.MapFS{
		"templates/home.html": &fstest.MapFile{
			Data: []byte(`<h1>{{.Title}}</h1><p>{{.Content}}</p>`),
		},
		"templates/team.html": &fstest.MapFile{
			Data: []byte(`<h2>{{.Title}}</h2><p>{{.Content}}</p>`),
		},
	}

	// Create a new registry
	reg, _ := templator.NewRegistry[templator.TestData](fs)

	// Get and execute home template
	home, _ := reg.Get("home")
	var homeOutput bytes.Buffer
	home.Execute(context.Background(), &homeOutput, templator.TestData{
		Title:   "Welcome",
		Content: "Hello, World!",
	})

	// Get and execute team template
	team, _ := reg.Get("team")
	var teamOutput bytes.Buffer
	team.Execute(context.Background(), &teamOutput, templator.TestData{
		Title:   "Engineering",
		Content: "Building amazing things",
	})

	// Print the outputs
	fmt.Printf("Home template output:\n%s\n\n", homeOutput.String())
	fmt.Printf("Team template output:\n%s\n", teamOutput.String())

}
Output:

Home template output:
<h1>Welcome</h1><p>Hello, World!</p>

Team template output:
<h2>Engineering</h2><p>Building amazing things</p>

Index

Examples

Constants

View Source
const (
	// DefaultTemplateDir is the default directory where templates are stored.
	DefaultTemplateDir = "templates"
	// DefaultTemplateExt is the default file extension for templates.
	DefaultTemplateExt = "html"

	// ExtensionHTML defines the standard HTML template file extension.
	ExtensionHTML Extension = ".html"
)

Variables

This section is empty.

Functions

This section is empty.

Types

type ErrTemplateExecution

type ErrTemplateExecution struct {
	Name string
	Err  error
}

ErrTemplateExecution is returned when a template fails to execute.

func (ErrTemplateExecution) Error

func (e ErrTemplateExecution) Error() string

func (ErrTemplateExecution) Unwrap

func (e ErrTemplateExecution) Unwrap() error

type ErrTemplateNotFound

type ErrTemplateNotFound struct {
	Name string
}

ErrTemplateNotFound is returned when a template cannot be found.

func (ErrTemplateNotFound) Error

func (e ErrTemplateNotFound) Error() string

type Extension

type Extension string

Extension represents a template file extension type.

type Handler

type Handler[T any] struct {
	// contains filtered or unexported fields
}

Handler manages a specific template instance with type-safe data handling. It provides methods for template execution and customization.

func (*Handler[T]) Execute

func (h *Handler[T]) Execute(ctx context.Context, w io.Writer, data T) error

Execute renders the template with the provided data and writes the output to the writer. The context parameter can be used for cancellation and deadline control.

func (*Handler[T]) WithFuncs

func (h *Handler[T]) WithFuncs(funcMap template.FuncMap) *Handler[T]

WithFuncs adds custom template functions to the handler. Returns the handler for method chaining.

type Option

type Option[T any] func(*Registry[T])

Option configures a Registry instance.

func WithFieldValidation

func WithFieldValidation[T any](model T) Option[T]

WithFieldValidation enables template field validation against the provided model

func WithTemplateFuncs

func WithTemplateFuncs[T any](funcMap template.FuncMap) Option[T]

func WithTemplatesPath

func WithTemplatesPath[T any](path string) Option[T]

WithTemplatesPath returns an Option that sets a custom template directory path. If an empty path is provided, the default path will be used.

type Registry

type Registry[T any] struct {
	// contains filtered or unexported fields
}

Registry manages template handlers in a concurrent-safe manner.

func NewRegistry

func NewRegistry[T any](fsys fs.FS, opts ...Option[T]) (*Registry[T], error)

NewRegistry creates a new template registry with the provided filesystem and options. It accepts a filesystem interface and variadic options for customization.

func (*Registry[T]) Get

func (r *Registry[T]) Get(name string) (*Handler[T], error)

Get retrieves or creates a type-safe handler for a specific template. It automatically appends the .html extension to the template name. Returns an error if the template cannot be parsed.

type ValidationError

type ValidationError struct {
	TemplateName string
	FieldPath    string
	Err          error
}

func (*ValidationError) Error

func (e *ValidationError) Error() string

Directories

Path Synopsis
cmd
generate
Package main provides a code generator for creating type-safe template handler methods.
Package main provides a code generator for creating type-safe template handler methods.

Jump to

Keyboard shortcuts

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