package generatedtext import ( "fmt" "gitea.maximumdirect.net/eric/weatherreporter/internal/briefing" "gitea.maximumdirect.net/eric/weatherreporter/internal/facts" "gitea.maximumdirect.net/eric/weatherreporter/internal/module" "gitea.maximumdirect.net/eric/weatherreporter/internal/report" "gitea.maximumdirect.net/eric/weatherreporter/internal/reporttemplate" ) const ( schemaIDHourly = "hourly" schemaIDTomorrow = "tomorrow" templateIDHourly = "hourly" templateIDTomorrow = "tomorrow" ) type validator func([]byte) (any, []byte, error) type renderContextBuilder func(report.ID, string, briefing.Metadata, module.Snapshot, facts.CollectedFacts, facts.DerivedFacts, any) (any, error) type catalogEntry struct { schemaID string templateID string validate validator buildRenderContext renderContextBuilder } type Handler struct { reportID report.ID schemaID string templateID string validate validator buildRenderContext renderContextBuilder } var catalog = []catalogEntry{ { schemaID: schemaIDHourly, templateID: templateIDHourly, validate: validateHourly, buildRenderContext: buildHourlyContext, }, { schemaID: schemaIDTomorrow, templateID: templateIDTomorrow, validate: validateTomorrow, buildRenderContext: buildTomorrowContext, }, } func LookupDefinition(definition report.Definition) (Handler, error) { if definition.GenerationMode != report.GenerationModeGeneratedTextTemplate { return Handler{}, fmt.Errorf("report %q uses generation mode %q, not %q", definition.ID, definition.GenerationMode, report.GenerationModeGeneratedTextTemplate) } var schemaKnown, templateKnown bool for _, entry := range catalog { if entry.schemaID == definition.GeneratedTextSchemaID { schemaKnown = true } if entry.templateID == definition.TemplateID { templateKnown = true } if entry.schemaID == definition.GeneratedTextSchemaID && entry.templateID == definition.TemplateID { return Handler{ reportID: definition.ID, schemaID: entry.schemaID, templateID: entry.templateID, validate: entry.validate, buildRenderContext: entry.buildRenderContext, }, nil } } if !schemaKnown { return Handler{}, fmt.Errorf("generated text schema %q is not supported for report %q", definition.GeneratedTextSchemaID, definition.ID) } if !templateKnown { return Handler{}, fmt.Errorf("report template %q is not supported for report %q", definition.TemplateID, definition.ID) } return Handler{}, fmt.Errorf("generated text schema %q and report template %q are not supported together for report %q", definition.GeneratedTextSchemaID, definition.TemplateID, definition.ID) } func (h Handler) SchemaID() string { return h.schemaID } func (h Handler) TemplateID() string { return h.templateID } func (h Handler) Schema() ([]byte, error) { data, err := reporttemplate.Schema(h.schemaID) if err != nil { return nil, fmt.Errorf("load generated text schema %q for report %q: %w", h.schemaID, h.reportID, err) } return data, nil } func (h Handler) Template() (string, error) { source, err := reporttemplate.Template(h.templateID) if err != nil { return "", fmt.Errorf("load report template %q for report %q: %w", h.templateID, h.reportID, err) } return source, nil } func (h Handler) Validate(data []byte) (any, []byte, error) { if h.validate == nil { return nil, nil, fmt.Errorf("generated text validator is not registered for schema %q on report %q", h.schemaID, h.reportID) } return h.validate(data) } func (h Handler) BuildRenderContext(metadata briefing.Metadata, snapshot module.Snapshot, collected facts.CollectedFacts, derived facts.DerivedFacts, generated any) (any, error) { if h.buildRenderContext == nil { return nil, fmt.Errorf("render-context builder is not registered for template %q on report %q", h.templateID, h.reportID) } return h.buildRenderContext(h.reportID, h.templateID, metadata, snapshot, collected, derived, generated) } func (h Handler) Render(data any) ([]byte, error) { rendered, err := reporttemplate.Render(h.templateID, data) if err != nil { return nil, fmt.Errorf("render report template %q for report %q: %w", h.templateID, h.reportID, err) } return rendered, nil } func validateHourly(data []byte) (any, []byte, error) { return ValidateHourly(data) } func validateTomorrow(data []byte) (any, []byte, error) { return ValidateTomorrow(data) } func buildHourlyContext(reportID report.ID, templateID string, metadata briefing.Metadata, snapshot module.Snapshot, collected facts.CollectedFacts, derived facts.DerivedFacts, generated any) (any, error) { hourly, ok := generated.(Hourly) if !ok { return nil, fmt.Errorf("report template %q requires hourly generated text for report %q", templateID, reportID) } return BuildHourlyRenderContext(metadata, snapshot, hourly, collected, derived) } func buildTomorrowContext(reportID report.ID, templateID string, metadata briefing.Metadata, snapshot module.Snapshot, collected facts.CollectedFacts, derived facts.DerivedFacts, generated any) (any, error) { tomorrow, ok := generated.(Tomorrow) if !ok { return nil, fmt.Errorf("report template %q requires tomorrow generated text for report %q", templateID, reportID) } return BuildTomorrowRenderContext(metadata, snapshot, tomorrow, collected, derived) }