Skip to content

Commit c6d3ba4

Browse files
authored
feat(godocfx): add friendlyAPIName (#6447)
1 parent d28e55d commit c6d3ba4

File tree

4 files changed

+166
-25
lines changed

4 files changed

+166
-25
lines changed

internal/godocfx/godocfx_test.go

+65-2
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,12 @@ package main
1919

2020
import (
2121
"bytes"
22+
"encoding/json"
2223
"flag"
2324
"fmt"
2425
"io/ioutil"
26+
"net/http"
27+
"net/http/httptest"
2528
"os"
2629
"path/filepath"
2730
"testing"
@@ -42,9 +45,28 @@ func TestMain(m *testing.M) {
4245
os.Exit(m.Run())
4346
}
4447

48+
func fakeMetaServer() *httptest.Server {
49+
meta := repoMetadata{
50+
"cloud.google.com/go/storage": repoMetadataItem{
51+
Description: "Storage API",
52+
},
53+
"cloud.google.com/iam/apiv1beta1": repoMetadataItem{
54+
Description: "IAM",
55+
},
56+
"cloud.google.com/go/cloudbuild/apiv1/v2": repoMetadataItem{
57+
Description: "Cloud Build API",
58+
},
59+
}
60+
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
61+
json.NewEncoder(w).Encode(meta)
62+
}))
63+
}
64+
4565
func TestParse(t *testing.T) {
4666
mod := "cloud.google.com/go/bigquery"
47-
r, err := parse(mod+"/...", ".", []string{"README.md"}, nil)
67+
metaServer := fakeMetaServer()
68+
defer metaServer.Close()
69+
r, err := parse(mod+"/...", ".", []string{"README.md"}, nil, &friendlyAPINamer{metaURL: metaServer.URL})
4870
if err != nil {
4971
t.Fatalf("Parse: %v", err)
5072
}
@@ -110,7 +132,9 @@ func TestGoldens(t *testing.T) {
110132
extraFiles := []string{"README.md"}
111133

112134
testPath := "cloud.google.com/go/storage"
113-
r, err := parse(testPath, ".", extraFiles, nil)
135+
metaServer := fakeMetaServer()
136+
defer metaServer.Close()
137+
r, err := parse(testPath, ".", extraFiles, nil, &friendlyAPINamer{metaURL: metaServer.URL})
114138
if err != nil {
115139
t.Fatalf("parse: %v", err)
116140
}
@@ -282,3 +306,42 @@ Deprecated: use Reader.Attrs.Size.`,
282306
}
283307
}
284308
}
309+
310+
func TestFriendlyAPIName(t *testing.T) {
311+
metaServer := fakeMetaServer()
312+
defer metaServer.Close()
313+
namer := &friendlyAPINamer{metaURL: metaServer.URL}
314+
315+
tests := []struct {
316+
importPath string
317+
want string
318+
}{
319+
{
320+
importPath: "cloud.google.com/go/storage",
321+
want: "Storage API",
322+
},
323+
{
324+
importPath: "cloud.google.com/iam/apiv1beta1",
325+
want: "IAM v1beta1",
326+
},
327+
{
328+
importPath: "cloud.google.com/go/cloudbuild/apiv1/v2",
329+
want: "Cloud Build API v1",
330+
},
331+
{
332+
importPath: "not found",
333+
want: "",
334+
},
335+
}
336+
337+
for _, test := range tests {
338+
got, err := namer.friendlyAPIName(test.importPath)
339+
if err != nil {
340+
t.Errorf("friendlyAPIName(%q) got err: %v", test.importPath, err)
341+
continue
342+
}
343+
if got != test.want {
344+
t.Errorf("friendlyAPIName(%q) got %q, want %q", test.importPath, got, test.want)
345+
}
346+
}
347+
}

internal/godocfx/main.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,10 @@ func process(mod indexEntry, workingDir, outDir string, print bool) error {
171171

172172
log.Println("Starting to parse")
173173
optionalExtraFiles := []string{}
174-
r, err := parse(mod.Path+"/...", workingDir, optionalExtraFiles, filter)
174+
namer := &friendlyAPINamer{
175+
metaURL: "https://raw.githubusercontent.com/googleapis/google-cloud-go/main/internal/.repo-metadata-full.json",
176+
}
177+
r, err := parse(mod.Path+"/...", workingDir, optionalExtraFiles, filter, namer)
175178
if err != nil {
176179
return fmt.Errorf("parse: %v", err)
177180
}

internal/godocfx/parse.go

+96-22
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,19 @@ package main
2525

2626
import (
2727
"bytes"
28+
"encoding/json"
2829
"fmt"
2930
"go/format"
3031
"go/printer"
3132
"go/token"
3233
"log"
34+
"net/http"
3335
"os"
3436
"path/filepath"
3537
"regexp"
3638
"sort"
3739
"strings"
40+
"sync"
3841

3942
goldmarkcodeblock "cloud.google.com/go/internal/godocfx/goldmark-codeblock"
4043
"cloud.google.com/go/internal/godocfx/pkgload"
@@ -84,18 +87,19 @@ type example struct {
8487

8588
// item represents a DocFX item.
8689
type item struct {
87-
UID string `yaml:"uid"`
88-
Name string `yaml:"name,omitempty"`
89-
ID string `yaml:"id,omitempty"`
90-
Summary string `yaml:"summary,omitempty"`
91-
Parent string `yaml:"parent,omitempty"`
92-
Type string `yaml:"type,omitempty"`
93-
Langs []string `yaml:"langs,omitempty"`
94-
Syntax syntax `yaml:"syntax,omitempty"`
95-
Examples []example `yaml:"codeexamples,omitempty"`
96-
Children []child `yaml:"children,omitempty"`
97-
AltLink string `yaml:"alt_link,omitempty"`
98-
Status string `yaml:"status,omitempty"`
90+
UID string `yaml:"uid"`
91+
Name string `yaml:"name,omitempty"`
92+
ID string `yaml:"id,omitempty"`
93+
Summary string `yaml:"summary,omitempty"`
94+
Parent string `yaml:"parent,omitempty"`
95+
Type string `yaml:"type,omitempty"`
96+
Langs []string `yaml:"langs,omitempty"`
97+
Syntax syntax `yaml:"syntax,omitempty"`
98+
Examples []example `yaml:"codeexamples,omitempty"`
99+
Children []child `yaml:"children,omitempty"`
100+
AltLink string `yaml:"alt_link,omitempty"`
101+
Status string `yaml:"status,omitempty"`
102+
FriendlyAPIName string `yaml:"friendlyApiName,omitempty"`
99103
}
100104

101105
func (p *page) addItem(i *item) {
@@ -125,7 +129,7 @@ type result struct {
125129
// workingDir is the directory to use to run go commands.
126130
//
127131
// optionalExtraFiles is a list of paths relative to the module root to include.
128-
func parse(glob string, workingDir string, optionalExtraFiles []string, filter []string) (*result, error) {
132+
func parse(glob string, workingDir string, optionalExtraFiles []string, filter []string, namer *friendlyAPINamer) (*result, error) {
129133
pages := map[string]*page{}
130134

131135
pkgInfos, err := pkgload.Load(glob, workingDir, filter)
@@ -162,16 +166,21 @@ func parse(glob string, workingDir string, optionalExtraFiles []string, filter [
162166
for _, pi := range pkgInfos {
163167
link := newLinker(pi)
164168
topLevelDecls := pkgsite.TopLevelDecls(pi.Doc)
169+
friendly, err := namer.friendlyAPIName(pi.Doc.ImportPath)
170+
if err != nil {
171+
return nil, err
172+
}
165173
pkgItem := &item{
166-
UID: pi.Doc.ImportPath,
167-
Name: pi.Doc.ImportPath,
168-
ID: pi.Doc.Name,
169-
Summary: toHTML(pi.Doc.Doc),
170-
Langs: onlyGo,
171-
Type: "package",
172-
Examples: processExamples(pi.Doc.Examples, pi.Fset),
173-
AltLink: "https://pkg.go.dev/" + pi.Doc.ImportPath,
174-
Status: pi.Status,
174+
UID: pi.Doc.ImportPath,
175+
Name: pi.Doc.ImportPath,
176+
ID: pi.Doc.Name,
177+
Summary: toHTML(pi.Doc.Doc),
178+
Langs: onlyGo,
179+
Type: "package",
180+
Examples: processExamples(pi.Doc.Examples, pi.Fset),
181+
AltLink: "https://pkg.go.dev/" + pi.Doc.ImportPath,
182+
Status: pi.Status,
183+
FriendlyAPIName: friendly,
175184
}
176185
pkgPage := &page{Items: []*item{pkgItem}}
177186
pages[pi.Doc.ImportPath] = pkgPage
@@ -640,3 +649,68 @@ func hasPrefix(s string, prefixes []string) bool {
640649
}
641650
return false
642651
}
652+
653+
// repoMetadata is the JSON format of the .repo-metadata-full.json file.
654+
// See https://raw.githubusercontent.com/googleapis/google-cloud-go/main/internal/.repo-metadata-full.json.
655+
type repoMetadata map[string]repoMetadataItem
656+
657+
type repoMetadataItem struct {
658+
Description string `json:"description"`
659+
}
660+
661+
type friendlyAPINamer struct {
662+
// metaURL is the URL to .repo-metadata-full.json, which contains metadata
663+
// about packages in this repo. See
664+
// https://raw.githubusercontent.com/googleapis/google-cloud-go/main/internal/.repo-metadata-full.json.
665+
metaURL string
666+
667+
// metadata caches the repo metadata results.
668+
metadata repoMetadata
669+
// getOnce ensures we only fetch the metadata JSON once.
670+
getOnce sync.Once
671+
}
672+
673+
// vNumberRE is a heuristic for API versions.
674+
var vNumberRE = regexp.MustCompile(`apiv[0-9][^/]*`)
675+
676+
// friendlyAPIName returns the friendlyAPIName for the given import path.
677+
// We rely on the .repo-metadata-full.json file to get the description of the
678+
// API for the given import path. We use the importPath to parse out the
679+
// API version, since that isn't included in the metadata.
680+
//
681+
// If no API description is found, friendlyAPIName returns "".
682+
// If no API version is found, friendlyAPIName only returns the description.
683+
//
684+
// See https://github.com/googleapis/google-cloud-go/issues/5949.
685+
func (d *friendlyAPINamer) friendlyAPIName(importPath string) (string, error) {
686+
var err error
687+
d.getOnce.Do(func() {
688+
resp, getErr := http.Get(d.metaURL)
689+
if err != nil {
690+
err = fmt.Errorf("error getting repo metadata: %v", getErr)
691+
return
692+
}
693+
defer resp.Body.Close()
694+
if decodeErr := json.NewDecoder(resp.Body).Decode(&d.metadata); err != nil {
695+
err = fmt.Errorf("failed to decode repo metadata: %v", decodeErr)
696+
return
697+
}
698+
})
699+
if err != nil {
700+
return "", err
701+
}
702+
if d.metadata == nil {
703+
return "", fmt.Errorf("no metadata found: earlier error fetching?")
704+
}
705+
pkg, ok := d.metadata[importPath]
706+
if !ok {
707+
return "", nil
708+
}
709+
710+
if apiV := vNumberRE.FindString(importPath); apiV != "" {
711+
version := strings.TrimPrefix(apiV, "api")
712+
return fmt.Sprintf("%s %s", pkg.Description, version), nil
713+
}
714+
715+
return pkg.Description, nil
716+
}

internal/godocfx/testdata/golden/index.yml

+1
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,7 @@ items:
288288
- cloud.google.com/go/storage.Writer.Write
289289
- cloud.google.com/go/storage.SignedURL
290290
alt_link: https://pkg.go.dev/cloud.google.com/go/storage
291+
friendlyApiName: Storage API
291292
- uid: cloud.google.com/go/storage.DeleteAction,SetStorageClassAction,AbortIncompleteMPUAction
292293
name: DeleteAction, SetStorageClassAction, AbortIncompleteMPUAction
293294
id: DeleteAction,SetStorageClassAction,AbortIncompleteMPUAction

0 commit comments

Comments
 (0)