Skip to content

Commit fe44aed

Browse files
authored
feat(librariangen): add request package (#3933)
This will be used for parsing Librarian CLI requests. Copied from https://github.com/googleapis/google-cloud-go/tree/main/internal/librariangen/request.
1 parent f6b0b47 commit fe44aed

File tree

4 files changed

+180
-0
lines changed

4 files changed

+180
-0
lines changed

internal/librariangen/go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
module cloud.google.com/java/internal/librariangen
22

33
go 1.24.4
4+
5+
require github.com/google/go-cmp v0.7.0 // indirect

internal/librariangen/go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
2+
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package request
16+
17+
import (
18+
"encoding/json"
19+
"fmt"
20+
"os"
21+
)
22+
23+
// Library is the combination of all the fields used by CLI requests and responses.
24+
// Each CLI command has its own request/response type, but they all use Library.
25+
type Library struct {
26+
ID string `json:"id,omitempty"`
27+
Version string `json:"version,omitempty"`
28+
APIs []API `json:"apis,omitempty"`
29+
// SourcePaths are the directories to which librarian contributes code.
30+
// For Go, this is typically the Go module directory.
31+
SourcePaths []string `json:"source_roots,omitempty"`
32+
// PreserveRegex are files/directories to leave untouched during generation.
33+
// This is useful for preserving handwritten helper files or customizations.
34+
PreserveRegex []string `json:"preserve_regex,omitempty"`
35+
// RemoveRegex are files/directories to remove during generation.
36+
RemoveRegex []string `json:"remove_regex,omitempty"`
37+
// Changes are the changes being released (in a release request)
38+
Changes []*Change `json:"changes,omitempty"`
39+
// Specifying a tag format allows librarian to honor this format when creating
40+
// a tag for the release of the library. The replacement values of {id} and {version}
41+
// permitted to reference the values configured in the library. If not specified
42+
// the assumed format is {id}-{version}. e.g., {id}/v{version}.
43+
TagFormat string `yaml:"tag_format,omitempty" json:"tag_format,omitempty"`
44+
// ReleaseTriggered indicates whether this library is being released (in a release request)
45+
ReleaseTriggered bool `json:"release_triggered,omitempty"`
46+
}
47+
48+
// API corresponds to a single API definition within a librarian request/response.
49+
type API struct {
50+
Path string `json:"path,omitempty"`
51+
ServiceConfig string `json:"service_config,omitempty"`
52+
}
53+
54+
// Change represents a single commit change for a library.
55+
type Change struct {
56+
Type string `json:"type"`
57+
Subject string `json:"subject"`
58+
Body string `json:"body"`
59+
PiperCLNumber string `json:"piper_cl_number"`
60+
SourceCommitHash string `json:"source_commit_hash"`
61+
}
62+
63+
// ParseLibrary reads a file from the given path and unmarshals
64+
// it into a Library struct. This is used for build and generate, where the requests
65+
// are simply the library, with no wrapping.
66+
func ParseLibrary(path string) (*Library, error) {
67+
data, err := os.ReadFile(path)
68+
if err != nil {
69+
return nil, fmt.Errorf("librariangen: failed to read request file from %s: %w", path, err)
70+
}
71+
72+
var req Library
73+
if err := json.Unmarshal(data, &req); err != nil {
74+
return nil, fmt.Errorf("librariangen: failed to unmarshal request file %s: %w", path, err)
75+
}
76+
77+
return &req, nil
78+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package request
16+
17+
import (
18+
"os"
19+
"path/filepath"
20+
"testing"
21+
22+
"github.com/google/go-cmp/cmp"
23+
)
24+
25+
func TestParseLibrary(t *testing.T) {
26+
testCases := []struct {
27+
name string
28+
content string
29+
want *Library
30+
wantErr bool
31+
}{
32+
{
33+
name: "valid request",
34+
content: `{
35+
"id": "asset",
36+
"version": "1.15.0",
37+
"apis": [
38+
{
39+
"path": "google/cloud/asset/v1",
40+
"service_config": "cloudasset_v1.yaml"
41+
}
42+
],
43+
"source_roots": ["asset/apiv1"],
44+
"preserve_regex": ["asset/apiv1/foo.go"],
45+
"remove_regex": ["asset/apiv1/bar.go"]
46+
}`,
47+
want: &Library{
48+
ID: "asset",
49+
Version: "1.15.0",
50+
APIs: []API{
51+
{
52+
Path: "google/cloud/asset/v1",
53+
ServiceConfig: "cloudasset_v1.yaml",
54+
},
55+
},
56+
SourcePaths: []string{"asset/apiv1"},
57+
PreserveRegex: []string{"asset/apiv1/foo.go"},
58+
RemoveRegex: []string{"asset/apiv1/bar.go"},
59+
},
60+
wantErr: false,
61+
},
62+
{
63+
name: "malformed json",
64+
content: `{"id": "foo",`,
65+
wantErr: true,
66+
},
67+
}
68+
69+
for _, tc := range testCases {
70+
t.Run(tc.name, func(t *testing.T) {
71+
tmpDir := t.TempDir()
72+
reqPath := filepath.Join(tmpDir, "generate-request.json")
73+
if err := os.WriteFile(reqPath, []byte(tc.content), 0644); err != nil {
74+
t.Fatalf("failed to write test file: %v", err)
75+
}
76+
77+
got, err := ParseLibrary(reqPath)
78+
79+
if (err != nil) != tc.wantErr {
80+
t.Errorf("Parse() error = %v, wantErr %v", err, tc.wantErr)
81+
return
82+
}
83+
84+
if !tc.wantErr {
85+
if diff := cmp.Diff(tc.want, got); diff != "" {
86+
t.Errorf("Parse() mismatch (-want +got):\n%s", diff)
87+
}
88+
}
89+
})
90+
}
91+
}
92+
93+
func TestParseLibrary_FileNotFound(t *testing.T) {
94+
_, err := ParseLibrary("non-existent-file.json")
95+
if err == nil {
96+
t.Error("Parse() expected error for non-existent file, got nil")
97+
}
98+
}

0 commit comments

Comments
 (0)