Skip to content

Commit 14e186c

Browse files
authored
feat(sidekick/swift): swap client vs. protocol (#6566)
Move the client struct to the top-level namespace. At the same time, move the protocol to the nested `Clients` namespace. This makes the client libraries easier to use, as most applications only interact with the client type, which now has a shorter name.
1 parent faa88f0 commit 14e186c

9 files changed

Lines changed: 234 additions & 229 deletions

internal/sidekick/swift/annotate_message_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ func TestAnnotateMessage(t *testing.T) {
143143
TypeURL: "type.googleapis.com/test.Service",
144144
CustomSerialization: false,
145145
SampleField: "<placeholder>",
146-
ParameterTypeName: "Clients.ServiceClient",
146+
ParameterTypeName: "ServiceClient",
147147
PlaceholderName: "ServiceClient",
148148
},
149149
wantImports: []string{"GoogleCloudWkt"},
@@ -315,7 +315,7 @@ func TestAnnotateMessage_DiscoveryRequests(t *testing.T) {
315315
Name: "GetRequest",
316316
TypeURL: "type.googleapis.com/test.Service.getRequest",
317317
SampleField: "<placeholder>",
318-
ParameterTypeName: "Clients.ServiceClient.GetRequest",
318+
ParameterTypeName: "ServiceClient.GetRequest",
319319
},
320320
},
321321
{
@@ -326,7 +326,7 @@ func TestAnnotateMessage_DiscoveryRequests(t *testing.T) {
326326
Name: "ListRequest",
327327
TypeURL: "type.googleapis.com/test.Protocol.listRequest",
328328
SampleField: "<placeholder>",
329-
ParameterTypeName: "Clients.ProtocolClient.ListRequest",
329+
ParameterTypeName: "ProtocolClient.ListRequest",
330330
},
331331
},
332332
} {

internal/sidekick/swift/field_type_name.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ func scalarFieldTypeName(field *api.Field) (string, error) {
147147
func (c *codec) messageTypeName(m *api.Message) (string, error) {
148148
name := pascalCase(m.Name)
149149
if m.ServicePlaceholder {
150-
name = "Clients." + pascalCase(m.Name+"Client")
150+
name = pascalCase(m.Name + "Client")
151151
}
152152
if m.Parent == nil {
153153
prefix, err := c.externalTypePrefix(m.Package)

internal/sidekick/swift/generate_pagination_swift_test.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -155,18 +155,18 @@ func verifyGeneratedMapService(t *testing.T, outDir string) {
155155
contentStr := string(content)
156156

157157
gotMethodOverload := extractBlock(t, contentStr, ` public func listSecrets(
158-
byItem: `, "\n }")
158+
byItem: `, "\n }")
159159
wantMethodOverload := ` public func listSecrets(
160160
byItem: ListSecretsRequest, options: GoogleCloudGax.RequestOptions
161161
) throws -> any AsyncSequence<(Swift.String, Secret), Swift.Error>
162162
{
163-
let listRpc = { (token: String) async throws -> GoogleCloudSecretmanagerV1.ListSecretsResponse in
164-
var request = byItem
165-
request.pageToken = token
166-
return try await self.listSecrets(request: request, options: options)
167-
}
168-
return GoogleCloudGax.PaginatedResponseSequence(listRpc: listRpc)
169-
}`
163+
let listRpc = { (token: String) async throws -> GoogleCloudSecretmanagerV1.ListSecretsResponse in
164+
var request = byItem
165+
request.pageToken = token
166+
return try await self.listSecrets(request: request, options: options)
167+
}
168+
return GoogleCloudGax.PaginatedResponseSequence(listRpc: listRpc)
169+
}`
170170
if diff := cmp.Diff(wantMethodOverload, gotMethodOverload); diff != "" {
171171
t.Errorf("mismatch (-want +got):\n%s", diff)
172172
}

internal/sidekick/swift/generate_service_swift_test.go

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -100,9 +100,8 @@ func TestGenerateServiceSwift_SnippetReference(t *testing.T) {
100100
}
101101
contentStr := string(content)
102102

103-
gotBlock := extractBlock(t, contentStr, "/// @Snippet", "public protocol Protocol_ {")
104-
wantBlock := `/// @Snippet(path: "ProtocolQuickstart")
105-
public protocol Protocol_ {`
103+
gotBlock := extractBlock(t, contentStr, "public protocol ", "{")
104+
wantBlock := `public protocol ProtocolProtocol {`
106105
if diff := cmp.Diff(wantBlock, gotBlock); diff != "" {
107106
t.Errorf("mismatch (-want +got):\n%s", diff)
108107
}
@@ -173,8 +172,8 @@ func TestGenerateService_Delegation(t *testing.T) {
173172
contentStr := string(content)
174173

175174
for _, want := range []string{
176-
"let inner: any IAMStub",
177-
"var inner: any IAMStub = try IAMTransport(options)",
175+
"let inner: any Clients.IAMStub",
176+
"var inner: any Clients.IAMStub = try Clients.IAMTransport(options)",
178177
"try await self.inner.createRole(request: request, options: options)",
179178
} {
180179
if !strings.Contains(contentStr, want) {
@@ -579,18 +578,18 @@ func verifyGeneratedService(t *testing.T, outDir string) {
579578
contentStr := string(content)
580579

581580
gotMethodOverload := extractBlock(t, contentStr, ` public func listSecrets(
582-
byItem: ListSecretsRequest, options: `, "\n }")
581+
byItem: ListSecretsRequest, options: `, "\n }")
583582
wantMethodOverload := ` public func listSecrets(
584583
byItem: ListSecretsRequest, options: GoogleCloudGax.RequestOptions
585584
) throws -> any AsyncSequence<Secret, Swift.Error>
586585
{
587-
let listRpc = { (token: String) async throws -> GoogleCloudSecretmanagerV1.ListSecretsResponse in
588-
var request = byItem
589-
request.pageToken = token
590-
return try await self.listSecrets(request: request, options: options)
591-
}
592-
return GoogleCloudGax.PaginatedResponseSequence(listRpc: listRpc)
593-
}`
586+
let listRpc = { (token: String) async throws -> GoogleCloudSecretmanagerV1.ListSecretsResponse in
587+
var request = byItem
588+
request.pageToken = token
589+
return try await self.listSecrets(request: request, options: options)
590+
}
591+
return GoogleCloudGax.PaginatedResponseSequence(listRpc: listRpc)
592+
}`
594593
if diff := cmp.Diff(wantMethodOverload, gotMethodOverload); diff != "" {
595594
t.Errorf("mismatch (-want +got):\n%s", diff)
596595
}
@@ -955,8 +954,8 @@ func TestGenerateDiscoveryService_Files(t *testing.T) {
955954
t.Fatal(err)
956955
}
957956

958-
// Verify it contains an extension to the right Clients.$ServiceName type.
959-
wantExtension := fmt.Appendf(nil, "extension Clients.%sClient {", test.serviceName)
957+
// Verify it contains an extension to the right ${ServiceName}Client type.
958+
wantExtension := fmt.Appendf(nil, "extension %sClient {", test.serviceName)
960959
if !bytes.Contains(content, wantExtension) {
961960
t.Errorf("expected extension %q in %s, got:\n%s", wantExtension, filename, content)
962961
}
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
{{!
2+
Copyright 2026 Google LLC
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
https://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
}}
16+
extension Clients {
17+
/// A Swift protocol to mock `{{Codec.ClientName}}`.
18+
///
19+
/// To mock `{{Codec.ClientName}}` change your functions to receive
20+
/// `some {{Codec.StubPrefix}}Protocol` or `any {{Codec.StubPrefix}}Protocol`
21+
/// and pass a mock implementation in your tests.
22+
public protocol {{Codec.StubPrefix}}Protocol {
23+
{{#Codec.RestMethods}}
24+
/// See `{{Service.Codec.ClientName}}.{{Codec.Name}}`.
25+
func {{> /templates/common/method_signature/request}}
26+
{{#Codec.PlainRPC}}
27+
{{#Signatures}}
28+
/// See `{{Service.Codec.ClientName}}.{{Codec.Name}}`.
29+
func {{> /templates/common/method_signature/overload}}
30+
{{/Signatures}}
31+
{{/Codec.PlainRPC}}
32+
{{#Codec.Pagination}}
33+
/// See `{{Service.Codec.ClientName}}.{{Codec.Name}}`.
34+
func {{> /templates/common/method_signature/pagination}}
35+
{{#Signatures}}
36+
/// See `{{Service.Codec.ClientName}}.{{Codec.Name}}`.
37+
func {{> /templates/common/method_signature/pagination_overload}}
38+
{{/Signatures}}
39+
{{/Codec.Pagination}}
40+
{{#Codec.LRO}}
41+
/// See `{{Service.Codec.ClientName}}.{{Codec.Name}}`.
42+
func {{> /templates/common/method_signature/lro}}
43+
{{#Signatures}}
44+
/// See `{{Service.Codec.ClientName}}.{{Codec.Name}}`.
45+
func {{> /templates/common/method_signature/lro_overload}}
46+
{{/Signatures}}
47+
{{/Codec.LRO}}
48+
{{/Codec.RestMethods}}
49+
50+
{{#Codec.RestMethods}}
51+
/// See `{{Service.Codec.ClientName}}.{{Codec.Name}}`.
52+
func {{> /templates/common/method_signature/request_options}}
53+
{{#Codec.Pagination}}
54+
/// See `{{Service.Codec.ClientName}}.{{Codec.Name}}`.
55+
func {{> /templates/common/method_signature/pagination_options}}
56+
{{/Codec.Pagination}}
57+
{{#Codec.LRO}}
58+
/// See `{{Service.Codec.ClientName}}.{{Codec.Name}}`.
59+
func {{> /templates/common/method_signature/lro_options}}
60+
{{/Codec.LRO}}
61+
{{/Codec.RestMethods}}
62+
}
63+
}
64+
65+
// Default implementations
66+
extension Clients.{{Codec.StubPrefix}}Protocol {
67+
{{#Codec.RestMethods}}
68+
69+
public func {{> /templates/common/method_signature/request}} {
70+
try await self.{{Codec.Name}}(request: request, options: .init())
71+
}
72+
73+
public func {{> /templates/common/method_signature/request_options}} {
74+
throw GoogleCloudGax.RequestError.unimplemented
75+
}
76+
{{#Codec.PlainRPC}}
77+
{{#Signatures}}
78+
79+
public func {{> /templates/common/method_signature/overload}} {
80+
let request = {{Method.InputType.Codec.ParameterTypeName}}().with {
81+
{{#Fields}}
82+
$0.{{Codec.Name}} = {{Codec.Name}}
83+
{{/Fields}}
84+
}
85+
{{#Method.ReturnsEmpty}}
86+
try await self.{{Method.Codec.Name}}(request: request)
87+
{{/Method.ReturnsEmpty}}
88+
{{^Method.ReturnsEmpty}}
89+
return try await self.{{Method.Codec.Name}}(request: request)
90+
{{/Method.ReturnsEmpty}}
91+
}
92+
{{/Signatures}}
93+
{{/Codec.PlainRPC}}
94+
{{#Codec.Pagination}}
95+
96+
public func {{> /templates/common/method_signature/pagination}} {
97+
try self.{{Codec.Name}}(byItem: byItem, options: .init())
98+
}
99+
100+
public func {{> /templates/common/method_signature/pagination_options}} {
101+
let listRpc = { (token: String) async throws -> {{Codec.ReturnType}} in
102+
throw GoogleCloudGax.RequestError.unimplemented
103+
}
104+
return GoogleCloudGax.PaginatedResponseSequence(listRpc: listRpc)
105+
}
106+
{{#Signatures}}
107+
108+
public func {{> /templates/common/method_signature/pagination_overload}} {
109+
let request = {{Method.InputType.Codec.ParameterTypeName}}().with {
110+
{{#Fields}}
111+
$0.{{Codec.Name}} = {{Codec.Name}}
112+
{{/Fields}}
113+
}
114+
return try self.{{Codec.Name}}(byItem: request)
115+
}
116+
{{/Signatures}}
117+
{{/Codec.Pagination}}
118+
{{#Codec.LRO}}
119+
120+
public func {{> /templates/common/method_signature/lro}} {
121+
try await self.{{Codec.Name}}(withPolling: withPolling, options: .init())
122+
}
123+
124+
public func {{> /templates/common/method_signature/lro_options}} {
125+
let poll = { () async throws -> GoogleCloudGax._PollableOperationImpl<{{Codec.LRO.ReturnType}}>.State in
126+
throw GoogleCloudGax.RequestError.unimplemented
127+
}
128+
return GoogleCloudGax._PollableOperationImpl(initialState: .init(done: false, result: nil), poll: poll)
129+
}
130+
{{#Signatures}}
131+
132+
public func {{> /templates/common/method_signature/lro_overload}} {
133+
let request = {{Method.InputType.Codec.ParameterTypeName}}().with {
134+
{{#Fields}}
135+
$0.{{Codec.Name}} = {{Codec.Name}}
136+
{{/Fields}}
137+
}
138+
return try await self.{{Codec.Name}}(withPolling: request)
139+
}
140+
{{/Signatures}}
141+
{{/Codec.LRO}}
142+
{{/Codec.RestMethods}}
143+
}

internal/sidekick/swift/templates/common/client_snippet.swift.mustache

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import {{.}}
3232
{{/Codec.SnippetImports}}
3333

3434
func sample({{#Codec.QuickstartMethod.SampleInfo}}{{#Codec.Parameters}}{{.}}: String, {{/Codec.Parameters}}{{/Codec.QuickstartMethod.SampleInfo}}) async throws {
35-
let client = try {{Codec.PackageName}}.Clients.{{Codec.ClientName}}()
35+
let client = try {{Codec.PackageName}}.{{Codec.ClientName}}()
3636
{{#Codec.QuickstartMethod}}
3737
{{> /templates/snippet/generic}}
3838
{{/Codec.QuickstartMethod}}

internal/sidekick/swift/templates/common/method_snippet.swift.mustache

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import {{Service.Codec.PackageName}}
3131
import {{.}}
3232
{{/Service.Codec.SnippetImports}}
3333

34-
func sample(client: some {{Service.Codec.Name}}{{#SampleInfo}}{{#Codec.Parameters}}, {{.}}: String{{/Codec.Parameters}}{{/SampleInfo}}) async throws {
34+
func sample(client: {{Service.Codec.ClientName}}{{#SampleInfo}}{{#Codec.Parameters}}, {{.}}: String{{/Codec.Parameters}}{{/SampleInfo}}) async throws {
3535
{{> /templates/snippet/generic}}
3636
}
3737
// snippet.hide
@@ -40,7 +40,7 @@ func sample(client: some {{Service.Codec.Name}}{{#SampleInfo}}{{#Codec.Parameter
4040
struct SnippetRunner {
4141
static func main() async throws {
4242
do {
43-
let client = try {{Service.Codec.PackageName}}.Clients.{{Service.Codec.ClientName}}()
43+
let client = try {{Service.Codec.PackageName}}.{{Service.Codec.ClientName}}()
4444
try await sample(client: client{{#SampleInfo}}{{#Codec.Parameters}}, {{.}}: "[placeholder]"{{/Codec.Parameters}}{{/SampleInfo}})
4545
} catch {
4646
print("Error: \(error)")

internal/sidekick/swift/templates/common/placeholder.mustache

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1313
See the License for the specific language governing permissions and
1414
limitations under the License.
1515
}}
16-
extension Clients.{{Codec.PlaceholderName}} {
16+
extension {{Codec.PlaceholderName}} {
1717
{{#Messages}}
1818

1919
{{> /templates/common/message}}

0 commit comments

Comments
 (0)