From f4dfd6d0606fdac70d51935db3175d790225c112 Mon Sep 17 00:00:00 2001 From: Jason Streifling Date: Sat, 19 Oct 2024 12:28:09 +0200 Subject: [PATCH] Added more error handling and necessary functions --- README.md | 6 ++--- atom.go | 31 +++++++++-------------- category.go | 42 ++++++++++++++++++++++++++----- commonAttributes.go | 22 +++++++++++++---- content.go | 2 +- date.go | 2 +- entry.go | 47 +++++++++++++++++++++++++++-------- extensionElement.go | 17 ++++++++++--- feed.go | 57 ++++++++++++++++++++++++++++++++++--------- generator.go | 22 ++++++++++++++--- icon.go | 4 +-- id.go | 6 ++--- inlineOtherContent.go | 17 +++++++------ link.go | 44 ++++++++++++++++++++++++--------- logo.go | 6 ++--- outOfLineContent.go | 19 ++++++++------- person.go | 36 +++++++++++++++++++++------ plainText.go | 14 ++++++++++- source.go | 5 ++++ text.go | 12 +++------ xhtmlDiv.go | 11 ++++++--- xhtmlText.go | 17 ++++++++++++- 22 files changed, 317 insertions(+), 122 deletions(-) diff --git a/README.md b/README.md index 3d980e2..b92a4e0 100644 --- a/README.md +++ b/README.md @@ -14,9 +14,9 @@ go get git.streifling.com/jason/atom@latest ## Usage -This library provides easy to use functions to safely create and extend elements -and attributes of an Atom feed. The intended way of using it entails using these -functions. +This library provides convenient functions to safely create and extend elements +and attributes of an Atom feed. This is because it can be hard to know all +pitfalls of RFC4287. The intended way of using atom is with these functions. ```go package main diff --git a/atom.go b/atom.go index 6c855d1..a6a9fe1 100644 --- a/atom.go +++ b/atom.go @@ -10,18 +10,11 @@ import ( "golang.org/x/text/language" ) -type ( - EmailAddress string - LanguageTag string - MediaType string - IRI string -) - // isValidIRI checks whether an IRI is valid or not. It returns a bool. // https://www.w3.org/2011/04/XMLSchema/TypeLibrary-IRI-RFC3987.xsd -func isValidIRI(iri IRI) bool { +func isValidIRI(iri string) bool { pattern := `((([A-Za-z])[A-Za-z0-9+\-\.]*):((//(((([A-Za-z0-9\-\._~ -퟿豈-﷏ﷰ-￯𐀀-🿽𠀀-𯿽𰀀-𿿽񀀀-񏿽񐀀-񟿽񠀀-񯿽񰀀-񿿽򀀀-򏿽򐀀-򟿽򠀀-򯿽򰀀-򿿽󀀀-󏿽󐀀-󟿽󡀀-󯿽!$&'()*+,;=:]|(%[0-9A-Fa-f][0-9A-Fa-f]))*@))?((\[((((([0-9A-Fa-f]{0,4}:)){6}(([0-9A-Fa-f]{0,4}:[0-9A-Fa-f]{0,4})|(([0-9]|([1-9][0-9])|(1([0-9]){2})|(2[0-4][0-9])|(25[0-5]))\.([0-9]|([1-9][0-9])|(1([0-9]){2})|(2[0-4][0-9])|(25[0-5]))\.([0-9]|([1-9][0-9])|(1([0-9]){2})|(2[0-4][0-9])|(25[0-5]))\.([0-9]|([1-9][0-9])|(1([0-9]){2})|(2[0-4][0-9])|(25[0-5])))))|(::(([0-9A-Fa-f]{0,4}:)){5}(([0-9A-Fa-f]{0,4}:[0-9A-Fa-f]{0,4})|(([0-9]|([1-9][0-9])|(1([0-9]){2})|(2[0-4][0-9])|(25[0-5]))\.([0-9]|([1-9][0-9])|(1([0-9]){2})|(2[0-4][0-9])|(25[0-5]))\.([0-9]|([1-9][0-9])|(1([0-9]){2})|(2[0-4][0-9])|(25[0-5]))\.([0-9]|([1-9][0-9])|(1([0-9]){2})|(2[0-4][0-9])|(25[0-5])))))|(([0-9A-Fa-f]{0,4})?::(([0-9A-Fa-f]{0,4}:)){4}(([0-9A-Fa-f]{0,4}:[0-9A-Fa-f]{0,4})|(([0-9]|([1-9][0-9])|(1([0-9]){2})|(2[0-4][0-9])|(25[0-5]))\.([0-9]|([1-9][0-9])|(1([0-9]){2})|(2[0-4][0-9])|(25[0-5]))\.([0-9]|([1-9][0-9])|(1([0-9]){2})|(2[0-4][0-9])|(25[0-5]))\.([0-9]|([1-9][0-9])|(1([0-9]){2})|(2[0-4][0-9])|(25[0-5])))))|((((([0-9A-Fa-f]{0,4}:))?[0-9A-Fa-f]{0,4}))?::(([0-9A-Fa-f]{0,4}:)){3}(([0-9A-Fa-f]{0,4}:[0-9A-Fa-f]{0,4})|(([0-9]|([1-9][0-9])|(1([0-9]){2})|(2[0-4][0-9])|(25[0-5]))\.([0-9]|([1-9][0-9])|(1([0-9]){2})|(2[0-4][0-9])|(25[0-5]))\.([0-9]|([1-9][0-9])|(1([0-9]){2})|(2[0-4][0-9])|(25[0-5]))\.([0-9]|([1-9][0-9])|(1([0-9]){2})|(2[0-4][0-9])|(25[0-5])))))|((((([0-9A-Fa-f]{0,4}:)){0,2}[0-9A-Fa-f]{0,4}))?::(([0-9A-Fa-f]{0,4}:)){2}(([0-9A-Fa-f]{0,4}:[0-9A-Fa-f]{0,4})|(([0-9]|([1-9][0-9])|(1([0-9]){2})|(2[0-4][0-9])|(25[0-5]))\.([0-9]|([1-9][0-9])|(1([0-9]){2})|(2[0-4][0-9])|(25[0-5]))\.([0-9]|([1-9][0-9])|(1([0-9]){2})|(2[0-4][0-9])|(25[0-5]))\.([0-9]|([1-9][0-9])|(1([0-9]){2})|(2[0-4][0-9])|(25[0-5])))))|((((([0-9A-Fa-f]{0,4}:)){0,3}[0-9A-Fa-f]{0,4}))?::[0-9A-Fa-f]{0,4}:(([0-9A-Fa-f]{0,4}:[0-9A-Fa-f]{0,4})|(([0-9]|([1-9][0-9])|(1([0-9]){2})|(2[0-4][0-9])|(25[0-5]))\.([0-9]|([1-9][0-9])|(1([0-9]){2})|(2[0-4][0-9])|(25[0-5]))\.([0-9]|([1-9][0-9])|(1([0-9]){2})|(2[0-4][0-9])|(25[0-5]))\.([0-9]|([1-9][0-9])|(1([0-9]){2})|(2[0-4][0-9])|(25[0-5])))))|((((([0-9A-Fa-f]{0,4}:)){0,4}[0-9A-Fa-f]{0,4}))?::(([0-9A-Fa-f]{0,4}:[0-9A-Fa-f]{0,4})|(([0-9]|([1-9][0-9])|(1([0-9]){2})|(2[0-4][0-9])|(25[0-5]))\.([0-9]|([1-9][0-9])|(1([0-9]){2})|(2[0-4][0-9])|(25[0-5]))\.([0-9]|([1-9][0-9])|(1([0-9]){2})|(2[0-4][0-9])|(25[0-5]))\.([0-9]|([1-9][0-9])|(1([0-9]){2})|(2[0-4][0-9])|(25[0-5])))))|((((([0-9A-Fa-f]{0,4}:)){0,5}[0-9A-Fa-f]{0,4}))?::[0-9A-Fa-f]{0,4})|((((([0-9A-Fa-f]{0,4}:)){0,6}[0-9A-Fa-f]{0,4}))?::))|(v[0-9A-Fa-f]+\.[A-Za-z0-9\-\._~!$&'()*+,;=:]+))\])|(([0-9]|([1-9][0-9])|(1([0-9]){2})|(2[0-4][0-9])|(25[0-5]))\.([0-9]|([1-9][0-9])|(1([0-9]){2})|(2[0-4][0-9])|(25[0-5]))\.([0-9]|([1-9][0-9])|(1([0-9]){2})|(2[0-4][0-9])|(25[0-5]))\.([0-9]|([1-9][0-9])|(1([0-9]){2})|(2[0-4][0-9])|(25[0-5])))|(([A-Za-z0-9\-\._~ -퟿豈-﷏ﷰ-￯𐀀-🿽𠀀-𯿽𰀀-𿿽񀀀-񏿽񐀀-񟿽񠀀-񯿽񰀀-񿿽򀀀-򏿽򐀀-򟿽򠀀-򯿽򰀀-򿿽󀀀-󏿽󐀀-󟿽󡀀-󯿽]|(%[0-9A-Fa-f][0-9A-Fa-f])|[!$&'()*+,;=]))*)((:[0-9]*))?)((/(([A-Za-z0-9\-\._~ -퟿豈-﷏ﷰ-￯𐀀-🿽𠀀-𯿽𰀀-𿿽񀀀-񏿽񐀀-񟿽񠀀-񯿽񰀀-񿿽򀀀-򏿽򐀀-򟿽򠀀-򯿽򰀀-򿿽󀀀-󏿽󐀀-󟿽󡀀-󯿽]|(%[0-9A-Fa-f][0-9A-Fa-f])|[!$&'()*+,;=:@]))*))*)|(/(((([A-Za-z0-9\-\._~ -퟿豈-﷏ﷰ-￯𐀀-🿽𠀀-𯿽𰀀-𿿽񀀀-񏿽񐀀-񟿽񠀀-񯿽񰀀-񿿽򀀀-򏿽򐀀-򟿽򠀀-򯿽򰀀-򿿽󀀀-󏿽󐀀-󟿽󡀀-󯿽]|(%[0-9A-Fa-f][0-9A-Fa-f])|[!$&'()*+,;=:@]))+((/(([A-Za-z0-9\-\._~ -퟿豈-﷏ﷰ-￯𐀀-🿽𠀀-𯿽𰀀-𿿽񀀀-񏿽񐀀-񟿽񠀀-񯿽񰀀-񿿽򀀀-򏿽򐀀-򟿽򠀀-򯿽򰀀-򿿽󀀀-󏿽󐀀-󟿽󡀀-󯿽]|(%[0-9A-Fa-f][0-9A-Fa-f])|[!$&'()*+,;=:@]))*))*))?)|((([A-Za-z0-9\-\._~ -퟿豈-﷏ﷰ-￯𐀀-🿽𠀀-𯿽𰀀-𿿽񀀀-񏿽񐀀-񟿽񠀀-񯿽񰀀-񿿽򀀀-򏿽򐀀-򟿽򠀀-򯿽򰀀-򿿽󀀀-󏿽󐀀-󟿽󡀀-󯿽]|(%[0-9A-Fa-f][0-9A-Fa-f])|[!$&'()*+,;=:@]))+((/(([A-Za-z0-9\-\._~ -퟿豈-﷏ﷰ-￯𐀀-🿽𠀀-𯿽𰀀-𿿽񀀀-񏿽񐀀-񟿽񠀀-񯿽񰀀-񿿽򀀀-򏿽򐀀-򟿽򠀀-򯿽򰀀-򿿽󀀀-󏿽󐀀-󟿽󡀀-󯿽]|(%[0-9A-Fa-f][0-9A-Fa-f])|[!$&'()*+,;=:@]))*))*)|)((\?(([A-Za-z0-9\-\._~ -퟿豈-﷏ﷰ-￯𐀀-🿽𠀀-𯿽𰀀-𿿽񀀀-񏿽񐀀-񟿽񠀀-񯿽񰀀-񿿽򀀀-򏿽򐀀-򟿽򠀀-򯿽򰀀-򿿽󀀀-󏿽󐀀-󟿽󡀀-󯿽]|(%[0-9A-Fa-f][0-9A-Fa-f])|[!$&'()*+,;=:@])|[-󰀀-󿿽􀀀-􏿽/?])*))?((#((([A-Za-z0-9\-\._~ -퟿豈-﷏ﷰ-￯𐀀-🿽𠀀-𯿽𰀀-𿿽񀀀-񏿽񐀀-񟿽񠀀-񯿽񰀀-񿿽򀀀-򏿽򐀀-򟿽򠀀-򯿽򰀀-򿿽󀀀-󏿽󐀀-󟿽󡀀-󯿽]|(%[0-9A-Fa-f][0-9A-Fa-f])|[!$&'()*+,;=:@])|/|\?))*))?)` - return regexp.MustCompile(pattern).MatchString(string(iri)) + return regexp.MustCompile(pattern).MatchString(iri) } // isCorrectlyEscaped checks whether a string is correctly escaped as per @@ -40,8 +33,8 @@ func isCorrectlyEscaped(text string) bool { // isCompositeMediaType checks whether a string is a composite media type. It // returns a bool. -func isCompositeMediaType(mediaType string) bool { - mediaType, _, err := mime.ParseMediaType(mediaType) +func isCompositeMediaType(m string) bool { + mediaType, _, err := mime.ParseMediaType(m) if err != nil { return false } @@ -51,8 +44,8 @@ func isCompositeMediaType(mediaType string) bool { // isXMLMediaType checks whether a string is an xml media type. It returns a // bool. -func isXMLMediaType(mediaType string) bool { - mediaType, _, err := mime.ParseMediaType(mediaType) +func isXMLMediaType(m string) bool { + mediaType, _, err := mime.ParseMediaType(m) if err != nil { return false } @@ -62,8 +55,8 @@ func isXMLMediaType(mediaType string) bool { // isValidMediaType checks whether a string is a valid media type. It returns a // bool. -func isValidMediaType(mediaType string) bool { - mediaType, _, err := mime.ParseMediaType(mediaType) +func isValidMediaType(m string) bool { + mediaType, _, err := mime.ParseMediaType(m) if err != nil { return false } @@ -77,8 +70,8 @@ func isValidMediaType(mediaType string) bool { } // isValidLanguageTag checks whether a LanguageTag is valid. It returns a bool. -func isValidLanguageTag(tag LanguageTag) bool { - _, err := language.Parse(string(tag)) +func isValidLanguageTag(languageTag string) bool { + _, err := language.Parse(languageTag) return err == nil } @@ -89,6 +82,6 @@ func isValidAttribute(attribute string) bool { } // NewURN generates an new valid IRI based on a UUID. It returns an IRI. -func NewURN() IRI { - return IRI(fmt.Sprint("urn:uuid:", uuid.New())) +func NewURN() string { + return fmt.Sprint("urn:uuid:", uuid.New()) } diff --git a/category.go b/category.go index ecc3600..c4a1909 100644 --- a/category.go +++ b/category.go @@ -2,6 +2,7 @@ package atom import ( "encoding/xml" + "errors" "fmt" "html" ) @@ -10,18 +11,47 @@ type Category struct { XMLName xml.Name `xml:"category"` *CommonAttributes Term string `xml:"term,attr"` - Scheme IRI `xml:"scheme,attr,omitempty"` + Scheme string `xml:"scheme,attr,omitempty"` // IRI Label string `xml:"label,attr,omitempty"` } -// NewCategory creates a new Category. It returns a *Category. -func NewCategory(term string) *Category { - return &Category{Term: term} +// NewCategory creates a new Category. It returns a *Category and an error. +func NewCategory(term string) (*Category, error) { + if term == "" { + return nil, errors.New("error creating new category: term string empty") + } + + return &Category{Term: term}, nil } -// SetLabel sets the label of the Category. -func (c *Category) SetLabel(label string) { +// SetTerm sets the Term attribute of the Category. It returns an error. +func (c *Category) SetTerm(t string) error { + if t == "" { + return errors.New("error setting term of category: t string empty") + } + + c.Term = t + return nil +} + +// SetScheme sets the Scheme attribute of the Category. It returns an error. +func (c *Category) SetScheme(s string) error { + if !isValidIRI(s) { + return fmt.Errorf("scheme %v not correctly formatted", s) + } + + c.Scheme = s + return nil +} + +// SetLabel sets the Label attribute of the Category. It returns an error. +func (c *Category) SetLabel(label string) error { + if label == "" { + return errors.New("error setting label of category: label string empty") + } + c.Label = html.UnescapeString(label) + return nil } // Check checks the Category for incompatibilities with RFC4287. It returns an diff --git a/commonAttributes.go b/commonAttributes.go index 33d0c45..093d834 100644 --- a/commonAttributes.go +++ b/commonAttributes.go @@ -1,10 +1,13 @@ package atom -import "encoding/xml" +import ( + "encoding/xml" + "errors" +) type CommonAttributes struct { - Base IRI `xml:"base,attr,omitempty"` - Lang LanguageTag `xml:"lang,attr,omitempty"` + Base string `xml:"base,attr,omitempty"` // IRI + Lang string `xml:"lang,attr,omitempty"` // LanguageTag UndefinedAttributes []*xml.Attr `xml:",attr,omitempty"` } @@ -14,12 +17,21 @@ func NewCommonAttributes() *CommonAttributes { return new(CommonAttributes) } -// AddExtensionAttribute adds the ExtensionAttribute to the CommonAttributes. -func (c *CommonAttributes) AddExtensionAttribute(name, value string) { +// AddAttribute adds the Attribute to the CommonAttributes. It returns an error. +func (c *CommonAttributes) AddAttribute(name, value string) error { + if name == "" { + return errors.New("error adding attribute: name string empty") + } + if value == "" { + return errors.New("error adding attribute: value string empty") + } + if c.UndefinedAttributes == nil { c.UndefinedAttributes = make([]*xml.Attr, 1) c.UndefinedAttributes[0] = &xml.Attr{Name: xml.Name{Local: name}, Value: value} } else { c.UndefinedAttributes = append(c.UndefinedAttributes, &xml.Attr{Name: xml.Name{Local: name}, Value: value}) } + + return nil } diff --git a/content.go b/content.go index 94b8238..ae934f7 100644 --- a/content.go +++ b/content.go @@ -28,6 +28,6 @@ func NewContent(contentType int, mediaType string, content any) (Content, error) case 3: return newOutOfLineContent(mediaType, content) default: - return nil, fmt.Errorf("%v is not a valid text type", contentType) + return nil, fmt.Errorf("error creating new content: %v is not a valid text type", contentType) } } diff --git a/date.go b/date.go index 309b165..a587be8 100644 --- a/date.go +++ b/date.go @@ -13,7 +13,7 @@ type Date struct { // DateTime formats a time.Time to string formated as defined by RFC3339. It // returns a string. func DateTime(t time.Time) string { - return string(t.Format(time.RFC3339)) + return t.Format(time.RFC3339) } // NewDate creates a new Date. It returns a *Date. diff --git a/entry.go b/entry.go index 287e1bd..510e68d 100644 --- a/entry.go +++ b/entry.go @@ -2,6 +2,7 @@ package atom import ( "encoding/xml" + "errors" "fmt" "strings" "time" @@ -75,8 +76,12 @@ func NewEntry(title string) (*Entry, error) { }, nil } -// AddAuthor adds the Person as an author to the Entry. -func (e *Entry) AddAuthor(p *Person) { +// AddAuthor adds the Person as an author to the Entry. It returns an error. +func (e *Entry) AddAuthor(p *Person) error { + if p == nil { + return errors.New("error adding author element to entry: *Person is nil") + } + if e.Authors == nil { e.Authors = make([]*Person, 1) e.Authors[0] = p @@ -85,10 +90,15 @@ func (e *Entry) AddAuthor(p *Person) { } e.Updated.DateTime = DateTime(time.Now()) + return nil } -// AddCategory adds the Category to the Entry. -func (e *Entry) AddCategory(c *Category) { +// AddCategory adds the Category to the Entry. It returns an error. +func (e *Entry) AddCategory(c *Category) error { + if c == nil { + return errors.New("error adding category element to entry: *Category is nil") + } + if e.Categories == nil { e.Categories = make([]*Category, 1) e.Categories[0] = c @@ -97,10 +107,16 @@ func (e *Entry) AddCategory(c *Category) { } e.Updated.DateTime = DateTime(time.Now()) + return nil } -// AddContributor adds the Person as a contributor to the Entry. -func (e *Entry) AddContributor(c *Person) { +// AddContributor adds the Person as a contributor to the Entry. It returns an +// error. +func (e *Entry) AddContributor(c *Person) error { + if c == nil { + return errors.New("error adding contributor element to entry: *Person is nil") + } + if e.Contributors == nil { e.Contributors = make([]*Person, 1) e.Contributors[0] = c @@ -109,10 +125,15 @@ func (e *Entry) AddContributor(c *Person) { } e.Updated.DateTime = DateTime(time.Now()) + return nil } -// AddLink adds the Link to the Entry. -func (e *Entry) AddLink(l *Link) { +// AddLink adds the Link to the Entry. It returns an error. +func (e *Entry) AddLink(l *Link) error { + if l == nil { + return errors.New("error adding link element to entry: *Link is nil") + } + if e.Links == nil { e.Links = make([]*Link, 1) e.Links[0] = l @@ -121,10 +142,15 @@ func (e *Entry) AddLink(l *Link) { } e.Updated.DateTime = DateTime(time.Now()) + return nil } -// AddExtension adds the ExtensionElement to the Entry. -func (e *Entry) AddExtension(x *ExtensionElement) { +// AddExtension adds the ExtensionElement to the Entry. It returns an error. +func (e *Entry) AddExtension(x *ExtensionElement) error { + if x == nil { + return errors.New("error adding extension element to entry: *ExtensionElement is nil") + } + if e.Extensions == nil { e.Extensions = make([]*ExtensionElement, 1) e.Extensions[0] = x @@ -133,6 +159,7 @@ func (e *Entry) AddExtension(x *ExtensionElement) { } e.Updated.DateTime = DateTime(time.Now()) + return nil } // Check checks the Entry for incompatibilities with RFC4287. It returns an diff --git a/extensionElement.go b/extensionElement.go index ebd62d7..dd8dcc7 100644 --- a/extensionElement.go +++ b/extensionElement.go @@ -11,14 +11,25 @@ type ExtensionElement struct { } // NewExtensionElement creates a new ExtensionElement. It returns a -// *ExtensionElement. -func NewExtensionElement(name string, value any) *ExtensionElement { - return &ExtensionElement{XMLName: xml.Name{Local: name}, Value: value} +// *ExtensionElement and an error. +func NewExtensionElement(name string, value any) (*ExtensionElement, error) { + if name == "" { + return nil, errors.New("error adding extension attribute: name string empty") + } + if value == "" { + return nil, errors.New("error adding extension attribute: value string empty") + } + + return &ExtensionElement{XMLName: xml.Name{Local: name}, Value: value}, nil } // Check checks the ExtensionElement for incompatibilities with RFC4287. It // returns an error. func (e *ExtensionElement) Check() error { + if e.XMLName.Local == "" { + return errors.New("xml name of extension empty") + } + if e.Value == nil { return errors.New("value element of extension empty") } diff --git a/feed.go b/feed.go index 935a629..795d5a5 100644 --- a/feed.go +++ b/feed.go @@ -2,6 +2,7 @@ package atom import ( "encoding/xml" + "errors" "fmt" "time" ) @@ -44,8 +45,12 @@ func NewFeed(title string) (*Feed, error) { }, nil } -// AddAuthor adds the Person as an author to the Feed. -func (f *Feed) AddAuthor(p *Person) { +// AddAuthor adds the Person as an author to the Feed. It returns an error. +func (f *Feed) AddAuthor(p *Person) error { + if p == nil { + return errors.New("error adding author element to feed: *Person is nil") + } + if f.Authors == nil { f.Authors = make([]*Person, 1) f.Authors[0] = p @@ -54,10 +59,15 @@ func (f *Feed) AddAuthor(p *Person) { } f.Updated.DateTime = DateTime(time.Now()) + return nil } -// AddCategory adds the Category to the Feed. -func (f *Feed) AddCategory(c *Category) { +// AddCategory adds the Category to the Feed. It returns an error. +func (f *Feed) AddCategory(c *Category) error { + if c == nil { + return errors.New("error adding category element to feed: *Category is nil") + } + if f.Categories == nil { f.Categories = make([]*Category, 1) f.Categories[0] = c @@ -66,10 +76,16 @@ func (f *Feed) AddCategory(c *Category) { } f.Updated.DateTime = DateTime(time.Now()) + return nil } -// AddContributor adds the Person as a contributor to the Feed. -func (f *Feed) AddContributor(c *Person) { +// AddContributor adds the Person as a contributor to the Feed. It returns an +// error. +func (f *Feed) AddContributor(c *Person) error { + if c == nil { + return errors.New("error adding contributor element to feed: *Person is nil") + } + if f.Contributors == nil { f.Contributors = make([]*Person, 1) f.Contributors[0] = c @@ -78,10 +94,16 @@ func (f *Feed) AddContributor(c *Person) { } f.Updated.DateTime = DateTime(time.Now()) + return nil } -// AddLink adds the Link to the Feed. There should be one Link with Rel "self". -func (f *Feed) AddLink(l *Link) { +// AddLink adds the Link to the Feed. It returns an error. There should be one +// Link with Rel "self". +func (f *Feed) AddLink(l *Link) error { + if l == nil { + return errors.New("error adding link element to feed: *Link is nil") + } + if f.Links == nil { f.Links = make([]*Link, 1) f.Links[0] = l @@ -90,10 +112,15 @@ func (f *Feed) AddLink(l *Link) { } f.Updated.DateTime = DateTime(time.Now()) + return nil } -// AddExtension adds the Extension to the Feed. -func (f *Feed) AddExtension(e *ExtensionElement) { +// AddExtension adds the Extension to the Feed. It returns an error. +func (f *Feed) AddExtension(e *ExtensionElement) error { + if e == nil { + return errors.New("error adding extension element to feed: *ExtensionElement is nil") + } + if f.Extensions == nil { f.Extensions = make([]*ExtensionElement, 1) f.Extensions[0] = e @@ -102,10 +129,15 @@ func (f *Feed) AddExtension(e *ExtensionElement) { } f.Updated.DateTime = DateTime(time.Now()) + return nil } -// AddEntry adds the Entry to the Feed. -func (f *Feed) AddEntry(e *Entry) { +// AddEntry adds the Entry to the Feed. It returns an error. +func (f *Feed) AddEntry(e *Entry) error { + if e == nil { + return errors.New("error adding entry element to feed: *Entry is nil") + } + if f.Entries == nil { f.Entries = make([]*Entry, 1) f.Entries[0] = e @@ -114,6 +146,7 @@ func (f *Feed) AddEntry(e *Entry) { } f.Updated.DateTime = DateTime(time.Now()) + return nil } // Check checks the Feed for incompatibilities with RFC4287. It returns an diff --git a/generator.go b/generator.go index ec1c452..c29a40d 100644 --- a/generator.go +++ b/generator.go @@ -2,6 +2,7 @@ package atom import ( "encoding/xml" + "errors" "fmt" "html" ) @@ -9,14 +10,27 @@ import ( type Generator struct { XMLName xml.Name `xml:"generator"` *CommonAttributes - URI IRI `xml:"uri,attr,omitempty"` + URI string `xml:"uri,attr,omitempty"` // IRI Version string `xml:"version,attr,omitempty"` Text string `xml:",chardata"` } -// NewGenerator creates a new Generator. It returns a *Generator. -func NewGenerator(text string) *Generator { - return &Generator{Text: html.UnescapeString(text)} +// NewGenerator creates a new Generator. It returns a *Generator and an error. +func NewGenerator(text string) (*Generator, error) { + if text == "" { + return nil, errors.New("error creating new generator: text string empty") + } + + return &Generator{Text: html.UnescapeString(text)}, nil +} + +// SetURI sets the URI attribute of the Generator. It returns an error. +func (g *Generator) SetURI(uri string) error { + if !isValidIRI(uri) { + return fmt.Errorf("uri %v not correctly formatted", g) + } + + return nil } // Check checks the Generator for incompatibilities with RFC4287. It returns an diff --git a/icon.go b/icon.go index 580a091..1242105 100644 --- a/icon.go +++ b/icon.go @@ -9,11 +9,11 @@ import ( type Icon struct { XMLName xml.Name `xml:"icon"` *CommonAttributes - URI IRI `xml:",chardata"` + URI string `xml:",chardata"` // IRI } // NewIcon creates a new Icon. It returns a *Icon and an error. -func NewIcon(uri IRI) (*Icon, error) { +func NewIcon(uri string) (*Icon, error) { if !isValidIRI(uri) { return nil, fmt.Errorf("uri %v not correctly formatted", uri) } diff --git a/id.go b/id.go index 5f10ba6..18367bf 100644 --- a/id.go +++ b/id.go @@ -9,16 +9,16 @@ import ( type ID struct { XMLName xml.Name `xml:"id"` *CommonAttributes - URI IRI `xml:",chardata"` + URI string `xml:",chardata"` // IRI } // NewID creates a new ID. It returns a *ID and an error. -func NewID(uri IRI) (*ID, error) { +func NewID(uri string) (*ID, error) { if !isValidIRI(uri) { return nil, fmt.Errorf("uri %v not correctly formatted", uri) } - return &ID{URI: IRI(uri)}, nil + return &ID{URI: uri}, nil } // Check checks the ID for incompatibilities with RFC4287. It returns an error. diff --git a/inlineOtherContent.go b/inlineOtherContent.go index ffed13c..4fccdd7 100644 --- a/inlineOtherContent.go +++ b/inlineOtherContent.go @@ -9,18 +9,19 @@ import ( type InlineOtherContent struct { XMLName xml.Name `xml:"content"` *CommonAttributes - AnyElement any `xml:",chardata"` - Type MediaType `xml:"type,attr,omitempty"` + AnyElement any `xml:",chardata"` + Type string `xml:"type,attr,omitempty"` // MediaType } // newInlineOtherContent creates a new InlineOtherContent. It returns a // *InlineOtherContent and an error. func newInlineOtherContent(mediaType string, content any) (*InlineOtherContent, error) { - if mediaType, _, err := mime.ParseMediaType(mediaType); err != nil { - return nil, fmt.Errorf("media type %v incompatible with inline other content", mediaType) + if !isValidMediaType(mediaType) { + return nil, fmt.Errorf("error creating new inline other content: media type %v invalid", mediaType) } + mediaType, _, _ = mime.ParseMediaType(mediaType) - return &InlineOtherContent{Type: MediaType(mediaType), AnyElement: content}, nil + return &InlineOtherContent{Type: mediaType, AnyElement: content}, nil } // isContent checks whether the InlineOtherContent is a Content. It returns a @@ -32,15 +33,15 @@ func (i *InlineOtherContent) isContent() bool { return true } func (i *InlineOtherContent) hasSRC() bool { return false } // getType returns the Type of the InlineOtherContent as a string. -func (i *InlineOtherContent) getType() string { return string(i.Type) } +func (i *InlineOtherContent) getType() string { return i.Type } // Check checks the InlineOtherContent for incompatibilities with RFC4287. It // returns an error. func (i *InlineOtherContent) Check() error { mediaType := i.getType() - if mediaType, _, err := mime.ParseMediaType(mediaType); err != nil { - return fmt.Errorf("type attribute %v incompatible with inline other content", mediaType) + if !isValidMediaType(mediaType) { + return fmt.Errorf("type attribute of inline other content %v invalid media type", i) } if isCompositeMediaType(mediaType) { diff --git a/link.go b/link.go index 7a5caeb..0688d69 100644 --- a/link.go +++ b/link.go @@ -9,17 +9,39 @@ import ( type Link struct { XMLName xml.Name `xml:"link"` *CommonAttributes - Title string `xml:"title,attr,omitempty"` - Href IRI `xml:"href,attr"` - Rel string `xml:"rel,attr,omitempty"` - Type MediaType `xml:"type,attr,omitempty"` - HrefLang LanguageTag `xml:"hreflang,attr,omitempty"` - Length uint `xml:"length,attr,omitempty"` + Title string `xml:"title,attr,omitempty"` + Href string `xml:"href,attr"` // IRI + Rel string `xml:"rel,attr,omitempty"` + Type string `xml:"type,attr,omitempty"` // MediaType + HrefLang string `xml:"hreflang,attr,omitempty"` // LanguageTag + Length uint `xml:"length,attr,omitempty"` } -// NewLink creates a new Link. It returns a *Link. -func NewLink(href string) *Link { - return &Link{Href: IRI(href)} +// NewLink creates a new Link. It returns a *Link and an error. +func NewLink(href string) (*Link, error) { + if !isValidIRI(href) { + return nil, fmt.Errorf("href %v not correctly formatted", href) + } + + return &Link{Href: href}, nil +} + +// SetType sets the Type attribute of the Link. It returns an error. +func (l *Link) SetType(t string) error { + if !isValidMediaType(t) { + return fmt.Errorf("type %v invalid media type", l) + } + + return nil +} + +// SetHrefLang sets the HrefLang attribute of the Link. It returns an error. +func (l *Link) SetHrefLang(h string) error { + if !isValidLanguageTag(h) { + return fmt.Errorf("hreflang %v invalid language tag", l) + } + + return nil } // Check checks the Link for incompatibilities with RFC4287. It returns an @@ -34,13 +56,13 @@ func (l *Link) Check() error { } if l.Rel != "" { - if strings.Contains(l.Rel, ":") && !isValidIRI(IRI(l.Rel)) { + if strings.Contains(l.Rel, ":") && !isValidIRI(l.Rel) { return fmt.Errorf("rel attribute of link %v not correctly formatted", l) } } if l.Type != "" { - if !isValidMediaType(string(l.Type)) { + if !isValidMediaType(l.Type) { return fmt.Errorf("type attribute of link %v invalid media type", l) } } diff --git a/logo.go b/logo.go index eac153a..62dbe07 100644 --- a/logo.go +++ b/logo.go @@ -8,11 +8,11 @@ import ( type Logo struct { XMLName xml.Name `xml:"logo"` *CommonAttributes - URI IRI `xml:",chardata"` + URI string `xml:",chardata"` // IRI } -// NewLogo creates a new Logo. It returns a *Logo. -func NewLogo(uri IRI) (*Logo, error) { +// NewLogo creates a new Logo. It returns a *Logo and an error. +func NewLogo(uri string) (*Logo, error) { if !isValidIRI(uri) { return nil, fmt.Errorf("uri %v not correctly formatted", uri) } diff --git a/outOfLineContent.go b/outOfLineContent.go index c3c4e29..c6dc931 100644 --- a/outOfLineContent.go +++ b/outOfLineContent.go @@ -9,18 +9,19 @@ import ( type OutOfLineContent struct { XMLName xml.Name `xml:"content"` *CommonAttributes - Type MediaType `xml:"type,attr,omitempty"` - SRC IRI `xml:"src,attr"` + Type string `xml:"type,attr,omitempty"` // MediaType + SRC string `xml:"src,attr"` // IRI } // newOutOfLineContent creates a new OutOfLineContent. It returns a // *OutOfLineContent and an error. func newOutOfLineContent(mediaType string, content any) (*OutOfLineContent, error) { - if mediaType, _, err := mime.ParseMediaType(mediaType); err != nil { - return nil, fmt.Errorf("media type %v incompatible with out of line content", mediaType) + if !isValidMediaType(mediaType) { + return nil, fmt.Errorf("error creating new out of line content: media type %v invalid", mediaType) } + mediaType, _, _ = mime.ParseMediaType(mediaType) - iri, ok := content.(IRI) + iri, ok := content.(string) if !ok { return nil, fmt.Errorf("content type %T incompatible with out of line content", content) } @@ -29,7 +30,7 @@ func newOutOfLineContent(mediaType string, content any) (*OutOfLineContent, erro return nil, fmt.Errorf("content %v not a valid uri", iri) } - return &OutOfLineContent{Type: MediaType(mediaType), SRC: iri}, nil + return &OutOfLineContent{Type: mediaType, SRC: iri}, nil } // isContent checks whether the OutOfLineContent is a Content. It returns a @@ -41,15 +42,15 @@ func (o *OutOfLineContent) isContent() bool { return true } func (o *OutOfLineContent) hasSRC() bool { return true } // getType returns the Type of the OutOfLineContent as a string. -func (o *OutOfLineContent) getType() string { return string(o.Type) } +func (o *OutOfLineContent) getType() string { return o.Type } // Check checks the OutOfLineContent for incompatibilities with RFC4287. It // returns an error. func (o *OutOfLineContent) Check() error { mediaType := o.getType() - if mediaType, _, err := mime.ParseMediaType(mediaType); err != nil { - return fmt.Errorf("type attribute %v incompatible with out of line content", mediaType) + if !isValidMediaType(mediaType) { + return fmt.Errorf("type attribute of out of line content %v invalid media type", o) } if isCompositeMediaType(mediaType) { diff --git a/person.go b/person.go index 5c2c6a9..899faf6 100644 --- a/person.go +++ b/person.go @@ -1,6 +1,7 @@ package atom import ( + "errors" "fmt" "net/mail" ) @@ -8,24 +9,43 @@ import ( type Person struct { *CommonAttributes Name string `xml:"name"` - URI IRI `xml:"uri,omitempty"` - Email EmailAddress `xml:"email,omitempty"` + URI string `xml:"uri,omitempty"` // IRI + Email string `xml:"email,omitempty"` // EmailAddress Extensions []*ExtensionElement `xml:",any,omitempty"` } -// NewPerson creates a new Person. It returns a *Person. -func NewPerson(name string) *Person { - return &Person{Name: name} +// NewPerson creates a new Person. It returns a *Person and an error. +func NewPerson(name string) (*Person, error) { + if name == "" { + return nil, errors.New("error creating new person: name string empty") + } + + return &Person{Name: name}, nil } -// AddExtension adds the Extension to the Person. -func (p *Person) AddExtension(e *ExtensionElement) { +// SetURI sets the URI element of the Person. It returns an error. +func (l *Link) SetURI(uri string) error { + if !isValidIRI(uri) { + return fmt.Errorf("uri %v not correctly formatted", uri) + } + + return nil +} + +// AddExtension adds the Extension to the Person. It returns an error. +func (p *Person) AddExtension(e *ExtensionElement) error { + if e == nil { + return errors.New("error adding extension element to person: *ExtensionElement is nil") + } + if p.Extensions == nil { p.Extensions = make([]*ExtensionElement, 1) p.Extensions[0] = e } else { p.Extensions = append(p.Extensions, e) } + + return nil } // Check checks the Person for incompatibilities with RFC4287. It returns an @@ -42,7 +62,7 @@ func (p *Person) Check() error { } if p.Email != "" { - if _, err := mail.ParseAddress(string(p.Email)); err != nil { + if _, err := mail.ParseAddress(p.Email); err != nil { return fmt.Errorf("email element of person %v not correctly formatted", p) } } diff --git a/plainText.go b/plainText.go index 3e1243d..a24d4ce 100644 --- a/plainText.go +++ b/plainText.go @@ -1,6 +1,9 @@ package atom -import "fmt" +import ( + "errors" + "fmt" +) type PlainText struct { *CommonAttributes @@ -11,6 +14,15 @@ type PlainText struct { // isText checks whether the PlainText is a Text. It returns a bool. func (p *PlainText) isText() bool { return true } +// newPlainText creates a new PlainText. It returns a *PlainText and an error. +func newPlainText(textType, content string) (*PlainText, error) { + if content == "" { + return nil, errors.New("error creating new plain text: content string empty") + } + + return &PlainText{Type: textType, Text: content}, nil +} + // Check checks the PlainText for incompatibilities with RFC4287. It returns an // error. func (p *PlainText) Check() error { diff --git a/source.go b/source.go index 3057599..f30981c 100644 --- a/source.go +++ b/source.go @@ -23,6 +23,11 @@ type Source struct { Extensions []*ExtensionElement `xml:",any,omitempty"` } +// NewSource creates a new Source. It returns a *Source. +func NewSource() *Source { + return new(Source) +} + // Check checks the Source for incompatibilities with RFC4287. It returns an // error. func (s *Source) Check() error { diff --git a/text.go b/text.go index d69f1af..a174d42 100644 --- a/text.go +++ b/text.go @@ -14,17 +14,11 @@ type Text interface { func NewText(textType, content string) (Text, error) { switch textType { case "text", "": - return &PlainText{Type: textType, Text: content}, nil + return newPlainText(textType, content) case "html": - return &PlainText{Type: textType, Text: html.UnescapeString(content)}, nil + return newPlainText(textType, html.UnescapeString(content)) case "xhtml": - return &XHTMLText{ - Type: textType, - XHTMLDiv: &XHTMLDiv{ - XMLNS: "http://www.w3.org/1999/xhtml", - Content: content, - }, - }, nil + return newXHTMLText(textType, content) default: return nil, fmt.Errorf("%v is not a valid text type", textType) } diff --git a/xhtmlDiv.go b/xhtmlDiv.go index 0629a37..0c9bbbe 100644 --- a/xhtmlDiv.go +++ b/xhtmlDiv.go @@ -2,6 +2,7 @@ package atom import ( "encoding/xml" + "errors" "fmt" ) @@ -11,12 +12,16 @@ type XHTMLDiv struct { Content string `xml:",innerxml"` } -// NewXHTMLDiv creates a new XHTMLDiv. It returns a *XHTMLDiv. -func NewXHTMLDiv(content string) *XHTMLDiv { +// NewXHTMLDiv creates a new XHTMLDiv. It returns a *XHTMLDiv and an error. +func NewXHTMLDiv(content string) (*XHTMLDiv, error) { + if content == "" { + return nil, errors.New("error creating new xhtml div: content string empty") + } + return &XHTMLDiv{ XMLNS: "http://www.w3.org/1999/xhtml", Content: content, - } + }, nil } // Check checks the XHTMLDiv for incompatibilities with RFC4287. It returns an diff --git a/xhtmlText.go b/xhtmlText.go index 3c94371..719fb74 100644 --- a/xhtmlText.go +++ b/xhtmlText.go @@ -1,6 +1,8 @@ package atom -import "fmt" +import ( + "fmt" +) type XHTMLText struct { *CommonAttributes @@ -11,6 +13,19 @@ type XHTMLText struct { // isText checks whether the XHTMLText is a Text. It returns a bool. func (x *XHTMLText) isText() bool { return true } +// newPlainText creates a new PlainText. It returns a *PlainText and an error. +func newXHTMLText(textType, content string) (*XHTMLText, error) { + xhtmlDiv, err := NewXHTMLDiv(content) + if err != nil { + return nil, fmt.Errorf("error creating new xhtml text: %v", err) + } + + return &XHTMLText{ + Type: textType, + XHTMLDiv: xhtmlDiv, + }, nil +} + // Check checks the XHTMLText for incompatibilities with RFC4287. It returns an // error. func (x *XHTMLText) Check() error {