Skip to content

Commit a8f35f7

Browse files
committed
Batch 2 of Mapstruct Mappers
1 parent 29df505 commit a8f35f7

File tree

13 files changed

+182
-120
lines changed

13 files changed

+182
-120
lines changed

client/transport/jsonrpc/src/test/java/io/a2a/client/transport/jsonrpc/JSONRPCTransportTest.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
import io.a2a.spec.MessageSendParams;
5656
import io.a2a.spec.OpenIdConnectSecurityScheme;
5757
import io.a2a.spec.Part;
58-
import io.a2a.spec.PushNotificationAuthenticationInfo;
58+
import io.a2a.spec.AuthenticationInfo;
5959
import io.a2a.spec.PushNotificationConfig;
6060
import io.a2a.spec.SecurityScheme;
6161
import io.a2a.spec.Task;
@@ -315,7 +315,7 @@ public void testA2AClientGetTaskPushNotificationConfig() throws Exception {
315315
PushNotificationConfig pushNotificationConfig = taskPushNotificationConfig.pushNotificationConfig();
316316
assertNotNull(pushNotificationConfig);
317317
assertEquals("https://example.com/callback", pushNotificationConfig.url());
318-
PushNotificationAuthenticationInfo authenticationInfo = pushNotificationConfig.authentication();
318+
AuthenticationInfo authenticationInfo = pushNotificationConfig.authentication();
319319
assertTrue(authenticationInfo.schemes().size() == 1);
320320
assertEquals("jwt", authenticationInfo.schemes().get(0));
321321
}
@@ -340,13 +340,13 @@ public void testA2AClientSetTaskPushNotificationConfig() throws Exception {
340340
new TaskPushNotificationConfig("de38c76d-d54c-436c-8b9f-4c2703648d64",
341341
new PushNotificationConfig.Builder()
342342
.url("https://example.com/callback")
343-
.authenticationInfo(new PushNotificationAuthenticationInfo(Collections.singletonList("jwt"),
343+
.authenticationInfo(new AuthenticationInfo(Collections.singletonList("jwt"),
344344
null))
345345
.build()), null);
346346
PushNotificationConfig pushNotificationConfig = taskPushNotificationConfig.pushNotificationConfig();
347347
assertNotNull(pushNotificationConfig);
348348
assertEquals("https://example.com/callback", pushNotificationConfig.url());
349-
PushNotificationAuthenticationInfo authenticationInfo = pushNotificationConfig.authentication();
349+
AuthenticationInfo authenticationInfo = pushNotificationConfig.authentication();
350350
assertEquals(1, authenticationInfo.schemes().size());
351351
assertEquals("jwt", authenticationInfo.schemes().get(0));
352352
}

client/transport/rest/src/test/java/io/a2a/client/transport/rest/RestTransportTest.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
import io.a2a.spec.MessageSendParams;
3939
import io.a2a.spec.Part;
4040
import io.a2a.spec.Part.Kind;
41-
import io.a2a.spec.PushNotificationAuthenticationInfo;
41+
import io.a2a.spec.AuthenticationInfo;
4242
import io.a2a.spec.PushNotificationConfig;
4343
import io.a2a.spec.StreamingEventKind;
4444
import io.a2a.spec.Task;
@@ -304,13 +304,13 @@ public void testSetTaskPushNotificationConfiguration() throws Exception {
304304
new PushNotificationConfig.Builder()
305305
.url("https://example.com/callback")
306306
.authenticationInfo(
307-
new PushNotificationAuthenticationInfo(Collections.singletonList("jwt"), null))
307+
new AuthenticationInfo(Collections.singletonList("jwt"), null))
308308
.build());
309309
TaskPushNotificationConfig taskPushNotificationConfig = client.setTaskPushNotificationConfiguration(pushedConfig, null);
310310
PushNotificationConfig pushNotificationConfig = taskPushNotificationConfig.pushNotificationConfig();
311311
assertNotNull(pushNotificationConfig);
312312
assertEquals("https://example.com/callback", pushNotificationConfig.url());
313-
PushNotificationAuthenticationInfo authenticationInfo = pushNotificationConfig.authentication();
313+
AuthenticationInfo authenticationInfo = pushNotificationConfig.authentication();
314314
assertEquals(1, authenticationInfo.schemes().size());
315315
assertEquals("jwt", authenticationInfo.schemes().get(0));
316316
}
@@ -338,7 +338,7 @@ public void testGetTaskPushNotificationConfiguration() throws Exception {
338338
PushNotificationConfig pushNotificationConfig = taskPushNotificationConfig.pushNotificationConfig();
339339
assertNotNull(pushNotificationConfig);
340340
assertEquals("https://example.com/callback", pushNotificationConfig.url());
341-
PushNotificationAuthenticationInfo authenticationInfo = pushNotificationConfig.authentication();
341+
AuthenticationInfo authenticationInfo = pushNotificationConfig.authentication();
342342
assertTrue(authenticationInfo.schemes().size() == 1);
343343
assertEquals("jwt", authenticationInfo.schemes().get(0));
344344
}
@@ -367,7 +367,7 @@ public void testListTaskPushNotificationConfigurations() throws Exception {
367367
assertNotNull(pushNotificationConfig);
368368
assertEquals("https://example.com/callback", pushNotificationConfig.url());
369369
assertEquals("10", pushNotificationConfig.id());
370-
PushNotificationAuthenticationInfo authenticationInfo = pushNotificationConfig.authentication();
370+
AuthenticationInfo authenticationInfo = pushNotificationConfig.authentication();
371371
assertTrue(authenticationInfo.schemes().size() == 1);
372372
assertEquals("jwt", authenticationInfo.schemes().get(0));
373373
assertEquals("", authenticationInfo.credentials());
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package io.a2a.grpc.mapper;
2+
3+
import org.mapstruct.*;
4+
import org.mapstruct.factory.Mappers;
5+
6+
/**
7+
* Mapper between {@link io.a2a.spec.AuthenticationInfo} and {@link io.a2a.grpc.AuthenticationInfo}.
8+
*/
9+
@Mapper(config = ProtoMapperConfig.class,
10+
collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED)
11+
public interface AuthenticationInfoMapper {
12+
13+
AuthenticationInfoMapper INSTANCE = Mappers.getMapper(AuthenticationInfoMapper.class);
14+
15+
@IgnoreProtobufInternals
16+
@Mapping(target = "credentialsBytes", ignore = true)
17+
@Mapping(source = "schemes", target = "schemesList")
18+
@Mapping(target = "credentials", source = "credentials", conditionExpression = "java(domain.credentials() != null)")
19+
io.a2a.grpc.AuthenticationInfo toProto(io.a2a.spec.AuthenticationInfo domain);
20+
21+
@Mapping(source = "schemesList", target = "schemes")
22+
io.a2a.spec.AuthenticationInfo fromProto(io.a2a.grpc.AuthenticationInfo proto);
23+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package io.a2a.grpc.mapper;
2+
3+
import org.mapstruct.CollectionMappingStrategy;
4+
import org.mapstruct.Mapper;
5+
import org.mapstruct.Mapping;
6+
import org.mapstruct.factory.Mappers;
7+
8+
/**
9+
* Mapper between {@link io.a2a.spec.AuthorizationCodeOAuthFlow} and {@link io.a2a.grpc.AuthorizationCodeOAuthFlow}.
10+
*/
11+
@Mapper(config = ProtoMapperConfig.class,
12+
collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED)
13+
public interface AuthorizationCodeOAuthFlowMapper {
14+
15+
AuthorizationCodeOAuthFlowMapper INSTANCE = Mappers.getMapper(AuthorizationCodeOAuthFlowMapper.class);
16+
17+
@IgnoreProtobufInternals
18+
@Mapping(target = "authorizationUrlBytes", ignore = true)
19+
@Mapping(target = "refreshUrlBytes", ignore = true)
20+
@Mapping(target = "tokenUrlBytes", ignore = true)
21+
@Mapping(target = "scopesMap", ignore = true)
22+
@Mapping(target = "scopesCount", ignore = true)
23+
@Mapping(target = "removeScopes", ignore = true)
24+
@Mapping(target = "putAllScopes", ignore = true)
25+
@Mapping(target = "mutableScopes", ignore = true)
26+
io.a2a.grpc.AuthorizationCodeOAuthFlow toProto(io.a2a.spec.AuthorizationCodeOAuthFlow domain);
27+
28+
io.a2a.spec.AuthorizationCodeOAuthFlow fromProto(io.a2a.grpc.AuthorizationCodeOAuthFlow proto);
29+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package io.a2a.grpc.mapper;
2+
3+
import org.mapstruct.CollectionMappingStrategy;
4+
import org.mapstruct.Mapper;
5+
import org.mapstruct.Mapping;
6+
import org.mapstruct.factory.Mappers;
7+
8+
/**
9+
* Mapper between {@link io.a2a.spec.ClientCredentialsOAuthFlow} and {@link io.a2a.grpc.ClientCredentialsOAuthFlow}.
10+
*/
11+
@Mapper(config = ProtoMapperConfig.class,
12+
collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED)
13+
public interface ClientCredentialsOAuthFlowMapper {
14+
15+
ClientCredentialsOAuthFlowMapper INSTANCE = Mappers.getMapper(ClientCredentialsOAuthFlowMapper.class);
16+
17+
@IgnoreProtobufInternals
18+
@Mapping(target = "refreshUrlBytes", ignore = true)
19+
@Mapping(target = "tokenUrlBytes", ignore = true)
20+
@Mapping(target = "scopesMap", ignore = true)
21+
@Mapping(target = "scopesCount", ignore = true)
22+
@Mapping(target = "removeScopes", ignore = true)
23+
@Mapping(target = "putAllScopes", ignore = true)
24+
@Mapping(target = "mutableScopes", ignore = true)
25+
io.a2a.grpc.ClientCredentialsOAuthFlow toProto(io.a2a.spec.ClientCredentialsOAuthFlow domain);
26+
27+
io.a2a.spec.ClientCredentialsOAuthFlow fromProto(io.a2a.grpc.ClientCredentialsOAuthFlow proto);
28+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package io.a2a.grpc.mapper;
2+
3+
import org.mapstruct.CollectionMappingStrategy;
4+
import org.mapstruct.Mapper;
5+
import org.mapstruct.Mapping;
6+
import org.mapstruct.factory.Mappers;
7+
8+
/**
9+
* Mapper between {@link io.a2a.spec.ImplicitOAuthFlow} and {@link io.a2a.grpc.ImplicitOAuthFlow}.
10+
*/
11+
@Mapper(config = ProtoMapperConfig.class,
12+
collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED)
13+
public interface ImplicitOAuthFlowMapper {
14+
15+
ImplicitOAuthFlowMapper INSTANCE = Mappers.getMapper(ImplicitOAuthFlowMapper.class);
16+
17+
@IgnoreProtobufInternals
18+
@Mapping(target = "authorizationUrlBytes", ignore = true)
19+
@Mapping(target = "refreshUrlBytes", ignore = true)
20+
@Mapping(target = "scopesMap", ignore = true)
21+
@Mapping(target = "scopesCount", ignore = true)
22+
@Mapping(target = "removeScopes", ignore = true)
23+
@Mapping(target = "putAllScopes", ignore = true)
24+
@Mapping(target = "mutableScopes", ignore = true)
25+
io.a2a.grpc.ImplicitOAuthFlow toProto(io.a2a.spec.ImplicitOAuthFlow domain);
26+
27+
io.a2a.spec.ImplicitOAuthFlow fromProto(io.a2a.grpc.ImplicitOAuthFlow proto);
28+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package io.a2a.grpc.mapper;
2+
3+
import org.mapstruct.CollectionMappingStrategy;
4+
import org.mapstruct.Mapper;
5+
import org.mapstruct.Mapping;
6+
import org.mapstruct.factory.Mappers;
7+
8+
/**
9+
* Mapper between {@link io.a2a.spec.PasswordOAuthFlow} and {@link io.a2a.grpc.PasswordOAuthFlow}.
10+
*/
11+
@Mapper(config = ProtoMapperConfig.class,
12+
collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED)
13+
public interface PasswordOAuthFlowMapper {
14+
15+
PasswordOAuthFlowMapper INSTANCE = Mappers.getMapper(PasswordOAuthFlowMapper.class);
16+
17+
@IgnoreProtobufInternals
18+
@Mapping(target = "refreshUrlBytes", ignore = true)
19+
@Mapping(target = "tokenUrlBytes", ignore = true)
20+
@Mapping(target = "scopesMap", ignore = true)
21+
@Mapping(target = "scopesCount", ignore = true)
22+
@Mapping(target = "removeScopes", ignore = true)
23+
@Mapping(target = "putAllScopes", ignore = true)
24+
@Mapping(target = "mutableScopes", ignore = true)
25+
io.a2a.grpc.PasswordOAuthFlow toProto(io.a2a.spec.PasswordOAuthFlow domain);
26+
27+
io.a2a.spec.PasswordOAuthFlow fromProto(io.a2a.grpc.PasswordOAuthFlow proto);
28+
}

spec-grpc/src/main/java/io/a2a/grpc/mapper/ProtoMapperConfig.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.google.protobuf.*;
44
import com.google.protobuf.util.Timestamps;
55
import org.mapstruct.MapperConfig;
6+
import org.mapstruct.NullValuePropertyMappingStrategy;
67
import org.mapstruct.ReportingPolicy;
78

89
import java.nio.ByteBuffer;
@@ -18,7 +19,10 @@
1819
unmappedTargetPolicy = ReportingPolicy.ERROR,
1920

2021
// 2. Use the default component model (Singleton instance pattern)
21-
componentModel = "default"
22+
componentModel = "default",
23+
24+
// 3. IGNORE null values when mapping to protobuf (builders don't accept null)
25+
nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE
2226
)
2327
public interface ProtoMapperConfig {
2428

spec-grpc/src/main/java/io/a2a/grpc/utils/ProtoUtils.java

Lines changed: 14 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@
1616
import io.a2a.grpc.StreamResponse;
1717
import io.a2a.grpc.mapper.AgentInterfaceMapper;
1818
import io.a2a.grpc.mapper.AgentProviderMapper;
19+
import io.a2a.grpc.mapper.AuthenticationInfoMapper;
20+
import io.a2a.grpc.mapper.AuthorizationCodeOAuthFlowMapper;
21+
import io.a2a.grpc.mapper.ClientCredentialsOAuthFlowMapper;
22+
import io.a2a.grpc.mapper.ImplicitOAuthFlowMapper;
23+
import io.a2a.grpc.mapper.PasswordOAuthFlowMapper;
1924
import io.a2a.spec.APIKeySecurityScheme;
2025
import io.a2a.spec.AgentCapabilities;
2126
import io.a2a.spec.AgentCard;
@@ -49,7 +54,7 @@
4954
import io.a2a.spec.OpenIdConnectSecurityScheme;
5055
import io.a2a.spec.Part;
5156
import io.a2a.spec.PasswordOAuthFlow;
52-
import io.a2a.spec.PushNotificationAuthenticationInfo;
57+
import io.a2a.spec.AuthenticationInfo;
5358
import io.a2a.spec.PushNotificationConfig;
5459
import io.a2a.spec.SecurityScheme;
5560
import io.a2a.spec.StreamingEventKind;
@@ -347,15 +352,8 @@ public static io.a2a.grpc.TaskState taskState(TaskState taskState) {
347352
};
348353
}
349354

350-
private static io.a2a.grpc.AuthenticationInfo authenticationInfo(PushNotificationAuthenticationInfo pushNotificationAuthenticationInfo) {
351-
io.a2a.grpc.AuthenticationInfo.Builder builder = io.a2a.grpc.AuthenticationInfo.newBuilder();
352-
if (pushNotificationAuthenticationInfo.schemes() != null) {
353-
builder.addAllSchemes(pushNotificationAuthenticationInfo.schemes());
354-
}
355-
if (pushNotificationAuthenticationInfo.credentials() != null) {
356-
builder.setCredentials(pushNotificationAuthenticationInfo.credentials());
357-
}
358-
return builder.build();
355+
private static io.a2a.grpc.AuthenticationInfo authenticationInfo(AuthenticationInfo authenticationInfo) {
356+
return AuthenticationInfoMapper.INSTANCE.toProto(authenticationInfo);
359357
}
360358

361359
public static io.a2a.grpc.SendMessageConfiguration messageSendConfiguration(MessageSendConfiguration messageSendConfiguration) {
@@ -537,20 +535,7 @@ private static io.a2a.grpc.OAuthFlows oauthFlows(OAuthFlows oAuthFlows) {
537535
}
538536

539537
private static io.a2a.grpc.AuthorizationCodeOAuthFlow authorizationCodeOAuthFlow(AuthorizationCodeOAuthFlow authorizationCodeOAuthFlow) {
540-
io.a2a.grpc.AuthorizationCodeOAuthFlow.Builder builder = io.a2a.grpc.AuthorizationCodeOAuthFlow.newBuilder();
541-
if (authorizationCodeOAuthFlow.authorizationUrl() != null) {
542-
builder.setAuthorizationUrl(authorizationCodeOAuthFlow.authorizationUrl());
543-
}
544-
if (authorizationCodeOAuthFlow.refreshUrl() != null) {
545-
builder.setRefreshUrl(authorizationCodeOAuthFlow.refreshUrl());
546-
}
547-
if (authorizationCodeOAuthFlow.scopes() != null) {
548-
builder.putAllScopes(authorizationCodeOAuthFlow.scopes());
549-
}
550-
if (authorizationCodeOAuthFlow.tokenUrl() != null) {
551-
builder.setTokenUrl(authorizationCodeOAuthFlow.tokenUrl());
552-
}
553-
return builder.build();
538+
return AuthorizationCodeOAuthFlowMapper.INSTANCE.toProto(authorizationCodeOAuthFlow);
554539
}
555540

556541
public static io.a2a.grpc.ListTaskPushNotificationConfigResponse listTaskPushNotificationConfigResponse(List<TaskPushNotificationConfig> configs) {
@@ -562,45 +547,15 @@ public static io.a2a.grpc.ListTaskPushNotificationConfigResponse listTaskPushNot
562547
}
563548

564549
private static io.a2a.grpc.ClientCredentialsOAuthFlow clientCredentialsOAuthFlow(ClientCredentialsOAuthFlow clientCredentialsOAuthFlow) {
565-
io.a2a.grpc.ClientCredentialsOAuthFlow.Builder builder = io.a2a.grpc.ClientCredentialsOAuthFlow.newBuilder();
566-
if (clientCredentialsOAuthFlow.refreshUrl() != null) {
567-
builder.setRefreshUrl(clientCredentialsOAuthFlow.refreshUrl());
568-
}
569-
if (clientCredentialsOAuthFlow.scopes() != null) {
570-
builder.putAllScopes(clientCredentialsOAuthFlow.scopes());
571-
}
572-
if (clientCredentialsOAuthFlow.tokenUrl() != null) {
573-
builder.setTokenUrl(clientCredentialsOAuthFlow.tokenUrl());
574-
}
575-
return builder.build();
550+
return ClientCredentialsOAuthFlowMapper.INSTANCE.toProto(clientCredentialsOAuthFlow);
576551
}
577552

578553
private static io.a2a.grpc.ImplicitOAuthFlow implicitOAuthFlow(ImplicitOAuthFlow implicitOAuthFlow) {
579-
io.a2a.grpc.ImplicitOAuthFlow.Builder builder = io.a2a.grpc.ImplicitOAuthFlow.newBuilder();
580-
if (implicitOAuthFlow.authorizationUrl() != null) {
581-
builder.setAuthorizationUrl(implicitOAuthFlow.authorizationUrl());
582-
}
583-
if (implicitOAuthFlow.refreshUrl() != null) {
584-
builder.setRefreshUrl(implicitOAuthFlow.refreshUrl());
585-
}
586-
if (implicitOAuthFlow.scopes() != null) {
587-
builder.putAllScopes(implicitOAuthFlow.scopes());
588-
}
589-
return builder.build();
554+
return ImplicitOAuthFlowMapper.INSTANCE.toProto(implicitOAuthFlow);
590555
}
591556

592557
private static io.a2a.grpc.PasswordOAuthFlow passwordOAuthFlow(PasswordOAuthFlow passwordOAuthFlow) {
593-
io.a2a.grpc.PasswordOAuthFlow.Builder builder = io.a2a.grpc.PasswordOAuthFlow.newBuilder();
594-
if (passwordOAuthFlow.refreshUrl() != null) {
595-
builder.setRefreshUrl(passwordOAuthFlow.refreshUrl());
596-
}
597-
if (passwordOAuthFlow.scopes() != null) {
598-
builder.putAllScopes(passwordOAuthFlow.scopes());
599-
}
600-
if (passwordOAuthFlow.tokenUrl() != null) {
601-
builder.setTokenUrl(passwordOAuthFlow.tokenUrl());
602-
}
603-
return builder.build();
558+
return PasswordOAuthFlowMapper.INSTANCE.toProto(passwordOAuthFlow);
604559
}
605560

606561
private static io.a2a.grpc.OpenIdConnectSecurityScheme openIdConnectSecurityScheme(OpenIdConnectSecurityScheme openIdConnectSecurityScheme) {
@@ -890,11 +845,8 @@ private static MessageSendConfiguration messageSendConfiguration(io.a2a.grpc.Sen
890845
return pushNotification(pushNotification, pushNotification.getId());
891846
}
892847

893-
private static PushNotificationAuthenticationInfo authenticationInfo(io.a2a.grpc.AuthenticationInfoOrBuilder authenticationInfo) {
894-
return new PushNotificationAuthenticationInfo(
895-
new ArrayList<>(authenticationInfo.getSchemesList()),
896-
authenticationInfo.getCredentials()
897-
);
848+
private static AuthenticationInfo authenticationInfo(io.a2a.grpc.AuthenticationInfoOrBuilder authenticationInfo) {
849+
return AuthenticationInfoMapper.INSTANCE.fromProto((io.a2a.grpc.AuthenticationInfo) authenticationInfo);
898850
}
899851

900852
public static Task task(io.a2a.grpc.TaskOrBuilder task) {

spec-grpc/src/test/java/io/a2a/grpc/utils/ToProtoTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
import io.a2a.spec.HTTPAuthSecurityScheme;
1414
import io.a2a.spec.Message;
1515
import io.a2a.spec.MessageSendConfiguration;
16-
import io.a2a.spec.PushNotificationAuthenticationInfo;
16+
import io.a2a.spec.AuthenticationInfo;
1717
import io.a2a.spec.PushNotificationConfig;
1818
import io.a2a.spec.Task;
1919
import io.a2a.spec.TaskArtifactUpdateEvent;
@@ -211,7 +211,7 @@ public void convertTaskPushNotificationConfig() {
211211
= new TaskPushNotificationConfig("push-task-123",
212212
new PushNotificationConfig.Builder()
213213
.token("AAAAAA")
214-
.authenticationInfo(new PushNotificationAuthenticationInfo(Collections.singletonList("jwt"), "credentials"))
214+
.authenticationInfo(new AuthenticationInfo(Collections.singletonList("jwt"), "credentials"))
215215
.url("http://example.com")
216216
.id("xyz")
217217
.build());

0 commit comments

Comments
 (0)