diff --git a/internal/sidekick/swift/generate_deprecated_enum_test.go b/internal/sidekick/swift/generate_deprecated_enum_test.go new file mode 100644 index 00000000000..cd6d0b0a608 --- /dev/null +++ b/internal/sidekick/swift/generate_deprecated_enum_test.go @@ -0,0 +1,109 @@ +// 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 TestGenerateEnum_Deprecated(t *testing.T) { + for _, test := range []struct { + name string + enumDeprecated bool + valDeprecated bool + wantEnum string + wantCase string + }{ + { + name: "deprecated-enum", + enumDeprecated: true, + valDeprecated: false, + wantEnum: "/// -- enum marker --\n@available(*, deprecated)\npublic enum Status", + wantCase: "/// -- case marker --\n case unspecified", + }, + { + name: "deprecated-value", + enumDeprecated: false, + valDeprecated: true, + wantEnum: "/// -- enum marker --\npublic enum Status", + wantCase: "/// -- case marker --\n @available(*, deprecated)\n case unspecified", + }, + { + name: "both-deprecated", + enumDeprecated: true, + valDeprecated: true, + wantEnum: "/// -- enum marker --\n@available(*, deprecated)\npublic enum Status", + wantCase: "/// -- case marker --\n @available(*, deprecated)\n case unspecified", + }, + { + name: "not-deprecated", + enumDeprecated: false, + valDeprecated: false, + wantEnum: "/// -- enum marker --\npublic enum Status", + wantCase: "/// -- case marker --\n case unspecified", + }, + } { + t.Run(test.name, func(t *testing.T) { + outDir := t.TempDir() + + enum := &api.Enum{ + Name: "Status", + Package: "google.cloud.test.v1", + ID: ".google.cloud.test.v1.Status", + Deprecated: test.enumDeprecated, + Documentation: "-- enum marker --", + } + enum.Values = []*api.EnumValue{ + { + Name: "STATUS_UNSPECIFIED", + Number: 0, + Parent: enum, + Deprecated: test.valDeprecated, + Documentation: "-- case marker --", + }, + } + enum.UniqueNumberValues = enum.Values + + model := api.NewTestAPI(nil, []*api.Enum{enum}, nil) + 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", "Status.swift") + content, err := os.ReadFile(filename) + if err != nil { + t.Fatal(err) + } + contentStr := string(content) + + got := extractBlock(t, contentStr, "/// -- enum marker --", "public enum Status") + if diff := cmp.Diff(test.wantEnum, got); diff != "" { + t.Errorf("mismatch (-want +got):\n%s", diff) + } + got = extractBlock(t, contentStr, "/// -- case marker --", "case unspecified") + if diff := cmp.Diff(test.wantCase, got); diff != "" { + t.Errorf("mismatch (-want +got):\n%s", diff) + } + }) + } +} diff --git a/internal/sidekick/swift/generate_deprecated_field_test.go b/internal/sidekick/swift/generate_deprecated_field_test.go new file mode 100644 index 00000000000..e3f46a55eee --- /dev/null +++ b/internal/sidekick/swift/generate_deprecated_field_test.go @@ -0,0 +1,96 @@ +// 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 TestGenerateField_Deprecated(t *testing.T) { + for _, test := range []struct { + name string + deprecated bool + repeated bool + want string + endStr string + }{ + { + name: "deprecated", + deprecated: true, + repeated: false, + want: " /// -- field marker --\n @available(*, deprecated)\n public var normalField: Swift.String", + endStr: "public var normalField: Swift.String", + }, + { + name: "not-deprecated", + deprecated: false, + repeated: false, + want: " /// -- field marker --\n public var normalField: Swift.String", + endStr: "public var normalField: Swift.String", + }, + { + name: "deprecated-repeated", + deprecated: true, + repeated: true, + want: " /// -- field marker --\n @available(*, deprecated)\n public var normalField: [Swift.String]", + endStr: "public var normalField: [Swift.String]", + }, + } { + t.Run(test.name, func(t *testing.T) { + outDir := t.TempDir() + + field := &api.Field{ + Name: "normal_field", + Documentation: "-- field marker --", + ID: ".google.cloud.test.v1.TestMessage.normal_field", + Typez: api.TypezString, + Deprecated: test.deprecated, + Repeated: test.repeated, + } + + msg := &api.Message{ + Name: "TestMessage", + Package: "google.cloud.test.v1", + ID: ".google.cloud.test.v1.TestMessage", + Fields: []*api.Field{field}, + } + + model := api.NewTestAPI([]*api.Message{msg}, nil, nil) + 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) + + got := extractBlock(t, contentStr, " /// -- field marker --", test.endStr) + if diff := cmp.Diff(test.want, got); diff != "" { + t.Errorf("mismatch (-want +got):\n%s", diff) + } + }) + } +} diff --git a/internal/sidekick/swift/generate_deprecated_message_test.go b/internal/sidekick/swift/generate_deprecated_message_test.go new file mode 100644 index 00000000000..87e5acd09f1 --- /dev/null +++ b/internal/sidekick/swift/generate_deprecated_message_test.go @@ -0,0 +1,109 @@ +// 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 TestGenerateMessage_Deprecated(t *testing.T) { + for _, test := range []struct { + name string + topDeprecated bool + nestedDeprecated bool + wantTop string + wantNested string + }{ + { + name: "deprecated-both", + topDeprecated: true, + nestedDeprecated: true, + wantTop: "/// -- top marker --\n@available(*, deprecated)\npublic struct TopMessage", + wantNested: " /// -- nested marker --\n @available(*, deprecated)\n public struct NestedMessage", + }, + { + name: "deprecated-top-only", + topDeprecated: true, + nestedDeprecated: false, + wantTop: "/// -- top marker --\n@available(*, deprecated)\npublic struct TopMessage", + wantNested: " /// -- nested marker --\n public struct NestedMessage", + }, + { + name: "deprecated-nested-only", + topDeprecated: false, + nestedDeprecated: true, + wantTop: "/// -- top marker --\npublic struct TopMessage", + wantNested: " /// -- nested marker --\n @available(*, deprecated)\n public struct NestedMessage", + }, + { + name: "not-deprecated", + topDeprecated: false, + nestedDeprecated: false, + wantTop: "/// -- top marker --\npublic struct TopMessage", + wantNested: " /// -- nested marker --\n public struct NestedMessage", + }, + } { + t.Run(test.name, func(t *testing.T) { + outDir := t.TempDir() + + nested := &api.Message{ + Name: "NestedMessage", + Package: "google.cloud.test.v1", + ID: ".google.cloud.test.v1.TopMessage.NestedMessage", + Deprecated: test.nestedDeprecated, + Documentation: "-- nested marker --", + } + + top := &api.Message{ + Name: "TopMessage", + Package: "google.cloud.test.v1", + ID: ".google.cloud.test.v1.TopMessage", + Deprecated: test.topDeprecated, + Documentation: "-- top marker --", + Messages: []*api.Message{nested}, + } + + model := api.NewTestAPI([]*api.Message{top}, nil, nil) + 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", "TopMessage.swift") + content, err := os.ReadFile(filename) + if err != nil { + t.Fatal(err) + } + contentStr := string(content) + + gotTop := extractBlock(t, contentStr, "/// -- top marker --", "public struct TopMessage") + if diff := cmp.Diff(test.wantTop, gotTop); diff != "" { + t.Errorf("mismatch top (-want +got):\n%s", diff) + } + + gotNested := extractBlock(t, contentStr, " /// -- nested marker --", "public struct NestedMessage") + if diff := cmp.Diff(test.wantNested, gotNested); diff != "" { + t.Errorf("mismatch nested (-want +got):\n%s", diff) + } + }) + } +} diff --git a/internal/sidekick/swift/generate_deprecated_method_test.go b/internal/sidekick/swift/generate_deprecated_method_test.go new file mode 100644 index 00000000000..7fb6ed92472 --- /dev/null +++ b/internal/sidekick/swift/generate_deprecated_method_test.go @@ -0,0 +1,223 @@ +// 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/config" + "github.com/googleapis/librarian/internal/sidekick/api" + "github.com/googleapis/librarian/internal/sidekick/parser" +) + +type expectedBlock struct { + start string + end string + want string +} + +func TestGenerateService_DeprecatedMethods(t *testing.T) { + // Common messages + requestType := api.NewTestMessage("Request"). + WithFields(api.NewTestField("name").WithType(api.TypezString)) + + responseType := api.NewTestMessage("Response") + + itemType := api.NewTestMessage("Item") + + paginationResponseType := api.NewTestMessage("PaginationResponse"). + WithFields( + api.NewTestField("items").WithMessageType(itemType).WithRepeated(), + api.NewTestField("next_page_token").WithType(api.TypezString), + ) + paginationResponseType.Pagination = &api.PaginationInfo{ + PageableItem: paginationResponseType.Fields[0], + NextPageToken: paginationResponseType.Fields[1], + } + + operationType := api.NewTestMessage("Operation").WithPackage("google.longrunning") + + lroResultType := api.NewTestMessage("LROResult") + lroMetadataType := api.NewTestMessage("LROMetadata") + getOperationInputType := api.NewTestMessage("GetOperationRequest").WithPackage("google.longrunning") + + for _, test := range []struct { + name string + setup func() *api.Method + want []expectedBlock + }{ + { + name: "Simple_Deprecated", + setup: func() *api.Method { + m := api.NewTestMethod("SimpleMethod"). + WithInput(requestType). + WithOutput(responseType). + WithVerb("POST"). + WithPathTemplate((&api.PathTemplate{}).WithLiteral("v1").WithLiteral("simple")) + m.Deprecated = true + m.Documentation = "-- simple marker --" + return m + }, + want: []expectedBlock{ + { + start: " /// See `TestServiceClient.simpleMethod`.", + end: "-> GoogleTest.Response", + want: " /// See `TestServiceClient.simpleMethod`.\n @available(*, deprecated)\n func simpleMethod(request: Request) async throws -> GoogleTest.Response", + }, + { + start: " /// -- simple marker --", + end: "async throws -> GoogleTest.Response", + want: " /// -- simple marker --\n ///\n /// @Snippet(path: \"TestService_SimpleMethod\")\n @available(*, deprecated)\n public func simpleMethod(\n request: Request, options: GoogleCloudGax.RequestOptions\n) async throws -> GoogleTest.Response", + }, + }, + }, + { + name: "Pagination_Deprecated", + setup: func() *api.Method { + m := api.NewTestMethod("PaginationMethod"). + WithInput(requestType). + WithOutput(paginationResponseType). + WithVerb("GET"). + WithPathTemplate((&api.PathTemplate{}).WithLiteral("v1").WithLiteral("pagination")) + m.Deprecated = true + m.Pagination = requestType.Fields[0] + m.Documentation = "-- pagination marker --" + return m + }, + want: []expectedBlock{ + { + start: " /// See `TestServiceClient.paginationMethod`.", + end: "-> any AsyncSequence", + want: " /// See `TestServiceClient.paginationMethod`.\n @available(*, deprecated)\n func paginationMethod(request: Request) async throws -> GoogleTest.PaginationResponse\n\n /// See `TestServiceClient.paginationMethod`.\n @available(*, deprecated)\n func paginationMethod(\n byItem: Request\n) throws -> any AsyncSequence", + }, + { + start: " /// -- pagination marker --", + end: "-> any AsyncSequence", + want: " /// -- pagination marker --\n ///\n /// @Snippet(path: \"TestService_PaginationMethod\")\n @available(*, deprecated)\n public func paginationMethod(\n request: Request, options: GoogleCloudGax.RequestOptions\n) async throws -> GoogleTest.PaginationResponse\n {\n try await self.inner.paginationMethod(request: request, options: options)\n }\n\n /// -- pagination marker --\n ///\n /// @Snippet(path: \"TestService_PaginationMethod\")\n @available(*, deprecated)\n public func paginationMethod(\n byItem: Request, options: GoogleCloudGax.RequestOptions\n) throws -> any AsyncSequence", + }, + }, + }, + { + name: "LRO_Deprecated", + setup: func() *api.Method { + m := api.NewTestMethod("LROMethod"). + WithInput(requestType). + WithOutput(operationType). + WithVerb("POST"). + WithPathTemplate((&api.PathTemplate{}).WithLiteral("v1").WithLiteral("lro")) + m.Deprecated = true + m.IsLRO = true + m.OperationInfo = &api.OperationInfo{ + ResponseTypeID: lroResultType.ID, + MetadataTypeID: lroMetadataType.ID, + } + m.Documentation = "-- lro marker --" + return m + }, + want: []expectedBlock{ + { + start: " /// See `TestServiceClient.lromethod`.", + end: "-> any GoogleCloudGax.PollableOperation", + want: " /// See `TestServiceClient.lromethod`.\n @available(*, deprecated)\n func lromethod(request: Request) async throws -> GoogleCloudLongrunningV1.Operation\n\n /// See `TestServiceClient.lromethod`.\n @available(*, deprecated)\n func lromethod(withPolling: Request) async throws -> any GoogleCloudGax.PollableOperation", + }, + { + start: " /// -- lro marker --", + end: "-> any GoogleCloudGax.PollableOperation", + want: " /// -- lro marker --\n ///\n /// @Snippet(path: \"TestService_LROMethod\")\n @available(*, deprecated)\n public func lromethod(\n request: Request, options: GoogleCloudGax.RequestOptions\n) async throws -> GoogleCloudLongrunningV1.Operation\n {\n try await self.inner.lromethod(request: request, options: options)\n }\n\n /// -- lro marker --\n ///\n /// @Snippet(path: \"TestService_LROMethod\")\n @available(*, deprecated)\n public func lromethod(\n withPolling: Request, options: GoogleCloudGax.RequestOptions\n) async throws -> any GoogleCloudGax.PollableOperation", + }, + }, + }, + { + name: "Simple_NotDeprecated", + setup: func() *api.Method { + m := api.NewTestMethod("NotDeprecatedMethod"). + WithInput(requestType). + WithOutput(responseType). + WithVerb("POST"). + WithPathTemplate((&api.PathTemplate{}).WithLiteral("v1").WithLiteral("notDeprecated")) + m.Documentation = "-- not deprecated marker --" + return m + }, + want: []expectedBlock{ + { + start: " /// See `TestServiceClient.notDeprecatedMethod`.", + end: "-> GoogleTest.Response", + want: " /// See `TestServiceClient.notDeprecatedMethod`.\n func notDeprecatedMethod(request: Request) async throws -> GoogleTest.Response", + }, + { + start: " /// -- not deprecated marker --", + end: "async throws -> GoogleTest.Response", + want: " /// -- not deprecated marker --\n ///\n /// @Snippet(path: \"TestService_NotDeprecatedMethod\")\n public func notDeprecatedMethod(\n request: Request, options: GoogleCloudGax.RequestOptions\n) async throws -> GoogleTest.Response", + }, + }, + }, + } { + t.Run(test.name, func(t *testing.T) { + outDir := t.TempDir() + + method := test.setup() + + // We need a fresh service for each test case + service := api.NewTestService("TestService").WithMethods( + method, + api.NewTestMethod("GetOperation"). + WithInput(getOperationInputType). + WithOutput(operationType). + WithVerb("GET"). + WithPathTemplate((&api.PathTemplate{}).WithLiteral("v1").WithLiteral("operations")), + ) + + model := api.NewTestAPI([]*api.Message{ + requestType, responseType, itemType, paginationResponseType, + operationType, lroResultType, lroMetadataType, getOperationInputType, + }, nil, []*api.Service{service}) + model.PackageName = "test" + + cfg := &parser.ModelConfig{ + Codec: map[string]string{ + "copyright-year": "2038", + }, + } + + swiftCfg := swiftConfig(t, []config.SwiftDependency{ + {Name: "GoogleCloudGax", RequiredByServices: true}, + {Name: "GoogleCloudAuth", RequiredByServices: true}, + {ApiPackage: "google.longrunning", Name: "GoogleCloudLongrunningV1"}, + {ApiPackage: "google.rpc", Name: "GoogleRpc"}, + }) + + if err := Generate(t.Context(), model, outDir, cfg, swiftCfg); err != nil { + t.Fatal(err) + } + + filename := filepath.Join(outDir, "Sources", "GoogleTest", "TestService.swift") + content, err := os.ReadFile(filename) + if err != nil { + t.Fatal(err) + } + contentStr := string(content) + + for _, want := range test.want { + got := extractBlock(t, contentStr, want.start, want.end) + if diff := cmp.Diff(want.want, got); diff != "" { + t.Errorf("mismatch (-want +got):\n%s", diff) + } + } + }) + } +} diff --git a/internal/sidekick/swift/generate_deprecated_oneof_test.go b/internal/sidekick/swift/generate_deprecated_oneof_test.go new file mode 100644 index 00000000000..047171b3241 --- /dev/null +++ b/internal/sidekick/swift/generate_deprecated_oneof_test.go @@ -0,0 +1,124 @@ +// 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 TestGenerateOneOf_Deprecated(t *testing.T) { + for _, test := range []struct { + name string + deprecated bool + isObject bool + want string + }{ + { + name: "deprecated-scalar", + deprecated: true, + isObject: false, + want: " /// -- case marker --\n @available(*, deprecated)\n case fieldOne(Swift.String)", + }, + { + name: "not-deprecated-scalar", + deprecated: false, + isObject: false, + want: " /// -- case marker --\n case fieldOne(Swift.String)", + }, + { + name: "deprecated-message", + deprecated: true, + isObject: true, + want: " /// -- case marker --\n @available(*, deprecated)\n indirect case fieldOne(Inner)", + }, + } { + t.Run(test.name, func(t *testing.T) { + outDir := t.TempDir() + + inner := &api.Message{ + Name: "Inner", + Package: "google.cloud.test.v1", + ID: ".google.cloud.test.v1.Inner", + } + + oneof := &api.OneOf{ + Name: "choice", + Documentation: "-- property marker --", + } + + field := &api.Field{ + Name: "field_one", + Documentation: "-- case marker --", + ID: ".google.cloud.test.v1.TestMessage.field_one", + Deprecated: test.deprecated, + IsOneOf: true, + Group: oneof, + } + if test.isObject { + field.Typez = api.TypezMessage + field.TypezID = ".google.cloud.test.v1.Inner" + } else { + field.Typez = api.TypezString + } + + msg := &api.Message{ + Name: "TestMessage", + Package: "google.cloud.test.v1", + ID: ".google.cloud.test.v1.TestMessage", + Fields: []*api.Field{field}, + OneOfs: []*api.OneOf{oneof}, + } + oneof.Fields = []*api.Field{field} + + model := api.NewTestAPI([]*api.Message{msg, inner}, nil, nil) + 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) + + endStr := "case fieldOne(Swift.String)" + if test.isObject { + endStr = "indirect case fieldOne(Inner)" + } + + got := extractBlock(t, contentStr, " /// -- case marker --", endStr) + if diff := cmp.Diff(test.want, got); diff != "" { + t.Errorf("mismatch (-want +got):\n%s", diff) + } + + // Verify the oneof property in the message. + // It should NOT be deprecated because api.OneOf doesn't have Deprecated field. + gotProperty := extractBlock(t, contentStr, " /// -- property marker --", "public var choice: OneOf_Choice? = nil") + wantProperty := " /// -- property marker --\n public var choice: OneOf_Choice? = nil" + if diff := cmp.Diff(wantProperty, gotProperty); diff != "" { + t.Errorf("mismatch (-want +got):\n%s", diff) + } + }) + } +} diff --git a/internal/sidekick/swift/generate_deprecated_service_test.go b/internal/sidekick/swift/generate_deprecated_service_test.go new file mode 100644 index 00000000000..a882d2cea77 --- /dev/null +++ b/internal/sidekick/swift/generate_deprecated_service_test.go @@ -0,0 +1,81 @@ +// 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_Deprecated(t *testing.T) { + for _, test := range []struct { + name string + deprecated bool + wantClient string + wantProtocol string + }{ + { + name: "deprecated", + deprecated: true, + wantClient: "/// @Snippet(path: \"DeprecatedServiceQuickstart\")\n@available(*, deprecated)\npublic class DeprecatedServiceClient", + wantProtocol: "/// and pass a mock implementation in your tests.\n @available(*, deprecated)\n public protocol DeprecatedServiceProtocol", + }, + { + name: "not-deprecated", + deprecated: false, + wantClient: "/// @Snippet(path: \"DeprecatedServiceQuickstart\")\npublic class DeprecatedServiceClient", + wantProtocol: "/// and pass a mock implementation in your tests.\n public protocol DeprecatedServiceProtocol", + }, + } { + t.Run(test.name, func(t *testing.T) { + outDir := t.TempDir() + + service := &api.Service{ + Name: "DeprecatedService", + Package: "test", + ID: ".test.DeprecatedService", + Deprecated: test.deprecated, + } + + model := api.NewTestAPI(nil, nil, []*api.Service{service}) + model.PackageName = "test" + cfg := &parser.ModelConfig{} + if err := Generate(t.Context(), model, outDir, cfg, swiftConfig(t, nil)); err != nil { + t.Fatal(err) + } + + filename := filepath.Join(outDir, "Sources", "GoogleTest", "DeprecatedService.swift") + content, err := os.ReadFile(filename) + if err != nil { + t.Fatal(err) + } + contentStr := string(content) + + got := extractBlock(t, contentStr, `/// @Snippet(path: "DeprecatedServiceQuickstart")`, "public class DeprecatedServiceClient") + if diff := cmp.Diff(test.wantClient, got); diff != "" { + t.Errorf("mismatch (-want +got):\n%s", diff) + } + got = extractBlock(t, contentStr, `/// and pass a mock implementation in your tests.`, "public protocol DeprecatedServiceProtocol") + if diff := cmp.Diff(test.wantProtocol, got); diff != "" { + t.Errorf("mismatch (-want +got):\n%s", diff) + } + }) + } +} diff --git a/internal/sidekick/swift/templates/common/client_protocol.mustache b/internal/sidekick/swift/templates/common/client_protocol.mustache index a190306feed..4155b51f05a 100644 --- a/internal/sidekick/swift/templates/common/client_protocol.mustache +++ b/internal/sidekick/swift/templates/common/client_protocol.mustache @@ -19,30 +19,51 @@ extension Clients { /// To mock `{{Codec.ClientName}}` change your functions to receive /// `some {{Codec.StubPrefix}}Protocol` or `any {{Codec.StubPrefix}}Protocol` /// and pass a mock implementation in your tests. + {{#Deprecated}} + @available(*, deprecated) + {{/Deprecated}} public protocol {{Codec.StubPrefix}}Protocol { {{#Codec.RestMethods}} {{^IsLroPoller}} /// See `{{Service.Codec.ClientName}}.{{Codec.Name}}`. + {{#Deprecated}} + @available(*, deprecated) + {{/Deprecated}} func {{> /templates/common/method_signature/request}} {{#Codec.PlainRPC}} {{#Signatures}} /// See `{{Service.Codec.ClientName}}.{{Codec.Name}}`. + {{#Deprecated}} + @available(*, deprecated) + {{/Deprecated}} func {{> /templates/common/method_signature/overload}} {{/Signatures}} {{/Codec.PlainRPC}} {{#Codec.Pagination}} /// See `{{Service.Codec.ClientName}}.{{Codec.Name}}`. + {{#Deprecated}} + @available(*, deprecated) + {{/Deprecated}} func {{> /templates/common/method_signature/pagination}} {{#Signatures}} /// See `{{Service.Codec.ClientName}}.{{Codec.Name}}`. + {{#Deprecated}} + @available(*, deprecated) + {{/Deprecated}} func {{> /templates/common/method_signature/pagination_overload}} {{/Signatures}} {{/Codec.Pagination}} {{#Codec.LRO}} /// See `{{Service.Codec.ClientName}}.{{Codec.Name}}`. + {{#Deprecated}} + @available(*, deprecated) + {{/Deprecated}} func {{> /templates/common/method_signature/lro}} {{#Signatures}} /// See `{{Service.Codec.ClientName}}.{{Codec.Name}}`. + {{#Deprecated}} + @available(*, deprecated) + {{/Deprecated}} func {{> /templates/common/method_signature/lro_overload}} {{/Signatures}} {{/Codec.LRO}} @@ -52,13 +73,22 @@ extension Clients { {{#Codec.RestMethods}} {{^IsLroPoller}} /// See `{{Service.Codec.ClientName}}.{{Codec.Name}}`. + {{#Deprecated}} + @available(*, deprecated) + {{/Deprecated}} func {{> /templates/common/method_signature/request_options}} {{#Codec.Pagination}} /// See `{{Service.Codec.ClientName}}.{{Codec.Name}}`. + {{#Deprecated}} + @available(*, deprecated) + {{/Deprecated}} func {{> /templates/common/method_signature/pagination_options}} {{/Codec.Pagination}} {{#Codec.LRO}} /// See `{{Service.Codec.ClientName}}.{{Codec.Name}}`. + {{#Deprecated}} + @available(*, deprecated) + {{/Deprecated}} func {{> /templates/common/method_signature/lro_options}} {{/Codec.LRO}} {{/IsLroPoller}} diff --git a/internal/sidekick/swift/templates/common/enum.mustache b/internal/sidekick/swift/templates/common/enum.mustache index 0cfb5ced054..9825233de9a 100644 --- a/internal/sidekick/swift/templates/common/enum.mustache +++ b/internal/sidekick/swift/templates/common/enum.mustache @@ -16,11 +16,17 @@ limitations under the License. {{#Codec.DocLines}} /// {{{.}}} {{/Codec.DocLines}} +{{#Deprecated}} +@available(*, deprecated) +{{/Deprecated}} public enum {{Codec.Name}}: Codable, Equatable, Sendable { {{#UniqueNumberValues}} {{#Codec.DocLines}} /// {{{.}}} {{/Codec.DocLines}} + {{#Deprecated}} + @available(*, deprecated) + {{/Deprecated}} case {{Codec.CaseName}} {{/UniqueNumberValues}} /// Encodes an unknown integer value. diff --git a/internal/sidekick/swift/templates/common/message.mustache b/internal/sidekick/swift/templates/common/message.mustache index f764b61ea39..9e1f40abc5c 100644 --- a/internal/sidekick/swift/templates/common/message.mustache +++ b/internal/sidekick/swift/templates/common/message.mustache @@ -16,6 +16,9 @@ limitations under the License. {{#Codec.DocLines}} /// {{{.}}} {{/Codec.DocLines}} +{{#Deprecated}} +@available(*, deprecated) +{{/Deprecated}} public struct {{Codec.Name}}: Codable, Equatable, {{Codec.Model.WktPackage}}._AnyPackable, {{#Codec.IsPaginatedResponse}} GoogleCloudGax._PaginatedResponse, @@ -27,6 +30,9 @@ public struct {{Codec.Name}}: Codable, Equatable, {{Codec.Model.WktPackage}}._An {{#Codec.DocLines}} /// {{{.}}} {{/Codec.DocLines}} + {{#Deprecated}} + @available(*, deprecated) + {{/Deprecated}} {{#Singular}} {{#Codec.Recursive}} public var {{Codec.Name}}: {{{Codec.FieldType}}} = nil diff --git a/internal/sidekick/swift/templates/common/oneof.mustache b/internal/sidekick/swift/templates/common/oneof.mustache index af1e5356779..a55c809fca1 100644 --- a/internal/sidekick/swift/templates/common/oneof.mustache +++ b/internal/sidekick/swift/templates/common/oneof.mustache @@ -21,6 +21,9 @@ public enum {{Codec.Name}}: Codable, Equatable, Sendable { {{#Codec.DocLines}} /// {{{.}}} {{/Codec.DocLines}} + {{#Deprecated}} + @available(*, deprecated) + {{/Deprecated}} {{^IsObject}} case {{Codec.Name}}({{Codec.FieldType}}) {{/IsObject}} diff --git a/internal/sidekick/swift/templates/common/service.swift.mustache b/internal/sidekick/swift/templates/common/service.swift.mustache index 36d5c3e8af8..69a43604d2c 100644 --- a/internal/sidekick/swift/templates/common/service.swift.mustache +++ b/internal/sidekick/swift/templates/common/service.swift.mustache @@ -38,6 +38,9 @@ import {{.}} {{/Codec.DocLines}} /// /// @Snippet(path: "{{Name}}Quickstart") +{{#Deprecated}} +@available(*, deprecated) +{{/Deprecated}} public class {{Codec.ClientName}}: Clients.{{Codec.StubPrefix}}Protocol { let inner: any Clients.{{Codec.StubPrefix}}Stub @@ -57,6 +60,9 @@ public class {{Codec.ClientName}}: Clients.{{Codec.StubPrefix}}Protocol { {{/Codec.DocLines}} /// /// @Snippet(path: "{{Service.Name}}_{{Name}}") + {{#Deprecated}} + @available(*, deprecated) + {{/Deprecated}} {{^IsLroPoller}}public {{/IsLroPoller}}func {{> /templates/common/method_signature/request_options}} { try await self.inner.{{Codec.Name}}(request: request, options: options) } @@ -67,6 +73,9 @@ public class {{Codec.ClientName}}: Clients.{{Codec.StubPrefix}}Protocol { {{/Codec.DocLines}} /// /// @Snippet(path: "{{Service.Name}}_{{Name}}") + {{#Deprecated}} + @available(*, deprecated) + {{/Deprecated}} public func {{> /templates/common/method_signature/pagination_options}} { let listRpc = { (token: String) async throws -> {{Codec.ReturnType}} in var request = byItem @@ -83,6 +92,9 @@ public class {{Codec.ClientName}}: Clients.{{Codec.StubPrefix}}Protocol { {{/Codec.DocLines}} /// /// @Snippet(path: "{{Service.Name}}_{{Name}}") + {{#Deprecated}} + @available(*, deprecated) + {{/Deprecated}} public func {{> /templates/common/method_signature/lro_options}} { let extractStatus = { (op: {{Codec.ReturnType}}) throws -> GoogleCloudGax._PollableOperationImpl<{{Codec.LRO.ReturnType}}>.State in guard op.done else {