Architecture¶
This document describes the internal architecture of ZipReport Go.
Overview¶
┌─────────────────────────────────────────────────────────────────┐
│ pkg/api │
│ ZipReport │ MIMEReport │
└───────────────────────────────────────────────────────────────────┘
│
┌───────────────────────┼───────────────────────┐
│ │ │
▼ ▼ ▼
┌───────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ pkg/report │ │ pkg/template │ │ pkg/processor │
│ ReportFile │ │ MiyaRender │ │ ServerBackend │
│ ReportJob │ │ Filters │ │ MIMEProcessor │
└───────────────┘ └─────────────────┘ └─────────────────┘
│ │
└───────────┬───────────┘
│
▼
┌───────────────┐
│ pkg/fileutils │
│ ZipFs │
│ DiskFs │
└───────────────┘
Package Descriptions¶
pkg/api¶
High-level API providing simple interfaces for common use cases.
- ZipReport - PDF generation via zipreport-server
- MIMEReport - MIME email generation
- BaseReport - Shared functionality
The API layer orchestrates template rendering and processing:
func (b *BaseReport) Render(job, data, wrapper) *JobResult {
// 1. Create renderer
renderer := template.NewMiyaRender(job.GetReport(), opts...)
// 2. Render template
renderer.Render(data)
// 3. Process with backend
return b.processor.Process(job)
}
pkg/report¶
Core types for report definition and configuration.
ReportFile¶
Represents a loaded report template. Wraps a filesystem (ZipFs or DiskFs) with a parsed manifest.
type ReportFile struct {
fs FsInterface // ZipFs or DiskFs
manifest *Manifest // Parsed manifest.json
}
Key methods:
Load(path)- Auto-detect and load from file or directoryLoadFile(filename)- Load from .zpt fileLoadDir(dirPath)- Load from directoryGetBytes(name)- Read file contentsAdd(name, data, overwrite)- Add/update fileExists(name)- Check file existence
ReportJob¶
Configuration for a rendering job:
type ReportJob struct {
report *ReportFile
pageSize PageSize // A4, Letter, etc.
margins MarginStyle // standard, none, minimum
customMargins *Margins // Custom mm values
landscape bool
settlingTime int // ms
jsTimeout int // seconds
jobTimeout int // seconds
useJSEvent bool
}
Builder¶
Functions for creating .zpt files:
Build(srcDir, destFile, opts)- Build and save to fileBuildZipFs(srcDir, opts)- Build to in-memory ZipFs
pkg/template¶
Template rendering using the miya engine.
MiyaRender¶
Wraps miya for report rendering:
type MiyaRender struct {
report *report.ReportFile
env *miya.Environment
options *RenderOptions
wrapper EnvironmentWrapper
}
Features:
- Jinja2-compatible syntax via miya
- Custom file loader for ReportFile
- Built-in image filters (png, gif, jpg, svg)
- Parameter validation
- Default data from data.json
EnvironmentWrapper¶
Interface for customizing the miya environment:
Implementations:
FuncEnvironmentWrapper- Function-based wrapperChainedWrapper- Combines multiple wrappers
Filters¶
Built-in filters for dynamic content:
| Filter | Description |
|---|---|
png |
Generate PNG image |
gif |
Generate GIF image |
jpg/jpeg |
Generate JPEG image |
svg |
Generate SVG image |
json |
Convert to JSON string |
Image filters:
- Call the generator function with data
- Add generated bytes to the report
- Return an
<img>tag as safe HTML
pkg/processor¶
Backend implementations for different output types.
Interface¶
type RenderBackend interface {
Render(zptData []byte, options map[string]interface{}) ([]byte, error)
}
type Processor interface {
Process(job *report.ReportJob) *report.JobResult
}
ServerBackend¶
Renders PDFs via zipreport-server HTTP API:
- Export ReportFile to ZIP bytes
- Build multipart form with ZIP and options
- POST to
/v2/render - Return PDF bytes
Configuration options:
- API version (default: 2)
- Timeout (default: 5 minutes)
- SSL verification
- Custom HTTP client
MIMEProcessor¶
Generates MIME messages for email:
- Render template to HTML
- Parse HTML for images
- Embed images as MIME parts
- Build multipart/related message
pkg/fileutils¶
File system abstractions for uniform file access.
Interface¶
type FsInterface interface {
Get(name string) (io.ReadCloser, error)
GetBytes(name string) ([]byte, error)
Add(name string, content []byte, overwrite bool) error
Mkdir(name string) error
Exists(name string) bool
IsDir(name string) bool
List(path string) ([]string, error)
ListFiles(path string) ([]string, error)
ListDirs(path string) ([]string, error)
Remove(name string) error
}
ZipFs¶
In-memory ZIP filesystem:
NewZipFs()- Create emptyNewZipFsFromFile(filename)- Load from fileNewZipFsFromBytes(data)- Load from bytesSave()- Export to bytes
Used for .zpt files and report export.
DiskFs¶
Read-only disk filesystem:
NewDiskFs(basePath)- Create from directoryBasePath()- Get base directory
Used for directory-based templates during development.
PathCache¶
Trie-based structure for efficient path lookups:
type PathCache struct {
mu sync.RWMutex
root *pathNode
}
type pathNode struct {
children map[string]*pathNode
isFile bool
isDir bool
}
Used internally by ZipFs for fast file/directory existence checks.
Data Flow¶
PDF Generation¶
1. Load Template
report.Load("template.zpt")
↓
Creates ReportFile with ZipFs
2. Create Job
api.NewZipReport(url, apiKey)
client.CreateJob(zpt)
↓
Creates ReportJob with settings
3. Render Template
template.NewMiyaRender(zpt)
renderer.Render(data)
↓
Executes template, writes report.html
4. Process
processor.ZipReportProcessor.Process(job)
↓
Exports to ZIP, sends to server
5. Return Result
JobResult{Report: pdfBytes, Success: true}
MIME Generation¶
1. Load & Render Template
(same as PDF steps 1-3)
2. Process with MIME
processor.MIMEProcessor.Process(job)
↓
- Parse HTML
- Find images
- Embed as MIME parts
- Build message
3. Return Result
JobResult{Report: mimeBytes, Success: true}
Design Decisions¶
File System Abstraction¶
Both ZipFs and DiskFs implement the same interface, allowing:
- Transparent handling of .zpt files and directories
- Easy development with directories, deployment with .zpt
- Testability with in-memory filesystems
Separation of Concerns¶
- api - User-facing, simple interface
- report - Data structures, no I/O
- template - Rendering logic
- processor - Output generation
- fileutils - Storage abstraction
Template Engine Choice¶
Uses miya for Jinja2 compatibility:
- Familiar syntax for Python users
- Full template inheritance support
- Extensible filter system
- Good performance
Backend Abstraction¶
The RenderBackend interface allows:
- Easy testing with mocks
- Future local rendering support
- Custom backends for special cases
Extension Points¶
Custom Filters¶
wrapper := template.FuncEnvironmentWrapper(func(env *miya.Environment) {
env.AddFilter("myfilter", myFilterFunc)
})
Custom Backends¶
type MyBackend struct{}
func (b *MyBackend) Render(zpt []byte, opts map[string]interface{}) ([]byte, error) {
// Custom rendering logic
}
client := api.NewZipReportWithBackend(&MyBackend{})
Custom File Systems¶
type MyFs struct {
// Custom storage
}
// Implement all FsInterface methods:
func (f *MyFs) Get(name string) (io.ReadCloser, error) { ... }
func (f *MyFs) GetBytes(name string) ([]byte, error) { ... }
func (f *MyFs) Add(name string, content []byte, overwrite bool) error { ... }
func (f *MyFs) Mkdir(name string) error { ... }
func (f *MyFs) Exists(name string) bool { ... }
func (f *MyFs) IsDir(name string) bool { ... }
func (f *MyFs) List(path string) ([]string, error) { ... }
func (f *MyFs) ListFiles(path string) ([]string, error) { ... }
func (f *MyFs) ListDirs(path string) ([]string, error) { ... }
func (f *MyFs) Remove(name string) error { ... }