Skip to content

Commit 74fb6c1

Browse files
authored
feat(internal/librarian): allow adding APIs to existing Go libraries (#5124)
The `librarian add` command is updated to support adding new APIs to an already existing library in `librarian.yaml`, specifically for Go. For #4996
1 parent 2ab1a7b commit 74fb6c1

2 files changed

Lines changed: 146 additions & 1 deletion

File tree

internal/librarian/add.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ import (
3838

3939
var (
4040
errLibraryAlreadyExists = errors.New("library already exists in config")
41+
errAPIAlreadyExists = errors.New("api already exists in library")
42+
errAPIDuplicate = errors.New("api duplicate in input")
4143
errMissingAPI = errors.New("must provide at least one API")
4244
errMixedPreviewAndNonPreview = errors.New("cannot mix preview and non-preview APIs")
4345
errPreviewRequiresLibrary = errors.New("only APIs with an existing Library can have a Preview")
@@ -134,10 +136,15 @@ func addLibrary(cfg *config.Config, apis ...string) (string, *config.Config, err
134136
return "", nil, errMixedPreviewAndNonPreview
135137
}
136138
paths := make([]*config.API, 0, len(apis))
139+
seen := make(map[string]bool)
137140
for _, a := range apis {
138141
if isPreview {
139142
a = strings.TrimPrefix(a, "preview/")
140143
}
144+
if seen[a] {
145+
return "", nil, fmt.Errorf("%w: %s", errAPIDuplicate, a)
146+
}
147+
seen[a] = true
141148
paths = append(paths, &config.API{Path: a})
142149
}
143150
name := deriveLibraryName(cfg.Language, paths[0].Path)
@@ -158,7 +165,10 @@ func addLibrary(cfg *config.Config, apis ...string) (string, *config.Config, err
158165
return addPreviewLibrary(cfg, existingLib, paths, name)
159166
}
160167
if exists {
161-
return "", nil, fmt.Errorf("%w: %s", errLibraryAlreadyExists, name)
168+
if cfg.Language != config.LanguageGo {
169+
return "", nil, fmt.Errorf("%w: %s", errLibraryAlreadyExists, name)
170+
}
171+
return updateExistingLibrary(cfg, existingLib, paths)
162172
}
163173
return addNewLibrary(cfg, paths, name)
164174
}
@@ -196,6 +206,16 @@ func addNewLibrary(cfg *config.Config, apis []*config.API, name string) (string,
196206
return name, cfg, nil
197207
}
198208

209+
func updateExistingLibrary(cfg *config.Config, existingLib *config.Library, apis []*config.API) (string, *config.Config, error) {
210+
for _, api := range apis {
211+
if slices.ContainsFunc(existingLib.APIs, func(a *config.API) bool { return api.Path == a.Path }) {
212+
return "", nil, fmt.Errorf("%w: %s in library %s", errAPIAlreadyExists, api.Path, existingLib.Name)
213+
}
214+
}
215+
existingLib.APIs = append(existingLib.APIs, apis...)
216+
return existingLib.Name, cfg, nil
217+
}
218+
199219
// syncToStateYAML updates the .librarian/state.yaml with any new libraries.
200220
func syncToStateYAML(repoDir string, cfg *config.Config) error {
201221
stateFile := filepath.Join(repoDir, legacyconfig.LibrarianDir, legacyconfig.LibrarianStateFile)

internal/librarian/add_test.go

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,131 @@ func TestAddLibrary(t *testing.T) {
287287
}
288288
}
289289

290+
func TestAddLibrary_ExistingLibrary(t *testing.T) {
291+
for _, test := range []struct {
292+
name string
293+
apis []string
294+
cfg *config.Config
295+
wantName string
296+
wantCfg *config.Config
297+
}{
298+
{
299+
name: "update existing library",
300+
apis: []string{"google/cloud/secretmanager/v1beta2"},
301+
cfg: &config.Config{
302+
Language: config.LanguageGo,
303+
Libraries: []*config.Library{
304+
{
305+
Name: "secretmanager",
306+
Version: "1.2.3",
307+
APIs: []*config.API{
308+
{Path: "google/cloud/secretmanager/v1"},
309+
},
310+
},
311+
},
312+
},
313+
wantName: "secretmanager",
314+
wantCfg: &config.Config{
315+
Language: config.LanguageGo,
316+
Libraries: []*config.Library{
317+
{
318+
Name: "secretmanager",
319+
Version: "1.2.3",
320+
APIs: []*config.API{
321+
{Path: "google/cloud/secretmanager/v1"},
322+
{Path: "google/cloud/secretmanager/v1beta2"},
323+
},
324+
},
325+
},
326+
},
327+
},
328+
} {
329+
t.Run(test.name, func(t *testing.T) {
330+
tmpDir := t.TempDir()
331+
t.Chdir(tmpDir)
332+
if err := yaml.Write(config.LibrarianYAML, test.cfg); err != nil {
333+
t.Fatal(err)
334+
}
335+
gotName, gotCfg, err := addLibrary(test.cfg, test.apis...)
336+
if err != nil {
337+
t.Fatal(err)
338+
}
339+
if diff := cmp.Diff(test.wantName, gotName); diff != "" {
340+
t.Errorf("mismatch (-want +got):\n%s", diff)
341+
}
342+
if diff := cmp.Diff(test.wantCfg, gotCfg); diff != "" {
343+
t.Errorf("mismatch (-want +got):\n%s", diff)
344+
}
345+
})
346+
}
347+
}
348+
349+
func TestAddLibrary_ExistingLibrary_Error(t *testing.T) {
350+
for _, test := range []struct {
351+
name string
352+
apis []string
353+
cfg *config.Config
354+
wantErr error
355+
}{
356+
{
357+
name: "fail if api already exists",
358+
apis: []string{"google/cloud/secretmanager/v1beta2"},
359+
cfg: &config.Config{
360+
Language: config.LanguageGo,
361+
Libraries: []*config.Library{
362+
{
363+
Name: "secretmanager",
364+
Version: "1.2.3",
365+
APIs: []*config.API{
366+
{Path: "google/cloud/secretmanager/v1"},
367+
{Path: "google/cloud/secretmanager/v1beta2"},
368+
},
369+
},
370+
},
371+
},
372+
wantErr: errAPIAlreadyExists,
373+
},
374+
{
375+
name: "fail if api duplicated",
376+
apis: []string{
377+
"google/cloud/secretmanager/v1beta2",
378+
"google/cloud/secretmanager/v1beta2",
379+
},
380+
wantErr: errAPIDuplicate,
381+
},
382+
{
383+
name: "python doesn't support updating existing library",
384+
apis: []string{"google/cloud/secretmanager/v1beta2"},
385+
cfg: &config.Config{
386+
Language: config.LanguagePython,
387+
Libraries: []*config.Library{
388+
{
389+
Name: "google-cloud-secretmanager",
390+
Version: "1.2.3",
391+
APIs: []*config.API{
392+
{Path: "google/cloud/secretmanager/v1"},
393+
{Path: "google/cloud/secretmanager/v1beta2"},
394+
},
395+
},
396+
},
397+
},
398+
wantErr: errLibraryAlreadyExists,
399+
},
400+
} {
401+
t.Run(test.name, func(t *testing.T) {
402+
tmpDir := t.TempDir()
403+
t.Chdir(tmpDir)
404+
if err := yaml.Write(config.LibrarianYAML, test.cfg); err != nil {
405+
t.Fatal(err)
406+
}
407+
_, _, err := addLibrary(test.cfg, test.apis...)
408+
if !errors.Is(err, test.wantErr) {
409+
t.Fatalf("expected error %v, got %v", test.wantErr, err)
410+
}
411+
})
412+
}
413+
}
414+
290415
func TestAddLibrary_Preview(t *testing.T) {
291416
for _, test := range []struct {
292417
name string

0 commit comments

Comments
 (0)