internal/blog: populate author names in feeds

Go blog posts have metadata like:

by:
- Author Name
- Maybe Another Author

Those YAML lists were decoded into a slice of empty interfaces, each
holding a string, and all that was being ignored. Update it to parse
the []any type of p["by"] and to return an error if there aren't any
authors. There are only 2 existing blog posts that cause such errors,
but they're very old and can be ignored (or updated if needed).

There's probably more that can be done, like having one <author> XML
item per 'by' item in the YAML/JSON metadata of blog posts, but this
is a reasonable step forward.

For golang/go#68869.

Change-Id: I7b97a09b006bacf4835442a749cb0e467c7dbb47
Reviewed-on: https://go-review.googlesource.com/c/website/+/605537
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-by: Hongxiang Jiang <hxjiang@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Dmitri Shuralyov <dmitshur@golang.org>
diff --git a/cmd/golangorg/testdata/blog.txt b/cmd/golangorg/testdata/blog.txt
index e3cd261..aea168d 100644
--- a/cmd/golangorg/testdata/blog.txt
+++ b/cmd/golangorg/testdata/blog.txt
@@ -41,6 +41,7 @@
 GET https://go.dev/blog/feed.atom
 header Content-Type == application/atom+xml; charset=utf-8
 body contains <feed xmlns="http://www.w3.org/2005/Atom"><title>The Go Blog</title>
+body !contains <author><name></name></author>
 
 GET https://go.dev/blog/.json
 header Content-Type == application/json; charset=utf-8
diff --git a/internal/blog/blog.go b/internal/blog/blog.go
index c0bfe79..ac225b8 100644
--- a/internal/blog/blog.go
+++ b/internal/blog/blog.go
@@ -7,6 +7,7 @@
 import (
 	"encoding/json"
 	"encoding/xml"
+	"fmt"
 	"html"
 	"io"
 	"net/http"
@@ -49,7 +50,10 @@
 		url, _ := p["URL"].(string)
 		date, _ := p["date"].(time.Time)
 		summary, _ := p["summary"].(string)
-		by, _ := p["by"].([]string)
+		authors, err := authors(p)
+		if err != nil {
+			return nil, fmt.Errorf("there's a problem in the 'by' metadata of the blog post file %v: %v", p["File"], err)
+		}
 		content, err := site.RenderContent(p, "blogfeed.tmpl")
 		if err != nil {
 			return nil, err
@@ -73,7 +77,7 @@
 				Body: string(content),
 			},
 			Author: &atom.Person{
-				Name: authors(by),
+				Name: authors,
 			},
 		}
 		feed.Entry = append(feed.Entry, e)
@@ -105,7 +109,10 @@
 		url, _ := p["URL"].(string)
 		date, _ := p["date"].(time.Time)
 		summary, _ := p["summary"].(string)
-		by, _ := p["by"].([]string)
+		authors, err := authors(p)
+		if err != nil {
+			return nil, fmt.Errorf("there's a problem in the 'by' metadata of the blog post file %v: %v", p["File"], err)
+		}
 		content, err := site.RenderContent(p, "blogfeed.tmpl")
 		if err != nil {
 			return nil, err
@@ -116,7 +123,7 @@
 			Time:    date,
 			Summary: summary,
 			Content: string(content),
-			Author:  authors(by),
+			Author:  authors,
 		}
 		feed = append(feed, item)
 	}
@@ -147,16 +154,26 @@
 	return pages, nil
 }
 
-func authors(by []string) string {
+func authors(p web.Page) (string, error) {
+	byAny, _ := p["by"].([]any)
+	if len(byAny) == 0 {
+		return "", fmt.Errorf("no author specified")
+	}
+	var by []string
+	for _, b := range byAny {
+		s, ok := b.(string)
+		if !ok {
+			return "", fmt.Errorf("author entry %q type is %[1]T, want string", b)
+		}
+		by = append(by, s)
+	}
 	switch len(by) {
-	case 0:
-		return ""
 	case 1:
-		return by[0]
+		return by[0], nil
 	case 2:
-		return by[0] + " and " + by[1]
+		return by[0] + " and " + by[1], nil
 	default:
-		return strings.Join(by[:len(by)-1], ", ") + ", and " + by[len(by)-1]
+		return strings.Join(by[:len(by)-1], ", ") + ", and " + by[len(by)-1], nil
 	}
 }