diff --git a/.github/workflows/go-cicd.yaml b/.github/workflows/go-cicd.yaml
index 129709a..abb1da2 100644
--- a/.github/workflows/go-cicd.yaml
+++ b/.github/workflows/go-cicd.yaml
@@ -3,7 +3,7 @@ name: go-cicd
jobs:
- golangci-lint:
+ lint:
runs-on: ubuntu-latest
steps:
- name: Install Go
@@ -16,20 +16,21 @@ jobs:
echo "::set-env name=GOPATH::${{ github.workspace }}/go"
echo "::add-path::${{ github.workspace }}/go/bin"
- name: checkout
- uses: actions/checkout@v1
+ uses: actions/checkout@v2
with:
fetch-depth: 1
- path: podcast/go/src/github.com/${{ github.repository }}
+ path: go/src/github.com/${{ github.repository }}
- name: Install golangci-lint
shell: bash
run: |
go get github.com/golangci/golangci-lint/cmd/golangci-lint
- name: Run linters
shell: bash
- run: |
+ run: |
+ cd $GOPATH/src/github.com/${{ github.repository }}
golangci-lint -E bodyclose,misspell,gocyclo,dupl,gofmt,golint,unconvert,depguard,interfacer run
- coveralls:
+ coverage:
runs-on: ubuntu-latest
steps:
- name: Install Go
@@ -62,7 +63,9 @@ jobs:
github-token: ${{ secrets.GITHUB_TOKEN }}
path-to-lcov: coverage.lcov
- go-bench:
+ benchmark:
+ # TODO: actually compare to previous runs
+ # maybe this setup: https://github.com/knqyf263/cob
runs-on: ubuntu-latest
steps:
- name: Install Go
@@ -75,13 +78,14 @@ jobs:
echo "::set-env name=GOPATH::${{ github.workspace }}/go"
echo "::add-path::${{ github.workspace }}/go/bin"
- name: checkout
- uses: actions/checkout@v1
+ uses: actions/checkout@v2
with:
fetch-depth: 1
- path: podcast/go/src/github.com/${{ github.repository }}
+ path: go/src/github.com/${{ github.repository }}
- name: Run Benchmark
shell: bash
run: |
+ cd $GOPATH/src/github.com/${{ github.repository }}
go test -test.run Benchmark -cpu 1 -bench .
go-test:
@@ -102,10 +106,12 @@ jobs:
with:
go-version: ${{ matrix.go-version }}
- name: checkout
- uses: actions/checkout@v1
+ uses: actions/checkout@v2
with:
fetch-depth: 1
- path: podcast/go/src/github.com/${{ github.repository }}
+ path: go/src/github.com/${{ github.repository }}
- name: Run tests
+ shell: bash
run: |
+ cd $GOPATH/src/github.com/${{ github.repository }}
go test -v -covermode=count
diff --git a/.gitignore b/.gitignore
index 2c44570..1b1053c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,7 @@
profile.out
README.md.tmp
-
+corpus
+crashers
+suppressions
+workdir
+podcast-fuzz.zip
diff --git a/Makefile b/Makefile
index 0dd4e38..1b8e97f 100644
--- a/Makefile
+++ b/Makefile
@@ -3,13 +3,15 @@ SHELL = /bin/bash
GITHUB_REPO:=eduncan911/podcast
README:
- godoc2ghmd github.com/$(GITHUB_REPO) > README.md.tmp
+ godoc2ghmd -play -ex -verify_import_links=0 github.com/$(GITHUB_REPO) > README.md.tmp
echo "[![GoDoc](https://godoc.org/github.com/$(GITHUB_REPO)?status.svg)](https://godoc.org/github.com/$(GITHUB_REPO))" > README.md
echo "[![Build Status](https://github.com/$(GITHUB_REPO)/workflows/go-cicd/badge.svg)](https://github.com/$(GITHUB_REPO)/actions?workflow=go-cicd)" >> README.md
echo "[![Coverage Status](https://coveralls.io/repos/github/$(GITHUB_REPO)/badge.svg?branch=master)](https://coveralls.io/github/$(GITHUB_REPO)?branch=master)" >> README.md
echo "[![Go Report Card](https://goreportcard.com/badge/github.com/$(GITHUB_REPO))](https://goreportcard.com/report/github.com/$(GITHUB_REPO))" >> README.md
- echo "[![GoDoc](https://godoc.org/github.com/$(GITHUB_REPO)?status.svg)](https://godoc.org/github.com/$(GITHUB_REPO))"
echo "[![MIT License](https://img.shields.io/npm/l/mediaelement.svg)](https://eduncan911.mit-license.org/)" >> README.md
echo >>README.md
cat README.md.tmp >> README.md
rm README.md.tmp
+
+clean:
+ rm -rf corpus crashers suppressions workdir podcast-fuzz.zip
diff --git a/README.md b/README.md
index e491b29..a407119 100644
--- a/README.md
+++ b/README.md
@@ -37,16 +37,42 @@ new installs, tested with Go 1.13. To keep 1.7 compatibility, we use
If either runtime has an issue, please create an Issue and I will address.
### Extensibility
-In no way are you restricted in having full control over your feeds. You may
-choose to skip the API methods and instead use the structs directly. The
-fields have been grouped by RSS 2.0 and iTunes fields.
-
-iTunes specific fields are all prefixed with the letter `I`.
-
-### References
-RSS 2.0: https://cyber.harvard.edu/rss/rss.html
-
-Podcasts: https://help.apple.com/itc/podcasts_connect/#/itca5b22233
+For version 1.x, you are not restricted in having full control over your feeds.
+You may choose to skip the API methods and instead use the structs directly. The
+fields have been grouped by RSS 2.0 and iTunes fields with iTunes specific fields
+all prefixed with the letter `I`.
+
+However, do note that the 2.x version currently in progress will break this
+extensibility and enforce API methods going forward. This is to ensure that the feed
+can both be marshalled, and unmarshalled back and forth (current 1.x branch can only
+be unmarshalled - hence the work for 2.x).
+
+### Fuzzing Inputs
+`go-fuzz` has been added in 1.4.1, covering all exported API methods. They have been
+ran extensively and no issues have come out of them yet (most tests were ran overnight,
+over about 11 hours with zero crashes).
+
+If you wish to help fuzz the inputs, with Go 1.13 or later you can run `go-fuzz` on any
+of the inputs.
+
+ go get -u github.com/dvyukov/go-fuzz/go-fuzz
+ go get -u github.com/dvyukov/go-fuzz/go-fuzz-build
+ go get -u github.com/eduncan911/podcast
+ cd $GOPATH/src/github.com/eduncan911/podcast
+ go-fuzz-build
+ go-fuzz -func FuzzPodcastAddItem
+
+To obtain a list of available funcs to pass, just run `go-fuzz` without any parameters:
+
+ $ go-fuzz
+ 2020/02/13 07:27:32 -func flag not provided, but multiple fuzz functions available:
+ FuzzItemAddDuration, FuzzItemAddEnclosure, FuzzItemAddImage, FuzzItemAddPubDate,
+ FuzzItemAddSummary, FuzzPodcastAddAtomLink, FuzzPodcastAddAuthor, FuzzPodcastAddCategory,
+ FuzzPodcastAddImage, FuzzPodcastAddItem, FuzzPodcastAddLastBuildDate, FuzzPodcastAddPubDate,
+ FuzzPodcastAddSubTitle, FuzzPodcastAddSummary, FuzzPodcastBytes, FuzzPodcastEncode,
+ FuzzPodcastNew
+
+If you do find an issue, please raise an issue immediately and I will quickly address.
### Roadmap
The 1.x branch is now mostly in maintenance mode, open to PRs. This means no
@@ -70,6 +96,13 @@ However, the new 2.x branch, while keeping the same API, is expected break those
bypass the API methods and use the underlying public properties instead.
### Release Notes
+1.4.1
+
+ * Implement fuzz logic testing of exported funcs (#31)
+ * Upgrade CICD Pipeline Tooling (#31)
+ * Update documentation for 1.x and 2.3 (#31)
+ * Allow godoc2ghmd to run without network (#31)
+
1.4.0
* Add Go Modules, Update vendor folder (#26, #25)
@@ -121,6 +154,226 @@ bypass the API methods and use the underlying public properties instead.
* Initial release.
* Full documentation, full examples and complete code coverage.
+### References
+RSS 2.0: https://cyber.harvard.edu/rss/rss.html
+
+Podcasts: https://help.apple.com/itc/podcasts_connect/#/itca5b22233
+
+#### Example:
+
+
+Click to expand code.
+
+```go
+// ResponseWriter example using Podcast.Encode(w io.Writer).
+ //
+ httpHandler := func(w http.ResponseWriter, r *http.Request) {
+
+ // instantiate a new Podcast
+ p := podcast.New(
+ "eduncan911 Podcasts",
+ "http://eduncan911.com/",
+ "An example Podcast",
+ &pubDate, &updatedDate,
+ )
+
+ // add some channel properties
+ p.AddAuthor("Jane Doe", "me@janedoe.com")
+ p.AddAtomLink("http://eduncan911.com/feed.rss")
+ p.AddImage("http://janedoe.com/i.jpg")
+ p.AddSummary(`link example.com`)
+ p.IExplicit = "no"
+
+ for i := int64(1); i < 3; i++ {
+ n := strconv.FormatInt(i, 10)
+ d := pubDate.AddDate(0, 0, int(i))
+
+ // create an Item
+ item := podcast.Item{
+ Title: "Episode " + n,
+ Link: "http://example.com/" + n + ".mp3",
+ Description: "Description for Episode " + n,
+ PubDate: &d,
+ }
+ item.AddImage("http://example.com/episode-" + n + ".png")
+ item.AddSummary(`item example.com`)
+ // add a Download to the Item
+ item.AddEnclosure("http://e.com/"+n+".mp3", podcast.MP3, 55*(i+1))
+
+ // add the Item and check for validation errors
+ if _, err := p.AddItem(item); err != nil {
+ fmt.Println(item.Title, ": error", err.Error())
+ return
+ }
+ }
+
+ // set the Content Type to that of XML
+ w.Header().Set("Content-Type", "application/xml")
+
+ // finally, Encode and write the Podcast to the ResponseWriter.
+ //
+ // a simple pattern is to handle any errors within this check.
+ // alternatively if using middleware, you can just return
+ // the Podcast entity as it also implements the io.Writer interface
+ // that complies with several middleware packages.
+ if err := p.Encode(w); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ }
+ }
+
+ rr := httptest.NewRecorder()
+ httpHandler(rr, nil)
+ os.Stdout.Write(rr.Body.Bytes())
+ // Output:
+ //
+ //
+ //
+ // eduncan911 Podcasts
+ // http://eduncan911.com/
+ // An example Podcast
+ // go podcast v1.3.1 (github.com/eduncan911/podcast)
+ // en-us
+ // Mon, 06 Feb 2017 08:21:52 +0000
+ // me@janedoe.com (Jane Doe)
+ // Sat, 04 Feb 2017 08:21:52 +0000
+ //
+ // http://janedoe.com/i.jpg
+ // eduncan911 Podcasts
+ // http://eduncan911.com/
+ //
+ //
+ // me@janedoe.com (Jane Doe)
+ // example.com]]>
+ //
+ // no
+ // -
+ // http://e.com/1.mp3
+ // Episode 1
+ // http://example.com/1.mp3
+ // Description for Episode 1
+ // Sun, 05 Feb 2017 08:21:52 +0000
+ //
+ // me@janedoe.com (Jane Doe)
+ // example.com]]>
+ //
+ //
+ // -
+ // http://e.com/2.mp3
+ // Episode 2
+ // http://example.com/2.mp3
+ // Description for Episode 2
+ // Mon, 06 Feb 2017 08:21:52 +0000
+ //
+ // me@janedoe.com (Jane Doe)
+ // example.com]]>
+ //
+ //
+ //
+ //
+```
+
+
+
+#### Example:
+
+
+Click to expand code.
+
+```go
+// instantiate a new Podcast
+ p := podcast.New(
+ "Sample Podcasts",
+ "http://example.com/",
+ "An example Podcast",
+ &createdDate, &updatedDate,
+ )
+
+ // add some channel properties
+ p.ISubtitle = "A simple Podcast"
+ p.AddSummary(`link example.com`)
+ p.AddImage("http://example.com/podcast.jpg")
+ p.AddAuthor("Jane Doe", "jane.doe@example.com")
+ p.AddAtomLink("http://example.com/atom.rss")
+
+ for i := int64(9); i < 11; i++ {
+ n := strconv.FormatInt(i, 10)
+ d := pubDate.AddDate(0, 0, int(i))
+
+ // create an Item
+ item := podcast.Item{
+ Title: "Episode " + n,
+ Description: "Description for Episode " + n,
+ ISubtitle: "A simple episode " + n,
+ PubDate: &d,
+ }
+ item.AddImage("http://example.com/episode-" + n + ".png")
+ item.AddSummary(`item k example.com`)
+ // add a Download to the Item
+ item.AddEnclosure("http://example.com/"+n+".mp3", podcast.MP3, 55*(i+1))
+
+ // add the Item and check for validation errors
+ if _, err := p.AddItem(item); err != nil {
+ os.Stderr.WriteString("item validation error: " + err.Error())
+ }
+ }
+
+ // Podcast.Encode writes to an io.Writer
+ if err := p.Encode(os.Stdout); err != nil {
+ fmt.Println("error writing to stdout:", err.Error())
+ }
+
+ // Output:
+ //
+ //
+ //
+ // Sample Podcasts
+ // http://example.com/
+ // An example Podcast
+ // go podcast v1.3.1 (github.com/eduncan911/podcast)
+ // en-us
+ // Mon, 06 Feb 2017 08:21:52 +0000
+ // jane.doe@example.com (Jane Doe)
+ // Wed, 01 Feb 2017 08:21:52 +0000
+ //
+ // http://example.com/podcast.jpg
+ // Sample Podcasts
+ // http://example.com/
+ //
+ //
+ // jane.doe@example.com (Jane Doe)
+ // A simple Podcast
+ // example.com]]>
+ //
+ // -
+ // http://example.com/9.mp3
+ // Episode 9
+ // http://example.com/9.mp3
+ // Description for Episode 9
+ // Mon, 13 Feb 2017 08:21:52 +0000
+ //
+ // jane.doe@example.com (Jane Doe)
+ // A simple episode 9
+ // example.com]]>
+ //
+ //
+ // -
+ // http://example.com/10.mp3
+ // Episode 10
+ // http://example.com/10.mp3
+ // Description for Episode 10
+ // Tue, 14 Feb 2017 08:21:52 +0000
+ //
+ // jane.doe@example.com (Jane Doe)
+ // A simple episode 10
+ // example.com]]>
+ //
+ //
+ //
+ //
+```
+
+
+
## Table of Contents
* [Imported Packages](#pkg-imports)
@@ -364,6 +617,29 @@ func (i *Item) AddDuration(durationInSeconds int64)
```
AddDuration adds the duration to the iTunes duration field.
+#### Example:
+
+
+Click to expand code.
+
+```go
+i := podcast.Item{
+ Title: "item title",
+ Description: "item desc",
+ Link: "item link",
+ }
+ d := int64(533)
+
+ // add the Duration in Seconds
+ i.AddDuration(d)
+
+ fmt.Println(i.IDuration)
+ // Output:
+ // 8:53
+```
+
+
+
### func (\*Item) [AddEnclosure](./item.go#L54-L55)
``` go
func (i *Item) AddEnclosure(
@@ -393,6 +669,42 @@ AddPubDate adds the datetime as a parsed PubDate.
UTC time is used by default.
+#### Example:
+
+
+Click to expand code.
+
+```go
+p := podcast.New("title", "link", "description", nil, nil)
+ i := podcast.Item{
+ Title: "item title",
+ Description: "item desc",
+ Link: "item link",
+ }
+ d := pubDate.AddDate(0, 0, -11)
+
+ // add the pub date
+ i.AddPubDate(&d)
+
+ // before adding
+ if i.PubDate != nil {
+ fmt.Println(i.PubDateFormatted, *i.PubDate)
+ }
+
+ // this should not override with Podcast.PubDate
+ if _, err := p.AddItem(i); err != nil {
+ fmt.Println(err)
+ }
+
+ // after adding item
+ fmt.Println(i.PubDateFormatted, *i.PubDate)
+ // Output:
+ // Tue, 24 Jan 2017 08:21:52 +0000 2017-01-24 08:21:52 +0000 UTC
+ // Tue, 24 Jan 2017 08:21:52 +0000 2017-01-24 08:21:52 +0000 UTC
+```
+
+
+
### func (\*Item) [AddSummary](./item.go#L92)
``` go
func (i *Item) AddSummary(summary string)
@@ -458,6 +770,26 @@ New instantiates a Podcast with required parameters.
Nil-able fields are optional but recommended as they are formatted
to the expected proper formats.
+#### Example:
+
+
+Click to expand code.
+
+```go
+ti, l, d := "title", "link", "description"
+
+ // instantiate a new Podcast
+ p := podcast.New(ti, l, d, &pubDate, &updatedDate)
+
+ fmt.Println(p.Title, p.Link, p.Description, p.Language)
+ fmt.Println(p.PubDate, p.LastBuildDate)
+ // Output:
+ // title link description en-us
+ // Sat, 04 Feb 2017 08:21:52 +0000 Mon, 06 Feb 2017 08:21:52 +0000
+```
+
+
+
### func (\*Podcast) [AddAtomLink](./podcast.go#L94)
``` go
func (p *Podcast) AddAtomLink(href string)
@@ -470,6 +802,26 @@ func (p *Podcast) AddAuthor(name, email string)
```
AddAuthor adds the specified Author to the podcast.
+#### Example:
+
+
+Click to expand code.
+
+```go
+p := podcast.New("title", "link", "description", nil, nil)
+
+ // add the Author
+ p.AddAuthor("the name", "me@test.com")
+
+ fmt.Println(p.ManagingEditor)
+ fmt.Println(p.IAuthor)
+ // Output:
+ // me@test.com (the name)
+ // me@test.com (the name)
+```
+
+
+
### func (\*Podcast) [AddCategory](./podcast.go#L183)
``` go
func (p *Podcast) AddCategory(category string, subCategories []string)
@@ -553,6 +905,28 @@ as follows.
* Tech News
* TV & Film
+#### Example:
+
+
+Click to expand code.
+
+```go
+p := podcast.New("title", "link", "description", nil, nil)
+
+ // add the Category
+ p.AddCategory("Bombay", nil)
+ p.AddCategory("American", []string{"Longhair", "Shorthair"})
+ p.AddCategory("Siamese", nil)
+
+ fmt.Println(len(p.ICategories), len(p.ICategories[1].ICategories))
+ fmt.Println(p.Category)
+ // Output:
+ // 3 2
+ // Bombay,American,Siamese
+```
+
+
+
### func (\*Podcast) [AddImage](./podcast.go#L214)
``` go
func (p *Podcast) AddImage(url string)
@@ -566,6 +940,28 @@ extensions (.jpg, .png), and in the RGB colorspace. To optimize
images for mobile devices, Apple recommends compressing your
image files.
+#### Example:
+
+
+Click to expand code.
+
+```go
+p := podcast.New("title", "link", "description", nil, nil)
+
+ // add the Image
+ p.AddImage("http://example.com/image.jpg")
+
+ if p.Image != nil && p.IImage != nil {
+ fmt.Println(p.Image.URL)
+ fmt.Println(p.IImage.HREF)
+ }
+ // Output:
+ // http://example.com/image.jpg
+ // http://example.com/image.jpg
+```
+
+
+
### func (\*Podcast) [AddItem](./podcast.go#L267)
``` go
func (p *Podcast) AddItem(i Item) (int, error)
@@ -611,6 +1007,52 @@ Recommendations:
* For specifications of itunes tags, see:
https://help.apple.com/itc/podcasts_connect/#/itcb54353390
+#### Example:
+
+
+Click to expand code.
+
+```go
+p := podcast.New("title", "link", "description", &pubDate, &updatedDate)
+ p.AddAuthor("the name", "me@test.com")
+ p.AddImage("http://example.com/image.jpg")
+
+ // create an Item
+ date := pubDate.AddDate(0, 0, 77)
+ item := podcast.Item{
+ Title: "Episode 1",
+ Description: "Description for Episode 1",
+ ISubtitle: "A simple episode 1",
+ PubDate: &date,
+ }
+ item.AddEnclosure(
+ "http://example.com/1.mp3",
+ podcast.MP3,
+ 183,
+ )
+ item.AddSummary("See more at Here")
+
+ // add the Item
+ if _, err := p.AddItem(item); err != nil {
+ fmt.Println("item validation error: " + err.Error())
+ }
+
+ if len(p.Items) != 1 {
+ fmt.Println("expected 1 item in the collection")
+ }
+ pp := p.Items[0]
+ fmt.Println(
+ pp.GUID, pp.Title, pp.Link, pp.Description, pp.Author,
+ pp.AuthorFormatted, pp.Category, pp.Comments, pp.Source,
+ pp.PubDate, pp.PubDateFormatted, *pp.Enclosure,
+ pp.IAuthor, pp.IDuration, pp.IExplicit, pp.IIsClosedCaptioned,
+ pp.IOrder, pp.ISubtitle, pp.ISummary)
+ // Output:
+ // http://example.com/1.mp3 Episode 1 http://example.com/1.mp3 Description for Episode 1 &{{ } me@test.com (the name)} 2017-04-22 08:21:52 +0000 UTC Sat, 22 Apr 2017 08:21:52 +0000 {{ } http://example.com/1.mp3 183 183 audio/mpeg audio/mpeg} me@test.com (the name) A simple episode 1 &{{ } See more at Here}
+```
+
+
+
### func (\*Podcast) [AddLastBuildDate](./podcast.go#L344)
``` go
func (p *Podcast) AddLastBuildDate(datetime *time.Time)
@@ -619,6 +1061,24 @@ AddLastBuildDate adds the datetime as a parsed PubDate.
UTC time is used by default.
+#### Example:
+
+
+Click to expand code.
+
+```go
+p := podcast.New("title", "link", "description", nil, nil)
+ d := pubDate.AddDate(0, 0, -7)
+
+ p.AddLastBuildDate(&d)
+
+ fmt.Println(p.LastBuildDate)
+ // Output:
+ // Sat, 28 Jan 2017 08:21:52 +0000
+```
+
+
+
### func (\*Podcast) [AddPubDate](./podcast.go#L337)
``` go
func (p *Podcast) AddPubDate(datetime *time.Time)
@@ -627,6 +1087,24 @@ AddPubDate adds the datetime as a parsed PubDate.
UTC time is used by default.
+#### Example:
+
+
+Click to expand code.
+
+```go
+p := podcast.New("title", "link", "description", nil, nil)
+ d := pubDate.AddDate(0, 0, -5)
+
+ p.AddPubDate(&d)
+
+ fmt.Println(p.PubDate)
+ // Output:
+ // Mon, 30 Jan 2017 08:21:52 +0000
+```
+
+
+
### func (\*Podcast) [AddSubTitle](./podcast.go#L353)
``` go
func (p *Podcast) AddSubTitle(subTitle string)
@@ -648,12 +1126,122 @@ Limit: 4000 characters
Note that this field is a CDATA encoded field which allows for rich text
such as html links: `http://www.apple.com">Apple`.
+#### Example:
+
+
+Click to expand code.
+
+```go
+p := podcast.New("title", "link", "description", nil, nil)
+
+ // add a summary
+ p.AddSummary(`A very cool podcast with a long summary!
+
+ See more at our website: example.com
+ `)
+
+ if p.ISummary != nil {
+ fmt.Println(p.ISummary.Text)
+ }
+ // Output:
+ // A very cool podcast with a long summary!
+ //
+ // See more at our website: example.com
+```
+
+
+
### func (\*Podcast) [Bytes](./podcast.go#L386)
``` go
func (p *Podcast) Bytes() []byte
```
Bytes returns an encoded []byte slice.
+#### Example:
+
+
+Click to expand code.
+
+```go
+p := podcast.New(
+ "eduncan911 Podcasts",
+ "http://eduncan911.com/",
+ "An example Podcast",
+ &pubDate, &updatedDate,
+ )
+ p.AddAuthor("Jane Doe", "me@janedoe.com")
+ p.AddImage("http://janedoe.com/i.jpg")
+ p.AddSummary(`A very cool podcast with a long summary using Bytes()!
+
+ See more at our website: example.com
+ `)
+
+ for i := int64(5); i < 7; i++ {
+ n := strconv.FormatInt(i, 10)
+ d := pubDate.AddDate(0, 0, int(i+3))
+
+ item := podcast.Item{
+ Title: "Episode " + n,
+ Link: "http://example.com/" + n + ".mp3",
+ Description: "Description for Episode " + n,
+ PubDate: &d,
+ }
+ if _, err := p.AddItem(item); err != nil {
+ fmt.Println(item.Title, ": error", err.Error())
+ break
+ }
+ }
+
+ // call Podcast.Bytes() to return a byte array
+ os.Stdout.Write(p.Bytes())
+
+ // Output:
+ //
+ //
+ //
+ // eduncan911 Podcasts
+ // http://eduncan911.com/
+ // An example Podcast
+ // go podcast v1.3.1 (github.com/eduncan911/podcast)
+ // en-us
+ // Mon, 06 Feb 2017 08:21:52 +0000
+ // me@janedoe.com (Jane Doe)
+ // Sat, 04 Feb 2017 08:21:52 +0000
+ //
+ // http://janedoe.com/i.jpg
+ // eduncan911 Podcasts
+ // http://eduncan911.com/
+ //
+ // me@janedoe.com (Jane Doe)
+ // example.com
+ // ]]>
+ //
+ // -
+ // http://example.com/5.mp3
+ // Episode 5
+ // http://example.com/5.mp3
+ // Description for Episode 5
+ // Sun, 12 Feb 2017 08:21:52 +0000
+ // me@janedoe.com (Jane Doe)
+ //
+ //
+ // -
+ // http://example.com/6.mp3
+ // Episode 6
+ // http://example.com/6.mp3
+ // Description for Episode 6
+ // Mon, 13 Feb 2017 08:21:52 +0000
+ // me@janedoe.com (Jane Doe)
+ //
+ //
+ //
+ //
+```
+
+
+
### func (\*Podcast) [Encode](./podcast.go#L391)
``` go
func (p *Podcast) Encode(w io.Writer) error
diff --git a/doc.go b/doc.go
index 8c280fa..d1e10b3 100644
--- a/doc.go
+++ b/doc.go
@@ -33,17 +33,43 @@
//
// Extensibility
//
-// In no way are you restricted in having full control over your feeds. You may
-// choose to skip the API methods and instead use the structs directly. The
-// fields have been grouped by RSS 2.0 and iTunes fields.
+// For version 1.x, you are not restricted in having full control over your feeds.
+// You may choose to skip the API methods and instead use the structs directly. The
+// fields have been grouped by RSS 2.0 and iTunes fields with iTunes specific fields
+// all prefixed with the letter `I`.
//
-// iTunes specific fields are all prefixed with the letter `I`.
+// However, do note that the 2.x version currently in progress will break this
+// extensibility and enforce API methods going forward. This is to ensure that the feed
+// can both be marshalled, and unmarshalled back and forth (current 1.x branch can only
+// be unmarshalled - hence the work for 2.x).
//
-// References
+// Fuzzing Inputs
//
-// RSS 2.0: https://cyber.harvard.edu/rss/rss.html
+// `go-fuzz` has been added in 1.4.1, covering all exported API methods. They have been
+// ran extensively and no issues have come out of them yet (most tests were ran overnight,
+// over about 11 hours with zero crashes).
//
-// Podcasts: https://help.apple.com/itc/podcasts_connect/#/itca5b22233
+// If you wish to help fuzz the inputs, with Go 1.13 or later you can run `go-fuzz` on any
+// of the inputs.
+//
+// go get -u github.com/dvyukov/go-fuzz/go-fuzz
+// go get -u github.com/dvyukov/go-fuzz/go-fuzz-build
+// go get -u github.com/eduncan911/podcast
+// cd $GOPATH/src/github.com/eduncan911/podcast
+// go-fuzz-build
+// go-fuzz -func FuzzPodcastAddItem
+//
+// To obtain a list of available funcs to pass, just run `go-fuzz` without any parameters:
+//
+// $ go-fuzz
+// 2020/02/13 07:27:32 -func flag not provided, but multiple fuzz functions available:
+// FuzzItemAddDuration, FuzzItemAddEnclosure, FuzzItemAddImage, FuzzItemAddPubDate,
+// FuzzItemAddSummary, FuzzPodcastAddAtomLink, FuzzPodcastAddAuthor, FuzzPodcastAddCategory,
+// FuzzPodcastAddImage, FuzzPodcastAddItem, FuzzPodcastAddLastBuildDate, FuzzPodcastAddPubDate,
+// FuzzPodcastAddSubTitle, FuzzPodcastAddSummary, FuzzPodcastBytes, FuzzPodcastEncode,
+// FuzzPodcastNew
+//
+// If you do find an issue, please raise an issue immediately and I will quickly address.
//
// Roadmap
//
@@ -70,6 +96,12 @@
//
// Release Notes
//
+// 1.4.1
+// * Implement fuzz logic testing of exported funcs (#31)
+// * Upgrade CICD Pipeline Tooling (#31)
+// * Update documentation for 1.x and 2.3 (#31)
+// * Allow godoc2ghmd to run without network (#31)
+//
// 1.4.0
// * Add Go Modules, Update vendor folder (#26, #25)
// * Add C.I. GitHub Actions (#25)
@@ -113,4 +145,10 @@
// * Initial release.
// * Full documentation, full examples and complete code coverage.
//
+// References
+//
+// RSS 2.0: https://cyber.harvard.edu/rss/rss.html
+//
+// Podcasts: https://help.apple.com/itc/podcasts_connect/#/itca5b22233
+//
package podcast
diff --git a/fuzz.go b/fuzz.go
new file mode 100644
index 0000000..27e9ece
--- /dev/null
+++ b/fuzz.go
@@ -0,0 +1,297 @@
+// +build gofuzz
+
+package podcast
+
+import (
+ "bytes"
+ "encoding/binary"
+ "time"
+)
+
+func FuzzItemAddDuration(data []byte) int {
+ input, read := binary.Varint(data)
+ if input <= 0 && read == 0 {
+ // error converting []byte into int64
+ return 0
+ }
+ i := newItem(data)
+
+ i.AddDuration(input)
+
+ p := newPodcast(data)
+ if _, err := p.AddItem(i); err != nil {
+ return 0
+ }
+ var buf bytes.Buffer
+ if err := p.Encode(&buf); err != nil {
+ return 0
+ }
+
+ return 1
+}
+
+func FuzzItemAddEnclosure(data []byte) int {
+ url := string(data)
+ length, read := binary.Varint(data)
+ if length <= 0 && read == 0 {
+ // error converting []byte into int64
+ return 0
+ }
+ i := newItem(data)
+
+ i.AddEnclosure(url, MP3, length)
+
+ p := newPodcast(data)
+ if _, err := p.AddItem(i); err != nil {
+ return 0
+ }
+ var buf bytes.Buffer
+ if err := p.Encode(&buf); err != nil {
+ return 0
+ }
+
+ return 1
+}
+
+func FuzzItemAddImage(data []byte) int {
+ i := newItem(data)
+
+ i.AddImage(string(data))
+
+ p := newPodcast(data)
+ if _, err := p.AddItem(i); err != nil {
+ return 0
+ }
+ var buf bytes.Buffer
+ if err := p.Encode(&buf); err != nil {
+ return 0
+ }
+
+ return 1
+}
+
+func FuzzItemAddPubDate(data []byte) int {
+ t := time.Time{}
+ if err := t.GobDecode(data); err != nil {
+ return 0
+ }
+ i := newItem(data)
+
+ i.AddPubDate(&t)
+
+ p := newPodcast(data)
+ if _, err := p.AddItem(i); err != nil {
+ return 0
+ }
+ var buf bytes.Buffer
+ if err := p.Encode(&buf); err != nil {
+ return 0
+ }
+
+ return 1
+}
+
+func FuzzItemAddSummary(data []byte) int {
+ i := newItem(data)
+
+ i.AddSummary(string(data))
+
+ p := newPodcast(data)
+ if _, err := p.AddItem(i); err != nil {
+ return 0
+ }
+ var buf bytes.Buffer
+ if err := p.Encode(&buf); err != nil {
+ return 0
+ }
+
+ return 1
+}
+
+func FuzzPodcastNew(data []byte) int {
+ p := newPodcast(data)
+
+ var buf bytes.Buffer
+ if err := p.Encode(&buf); err != nil {
+ return 0
+ }
+
+ return 1
+}
+
+func FuzzPodcastAddAtomLink(data []byte) int {
+ p := newPodcast(data)
+
+ p.AddAtomLink(string(data))
+
+ var buf bytes.Buffer
+ if err := p.Encode(&buf); err != nil {
+ return 0
+ }
+
+ return 1
+}
+
+func FuzzPodcastAddAuthor(data []byte) int {
+ p := newPodcast(data)
+
+ p.AddAuthor(string(data), string(data))
+
+ var buf bytes.Buffer
+ if err := p.Encode(&buf); err != nil {
+ return 0
+ }
+
+ return 1
+}
+
+func FuzzPodcastAddCategory(data []byte) int {
+ p := newPodcast(data)
+
+ subs := make([]string, 3)
+ subs[0] = string(data)
+ subs[1] = string(data)
+ subs[2] = string(data)
+ p.AddCategory(string(data), subs)
+
+ var buf bytes.Buffer
+ if err := p.Encode(&buf); err != nil {
+ return 0
+ }
+
+ return 1
+}
+
+func FuzzPodcastAddImage(data []byte) int {
+ p := newPodcast(data)
+
+ p.AddImage(string(data))
+
+ var buf bytes.Buffer
+ if err := p.Encode(&buf); err != nil {
+ return 0
+ }
+
+ return 1
+}
+
+func FuzzPodcastAddItem(data []byte) int {
+ p := newPodcast(data)
+ i := newItem(data)
+
+ if _, err := p.AddItem(i); err != nil {
+ return 0
+ }
+
+ var buf bytes.Buffer
+ if err := p.Encode(&buf); err != nil {
+ return 0
+ }
+
+ return 1
+}
+
+func FuzzPodcastAddLastBuildDate(data []byte) int {
+ p := newPodcast(data)
+ t := time.Time{}
+ if err := t.GobDecode(data); err != nil {
+ return 0
+ }
+
+ p.AddLastBuildDate(&t)
+
+ var buf bytes.Buffer
+ if err := p.Encode(&buf); err != nil {
+ return 0
+ }
+
+ return 1
+}
+
+func FuzzPodcastAddPubDate(data []byte) int {
+ p := newPodcast(data)
+ t := time.Time{}
+ if err := t.GobDecode(data); err != nil {
+ return 0
+ }
+
+ p.AddPubDate(&t)
+
+ var buf bytes.Buffer
+ if err := p.Encode(&buf); err != nil {
+ return 0
+ }
+
+ return 1
+}
+
+func FuzzPodcastAddSubTitle(data []byte) int {
+ p := newPodcast(data)
+
+ p.AddSubTitle(string(data))
+
+ var buf bytes.Buffer
+ if err := p.Encode(&buf); err != nil {
+ return 0
+ }
+
+ return 1
+}
+
+func FuzzPodcastAddSummary(data []byte) int {
+ p := newPodcast(data)
+
+ p.AddSummary(string(data))
+
+ var buf bytes.Buffer
+ if err := p.Encode(&buf); err != nil {
+ return 0
+ }
+
+ return 1
+}
+
+func FuzzPodcastBytes(data []byte) int {
+ p := newPodcast(data)
+
+ p.Bytes()
+
+ return 1
+}
+
+func FuzzPodcastEncode(data []byte) int {
+ p := newPodcast(data)
+
+ var buf bytes.Buffer
+ if err := p.Encode(&buf); err != nil {
+ return 0
+ }
+
+ return 1
+}
+
+func newPodcast(data []byte) Podcast {
+ return New(
+ string(data),
+ string(data),
+ string(data),
+ nil, nil)
+}
+
+func newItem(data []byte) Item {
+ // Article minimal requirements are:
+ // - Title
+ // - Description
+ // - Link
+ //
+ // Audio minimal requirements are:
+ // - Title
+ // - Description
+ // - Enclosure (HREF, Type and Length all required)
+ //
+ return Item{
+ Title: string(data),
+ Description: string(data),
+ Link: string(data),
+ }
+}
diff --git a/go.mod b/go.mod
index b258075..0c5590f 100644
--- a/go.mod
+++ b/go.mod
@@ -4,6 +4,7 @@ go 1.13
require (
github.com/davecgh/go-spew v1.1.1
+ github.com/dvyukov/go-fuzz v0.0.0-20191206100749-a378175e205c // indirect
github.com/kr/pretty v0.1.0 // indirect
github.com/pkg/errors v0.9.1
github.com/pmezard/go-difflib v1.0.0
diff --git a/go.sum b/go.sum
index f5055af..6cbaf2b 100644
--- a/go.sum
+++ b/go.sum
@@ -2,6 +2,8 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/dvyukov/go-fuzz v0.0.0-20191206100749-a378175e205c h1:/bXaeEuNG6V0HeyEGw11DYLW5BGsOPlcVRIXbHNUWSo=
+github.com/dvyukov/go-fuzz v0.0.0-20191206100749-a378175e205c/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=