Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions internal/librariangen/go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
module cloud.google.com/java/internal/librariangen

go 1.24.4

require github.com/google/go-cmp v0.7.0 // indirect
2 changes: 2 additions & 0 deletions internal/librariangen/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
78 changes: 78 additions & 0 deletions internal/librariangen/request/request.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package request

import (
"encoding/json"
"fmt"
"os"
)

// Library is the combination of all the fields used by CLI requests and responses.
// Each CLI command has its own request/response type, but they all use Library.
type Library struct {
ID string `json:"id,omitempty"`
Version string `json:"version,omitempty"`
APIs []API `json:"apis,omitempty"`
// SourcePaths are the directories to which librarian contributes code.
// For Go, this is typically the Go module directory.
SourcePaths []string `json:"source_roots,omitempty"`
// PreserveRegex are files/directories to leave untouched during generation.
// This is useful for preserving handwritten helper files or customizations.
PreserveRegex []string `json:"preserve_regex,omitempty"`
// RemoveRegex are files/directories to remove during generation.
RemoveRegex []string `json:"remove_regex,omitempty"`
// Changes are the changes being released (in a release request)
Changes []*Change `json:"changes,omitempty"`
// Specifying a tag format allows librarian to honor this format when creating
// a tag for the release of the library. The replacement values of {id} and {version}
// permitted to reference the values configured in the library. If not specified
// the assumed format is {id}-{version}. e.g., {id}/v{version}.
TagFormat string `yaml:"tag_format,omitempty" json:"tag_format,omitempty"`
// ReleaseTriggered indicates whether this library is being released (in a release request)
ReleaseTriggered bool `json:"release_triggered,omitempty"`
}

// API corresponds to a single API definition within a librarian request/response.
type API struct {
Path string `json:"path,omitempty"`
ServiceConfig string `json:"service_config,omitempty"`
}

// Change represents a single commit change for a library.
type Change struct {
Type string `json:"type"`
Subject string `json:"subject"`
Body string `json:"body"`
PiperCLNumber string `json:"piper_cl_number"`
SourceCommitHash string `json:"source_commit_hash"`
}

// ParseLibrary reads a file from the given path and unmarshals
// it into a Library struct. This is used for build and generate, where the requests
// are simply the library, with no wrapping.
func ParseLibrary(path string) (*Library, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("librariangen: failed to read request file from %s: %w", path, err)
}

var req Library
if err := json.Unmarshal(data, &req); err != nil {
return nil, fmt.Errorf("librariangen: failed to unmarshal request file %s: %w", path, err)
}

return &req, nil
}
98 changes: 98 additions & 0 deletions internal/librariangen/request/request_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package request

import (
"os"
"path/filepath"
"testing"

"github.com/google/go-cmp/cmp"
)

func TestParseLibrary(t *testing.T) {
testCases := []struct {
name string
content string
want *Library
wantErr bool
}{
{
name: "valid request",
content: `{
"id": "asset",
"version": "1.15.0",
"apis": [
{
"path": "google/cloud/asset/v1",
"service_config": "cloudasset_v1.yaml"
}
],
"source_roots": ["asset/apiv1"],
"preserve_regex": ["asset/apiv1/foo.go"],
"remove_regex": ["asset/apiv1/bar.go"]
}`,
want: &Library{
ID: "asset",
Version: "1.15.0",
APIs: []API{
{
Path: "google/cloud/asset/v1",
ServiceConfig: "cloudasset_v1.yaml",
},
},
SourcePaths: []string{"asset/apiv1"},
PreserveRegex: []string{"asset/apiv1/foo.go"},
RemoveRegex: []string{"asset/apiv1/bar.go"},
},
wantErr: false,
},
{
name: "malformed json",
content: `{"id": "foo",`,
wantErr: true,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
tmpDir := t.TempDir()
reqPath := filepath.Join(tmpDir, "generate-request.json")
if err := os.WriteFile(reqPath, []byte(tc.content), 0644); err != nil {
t.Fatalf("failed to write test file: %v", err)
}

got, err := ParseLibrary(reqPath)

if (err != nil) != tc.wantErr {
t.Errorf("Parse() error = %v, wantErr %v", err, tc.wantErr)
return
}

if !tc.wantErr {
if diff := cmp.Diff(tc.want, got); diff != "" {
t.Errorf("Parse() mismatch (-want +got):\n%s", diff)
}
}
})
}
}

func TestParseLibrary_FileNotFound(t *testing.T) {
_, err := ParseLibrary("non-existent-file.json")
if err == nil {
t.Error("Parse() expected error for non-existent file, got nil")
}
}
Loading