// Package feed defines the shared item type and channel metadata that all
// three writer packages (rss20, atom10, jsonfeed) agree on. Keeping the
// shape here prevents circular imports and makes it easy to add new
// writers. See mercemay.top/src/tilstream/ for architecture notes.
package feed
import "time"
// Item is a single feed entry, normalized across RSS, Atom, and JSON Feed.
// Not every writer uses every field; e.g. RSS 2.0 ignores Updated because
// the spec has no well-supported equivalent.
type Item struct {
Title string
Link string
GUID string
Author string
Summary string
Content string
Published time.Time
Updated time.Time
Categories []string
}
// Clone returns a deep copy of i. Used by writers that want to sort or
// rewrite fields without surprising the caller.
func (i Item) Clone() Item {
out := i
if i.Categories != nil {
out.Categories = append([]string(nil), i.Categories...)
}
return out
}
// Validate returns a non-nil error if the item is missing required fields
// that every feed format needs.
func (i Item) Validate() error {
if i.Title == "" {
return ErrMissingTitle
}
if i.Link == "" {
return ErrMissingLink
}
if i.GUID == "" {
return ErrMissingGUID
}
return nil
}
// Sentinel errors returned by Validate.
var (
ErrMissingTitle = feedErr("feed: item missing Title")
ErrMissingLink = feedErr("feed: item missing Link")
ErrMissingGUID = feedErr("feed: item missing GUID")
)
type feedErr string
func (e feedErr) Error() string { return string(e) }
// Metadata holds channel-level information shared across formats. Writers
// that need format-specific fields (e.g. Atom subtitle) embed this type.
type Metadata struct {
Title string
Link string
Description string
Language string
Author string
Generator string
Copyright string
Updated time.Time
}
// WithGenerator returns a copy of m with Generator set to g. Useful in
// writer helpers that want to stamp their identity without mutating.
func (m Metadata) WithGenerator(g string) Metadata {
m.Generator = g
return m
}
// Latest returns the most recent Published time across items, or the zero
// Time when items is empty.
func Latest(items []Item) time.Time {
var t time.Time
for _, it := range items {
if it.Published.After(t) {
t = it.Published
}
}
return t
}