From daee0519d135a6f8c72327697a51e10641d0aa06 Mon Sep 17 00:00:00 2001 From: Carlos O'Ryan Date: Thu, 9 Jul 2026 15:11:09 -0400 Subject: [PATCH] fix(sidekick/swift): missin enum value docs The mustache template had a bad name for the `Codec.DocLines` array, so no documentation was produced for enum values. I wrote tests for other documentation elements (fields, messages, methods, services) to avoid regressions. --- .../swift/generate_enum_swift_test.go | 47 ++++++++ .../swift/generate_field_swift_test.go | 70 ++++++++++++ .../swift/generate_message_swift_test.go | 47 ++++++++ .../swift/generate_service_comments_test.go | 108 ++++++++++++++++++ .../swift/templates/common/enum.mustache | 4 +- 5 files changed, 274 insertions(+), 2 deletions(-) create mode 100644 internal/sidekick/swift/generate_service_comments_test.go diff --git a/internal/sidekick/swift/generate_enum_swift_test.go b/internal/sidekick/swift/generate_enum_swift_test.go index f5b17de8eeb..02d8c6c1a41 100644 --- a/internal/sidekick/swift/generate_enum_swift_test.go +++ b/internal/sidekick/swift/generate_enum_swift_test.go @@ -19,6 +19,7 @@ import ( "path/filepath" "testing" + "github.com/google/go-cmp/cmp" "github.com/googleapis/librarian/internal/sidekick/api" "github.com/googleapis/librarian/internal/sidekick/parser" ) @@ -68,3 +69,49 @@ func TestGenerateEnum_Files(t *testing.T) { } } } + +func TestGenerateEnum_DocComments(t *testing.T) { + outDir := t.TempDir() + + color := &api.Enum{ + Name: "Color", + Package: "google.cloud.test.v1", + ID: ".google.cloud.test.v1.Color", + Documentation: "Documentation for the Color enum.", + } + color.Values = []*api.EnumValue{ + { + Name: "COLOR_UNSPECIFIED", + Number: 0, + Parent: color, + Documentation: "Documentation for the COLOR_UNSPECIFIED value.", + }, + } + color.UniqueNumberValues = color.Values + + model := api.NewTestAPI([]*api.Message{}, []*api.Enum{color}, []*api.Service{}) + model.PackageName = "google.cloud.test.v1" + cfg := &parser.ModelConfig{} + if err := Generate(t.Context(), model, outDir, cfg, nil); err != nil { + t.Fatal(err) + } + + filename := filepath.Join(outDir, "Sources", "GoogleCloudTestV1", "Color.swift") + content, err := os.ReadFile(filename) + if err != nil { + t.Fatal(err) + } + contentStr := string(content) + + want := "/// Documentation for the Color enum.\npublic enum Color" + got := extractBlock(t, contentStr, "/// Documentation for the Color enum.", "public enum Color") + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("mismatch (-want +got):\n%s", diff) + } + + want = "/// Documentation for the COLOR_UNSPECIFIED value.\n case unspecified" + got = extractBlock(t, contentStr, "/// Documentation for the COLOR_UNSPECIFIED value.", "case unspecified") + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("mismatch (-want +got):\n%s", diff) + } +} diff --git a/internal/sidekick/swift/generate_field_swift_test.go b/internal/sidekick/swift/generate_field_swift_test.go index e3d4b048870..a4f2ae6ff0e 100644 --- a/internal/sidekick/swift/generate_field_swift_test.go +++ b/internal/sidekick/swift/generate_field_swift_test.go @@ -87,3 +87,73 @@ func TestGenerateField_InitFromDecoder(t *testing.T) { t.Errorf("mismatch (-want +got):\n%s", diff) } } + +func TestGenerateField_DocComments(t *testing.T) { + outDir := t.TempDir() + + field1 := &api.Field{ + Name: "normal_field", + Documentation: "Documentation for normal_field.", + ID: ".google.cloud.test.v1.TestMessage.normal_field", + Typez: api.TypezString, + } + + field2 := &api.Field{ + Name: "oneof_field", + Documentation: "Documentation for oneof_field.", + ID: ".google.cloud.test.v1.TestMessage.oneof_field", + Typez: api.TypezString, + IsOneOf: true, + } + + oneof := &api.OneOf{ + Name: "my_oneof", + Documentation: "Documentation for my_oneof.", + Fields: []*api.Field{field2}, + } + field2.Group = oneof + + msg := &api.Message{ + Name: "TestMessage", + Package: "google.cloud.test.v1", + ID: ".google.cloud.test.v1.TestMessage", + Fields: []*api.Field{field1, field2}, + OneOfs: []*api.OneOf{oneof}, + } + + model := api.NewTestAPI([]*api.Message{msg}, []*api.Enum{}, []*api.Service{}) + model.PackageName = "google.cloud.test.v1" + cfg := &parser.ModelConfig{} + + if err := Generate(t.Context(), model, outDir, cfg, nil); err != nil { + t.Fatal(err) + } + + filename := filepath.Join(outDir, "Sources", "GoogleCloudTestV1", "TestMessage.swift") + content, err := os.ReadFile(filename) + if err != nil { + t.Fatal(err) + } + contentStr := string(content) + + // Verify normal field documentation + want := " /// Documentation for normal_field.\n public var normalField: Swift.String" + got := extractBlock(t, contentStr, " /// Documentation for normal_field.", "public var normalField: Swift.String") + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("mismatch (-want +got):\n%s", diff) + } + + // Verify oneof documentation in the message + want = " /// Documentation for my_oneof.\n public var myOneof: OneOf_MyOneof?" + got = extractBlock(t, contentStr, " /// Documentation for my_oneof.", "public var myOneof: OneOf_MyOneof?") + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("mismatch (-want +got):\n%s", diff) + } + + // Verify field documentation in the oneof enum + want = " /// Documentation for oneof_field.\n case oneofField(Swift.String)" + got = extractBlock(t, contentStr, " /// Documentation for oneof_field.", "case oneofField(Swift.String)") + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("mismatch (-want +got):\n%s", diff) + } +} diff --git a/internal/sidekick/swift/generate_message_swift_test.go b/internal/sidekick/swift/generate_message_swift_test.go index 3428757dbeb..211b07f0652 100644 --- a/internal/sidekick/swift/generate_message_swift_test.go +++ b/internal/sidekick/swift/generate_message_swift_test.go @@ -504,3 +504,50 @@ func TestGenerateMessage_RecursiveChain(t *testing.T) { t.Errorf("nodeA initializer mismatch: want %q; got:\n%s", wantInitC, contentStrC) } } + +func TestGenerateMessage_DocComments(t *testing.T) { + outDir := t.TempDir() + + nested := &api.Message{ + Name: "NestedMessage", + Package: "google.cloud.test.v1", + ID: ".google.cloud.test.v1.TestMessage.NestedMessage", + Documentation: "Documentation for NestedMessage.", + } + msg := &api.Message{ + Name: "TestMessage", + Package: "google.cloud.test.v1", + ID: ".google.cloud.test.v1.TestMessage", + Documentation: "Documentation for TestMessage.", + Messages: []*api.Message{nested}, + } + + model := api.NewTestAPI([]*api.Message{msg}, []*api.Enum{}, []*api.Service{}) + model.PackageName = "google.cloud.test.v1" + cfg := &parser.ModelConfig{} + + if err := Generate(t.Context(), model, outDir, cfg, swiftConfig(t, nil)); err != nil { + t.Fatal(err) + } + + filename := filepath.Join(outDir, "Sources", "GoogleCloudTestV1", "TestMessage.swift") + content, err := os.ReadFile(filename) + if err != nil { + t.Fatal(err) + } + contentStr := string(content) + + // Verify top-level message documentation + want := "/// Documentation for TestMessage.\npublic struct TestMessage" + got := extractBlock(t, contentStr, "/// Documentation for TestMessage.", "public struct TestMessage") + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("mismatch (-want +got):\n%s", diff) + } + + // Verify nested message documentation + want = " /// Documentation for NestedMessage.\n public struct NestedMessage" + got = extractBlock(t, contentStr, " /// Documentation for NestedMessage.", "public struct NestedMessage") + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("mismatch (-want +got):\n%s", diff) + } +} diff --git a/internal/sidekick/swift/generate_service_comments_test.go b/internal/sidekick/swift/generate_service_comments_test.go new file mode 100644 index 00000000000..d7534b1976c --- /dev/null +++ b/internal/sidekick/swift/generate_service_comments_test.go @@ -0,0 +1,108 @@ +// Copyright 2026 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 +// +// https://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 swift + +import ( + "os" + "path/filepath" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/googleapis/librarian/internal/sidekick/api" + "github.com/googleapis/librarian/internal/sidekick/parser" +) + +func TestGenerateService_DocComments(t *testing.T) { + outDir := t.TempDir() + + req := &api.Message{ + Name: "GetSecretRequest", + Package: "google.cloud.test.v1", + ID: ".google.cloud.test.v1.GetSecretRequest", + Fields: []*api.Field{ + { + Name: "project", + ID: ".google.cloud.test.v1.GetSecretRequest.project", + Typez: api.TypezString, + }, + { + Name: "secret", + ID: ".google.cloud.test.v1.GetSecretRequest.secret", + Typez: api.TypezString, + }, + }, + } + req.Fields[0].Parent = req + req.Fields[1].Parent = req + res := &api.Message{ + Name: "Secret", + Package: "google.cloud.test.v1", + ID: ".google.cloud.test.v1.Secret", + } + + method := &api.Method{ + Name: "GetSecret", + Documentation: "Documentation for GetSecret method.", + InputTypeID: req.ID, + OutputTypeID: res.ID, + InputType: req, + OutputType: res, + PathInfo: &api.PathInfo{ + Bindings: []*api.PathBinding{ + { + Verb: "GET", + PathTemplate: (&api.PathTemplate{}).WithLiteral("v1").WithLiteral("projects").WithVariableNamed("project").WithLiteral("secrets").WithVariableNamed("secret"), + }, + }, + }, + } + + service := &api.Service{ + Name: "SecretManager", + Package: "google.cloud.test.v1", + Documentation: "Documentation for SecretManager service.", + Methods: []*api.Method{method}, + } + method.Service = service + + model := api.NewTestAPI([]*api.Message{req, res}, []*api.Enum{}, []*api.Service{service}) + model.PackageName = "google.cloud.test.v1" + cfg := &parser.ModelConfig{} + + if err := Generate(t.Context(), model, outDir, cfg, swiftConfig(t, nil)); err != nil { + t.Fatal(err) + } + + filename := filepath.Join(outDir, "Sources", "GoogleCloudTestV1", "SecretManager.swift") + content, err := os.ReadFile(filename) + if err != nil { + t.Fatal(err) + } + contentStr := string(content) + + // Verify service documentation + want := "/// Documentation for SecretManager service.\n///\n/// @Snippet(path: \"SecretManagerQuickstart\")\npublic class SecretManagerClient" + got := extractBlock(t, contentStr, "/// Documentation for SecretManager service.", "public class SecretManagerClient") + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("mismatch (-want +got):\n%s", diff) + } + + // Verify method documentation + want = " /// Documentation for GetSecret method.\n ///\n /// @Snippet(path: \"SecretManager_GetSecret\")\n public func getSecret" + got = extractBlock(t, contentStr, " /// Documentation for GetSecret method.", "public func getSecret") + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("mismatch (-want +got):\n%s", diff) + } +} diff --git a/internal/sidekick/swift/templates/common/enum.mustache b/internal/sidekick/swift/templates/common/enum.mustache index 8ee92b90a90..0cfb5ced054 100644 --- a/internal/sidekick/swift/templates/common/enum.mustache +++ b/internal/sidekick/swift/templates/common/enum.mustache @@ -18,9 +18,9 @@ limitations under the License. {{/Codec.DocLines}} public enum {{Codec.Name}}: Codable, Equatable, Sendable { {{#UniqueNumberValues}} - {{#DocLines}} + {{#Codec.DocLines}} /// {{{.}}} - {{/DocLines}} + {{/Codec.DocLines}} case {{Codec.CaseName}} {{/UniqueNumberValues}} /// Encodes an unknown integer value.