Skip to content

Commit 2f8600c

Browse files
authored
Add granular allowlist for underscores in member names (#6677)
* Add granular allowlist for underscores in member names * Adding valitation errors for when both underscore configs are supplied * add javadoc * Fix javadoc
1 parent a80dc94 commit 2f8600c

File tree

6 files changed

+123
-3
lines changed

6 files changed

+123
-3
lines changed

codegen/src/main/java/software/amazon/awssdk/codegen/CodeGenerator.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
import java.io.IOException;
2121
import java.io.PrintWriter;
2222
import java.util.ArrayList;
23-
import java.util.Collections;
23+
import java.util.Arrays;
2424
import java.util.List;
2525
import java.util.concurrent.ForkJoinTask;
2626
import software.amazon.awssdk.codegen.emitters.GeneratorTask;
@@ -29,6 +29,7 @@
2929
import software.amazon.awssdk.codegen.internal.Jackson;
3030
import software.amazon.awssdk.codegen.internal.Utils;
3131
import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel;
32+
import software.amazon.awssdk.codegen.validation.CustomizationConfigValidator;
3233
import software.amazon.awssdk.codegen.validation.ModelInvalidException;
3334
import software.amazon.awssdk.codegen.validation.ModelValidationContext;
3435
import software.amazon.awssdk.codegen.validation.ModelValidationReport;
@@ -41,8 +42,9 @@ public class CodeGenerator {
4142
private static final Logger log = Logger.loggerFor(CodeGenerator.class);
4243
private static final String MODEL_DIR_NAME = "models";
4344

44-
private static final List<ModelValidator> DEFAULT_MODEL_VALIDATORS = Collections.singletonList(
45-
new SharedModelsValidator()
45+
private static final List<ModelValidator> DEFAULT_MODEL_VALIDATORS = Arrays.asList(
46+
new SharedModelsValidator(),
47+
new CustomizationConfigValidator()
4648
);
4749

4850
private final C2jModels c2jModels;

codegen/src/main/java/software/amazon/awssdk/codegen/model/config/customization/CustomizationConfig.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,11 @@ public class CustomizationConfig {
363363
*/
364364
private boolean enableEndpointProviderUriCaching;
365365

366+
/**
367+
* List of specific shape or member names that are allowed to contain underscores.
368+
*/
369+
private List<String> allowedUnderscoreNames = new ArrayList<>();
370+
366371
private CustomizationConfig() {
367372
}
368373

@@ -951,4 +956,12 @@ public boolean getEnableEndpointProviderUriCaching() {
951956
public void setEnableEndpointProviderUriCaching(boolean enableEndpointProviderUriCaching) {
952957
this.enableEndpointProviderUriCaching = enableEndpointProviderUriCaching;
953958
}
959+
960+
public List<String> getAllowedUnderscoreNames() {
961+
return allowedUnderscoreNames;
962+
}
963+
964+
public void setAllowedUnderscoreNames(List<String> allowedUnderscoreNames) {
965+
this.allowedUnderscoreNames = allowedUnderscoreNames;
966+
}
954967
}

codegen/src/main/java/software/amazon/awssdk/codegen/naming/DefaultNamingStrategy.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import java.util.Arrays;
2929
import java.util.Collections;
3030
import java.util.HashSet;
31+
import java.util.List;
3132
import java.util.Locale;
3233
import java.util.Objects;
3334
import java.util.Optional;
@@ -499,6 +500,10 @@ private void validateCustomerVisibleName(String name, String location) {
499500

500501
if (name.contains("_")) {
501502
UnderscoresInNameBehavior behavior = customizationConfig.getUnderscoresInNameBehavior();
503+
List<String> allowedNames = customizationConfig.getAllowedUnderscoreNames();
504+
if (allowedNames != null && allowedNames.contains(name)) {
505+
return;
506+
}
502507

503508
String supportedBehaviors = Arrays.toString(UnderscoresInNameBehavior.values());
504509
if (behavior == null) {
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
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+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.codegen.validation;
17+
18+
import java.util.Collections;
19+
import java.util.List;
20+
import software.amazon.awssdk.codegen.model.config.customization.CustomizationConfig;
21+
import software.amazon.awssdk.codegen.model.config.customization.UnderscoresInNameBehavior;
22+
23+
/**
24+
* Validator that ensures CustomizationConfig settings are not conflicting. This validator returns a validation
25+
* entry if mutually exclusive customization options are set simultaneously, such as both the global
26+
* underscoresInNameBehavior and the granular allowedUnderscoreNames list.
27+
*/
28+
public final class CustomizationConfigValidator implements ModelValidator {
29+
@Override
30+
public List<ValidationEntry> validateModels(ModelValidationContext context) {
31+
CustomizationConfig config = context.intermediateModel().getCustomizationConfig();
32+
33+
if (config.getUnderscoresInNameBehavior() == UnderscoresInNameBehavior.ALLOW &&
34+
config.getAllowedUnderscoreNames() != null &&
35+
!config.getAllowedUnderscoreNames().isEmpty()) {
36+
37+
return Collections.singletonList(
38+
ValidationEntry.create(
39+
ValidationErrorId.INVALID_CODEGEN_CUSTOMIZATION,
40+
ValidationErrorSeverity.DANGER,
41+
"Cannot set both 'underscoresInNameBehavior=ALLOW' and 'allowedUnderscoreNames'. " +
42+
"Use 'allowedUnderscoreNames' for granular control or 'underscoresInNameBehavior=ALLOW' for all underscores."
43+
)
44+
);
45+
}
46+
47+
return Collections.emptyList();
48+
}
49+
50+
}

codegen/src/test/java/software/amazon/awssdk/codegen/CodeGeneratorTest.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import org.junit.jupiter.api.Test;
4242
import software.amazon.awssdk.codegen.internal.Jackson;
4343
import software.amazon.awssdk.codegen.model.config.customization.CustomizationConfig;
44+
import software.amazon.awssdk.codegen.model.config.customization.UnderscoresInNameBehavior;
4445
import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel;
4546
import software.amazon.awssdk.codegen.model.rules.endpoints.EndpointTestSuiteModel;
4647
import software.amazon.awssdk.codegen.model.service.ServiceModel;
@@ -97,6 +98,24 @@ void execute_invokesModelValidators() {
9798
verify(mockValidator).validateModels(any());
9899
}
99100

101+
@Test
102+
void execute_conflictingUnderscoreConfigs_throwsValidationError() {
103+
CustomizationConfig config = CustomizationConfig.create();
104+
config.setAllowedUnderscoreNames(Collections.singletonList("foo_bar"));
105+
config.setUnderscoresInNameBehavior(UnderscoresInNameBehavior.ALLOW);
106+
107+
C2jModels referenceModels = ClientTestModels.awsJsonServiceC2jModels();
108+
109+
C2jModels modelsWithConflict = C2jModels.builder()
110+
.customizationConfig(config)
111+
.serviceModel(referenceModels.serviceModel())
112+
.build();
113+
114+
assertThatThrownBy(() -> generateCodeFromC2jModels(modelsWithConflict, outputDir))
115+
.isInstanceOf(RuntimeException.class)
116+
.hasMessageContaining("Validation failed");
117+
}
118+
100119
@Test
101120
void execute_c2jModelsAndIntermediateModel_generateSameCode() throws IOException {
102121
Path c2jModelsOutputDir = outputDir.resolve("c2jModels");

codegen/src/test/java/software/amazon/awssdk/codegen/naming/DefaultNamingStrategyTest.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
import java.util.ArrayList;
2727
import java.util.Arrays;
28+
import java.util.List;
2829
import java.util.Map;
2930
import java.util.function.Consumer;
3031
import org.junit.Before;
@@ -337,6 +338,36 @@ public void validateAllowsUnderscoresWithCustomization() {
337338
strategy.validateCustomerVisibleNaming(model);
338339
}
339340

341+
@Test
342+
public void validateAllowsSpecificUnderscoresWithAllowlist() {
343+
CustomizationConfig customization =
344+
CustomizationConfig.create();
345+
customization.setAllowedUnderscoreNames(Arrays.asList("checksumXXHASH3_64", "foo_bar"));
346+
347+
NamingStrategy strategy = new DefaultNamingStrategy(serviceModel, customization);
348+
Metadata metadata = new Metadata();
349+
350+
metadata.setAsyncBuilderInterface("foo_bar");
351+
IntermediateModel model = new IntermediateModel();
352+
model.setMetadata(metadata);
353+
strategy.validateCustomerVisibleNaming(model);
354+
}
355+
356+
@Test
357+
public void validateRejectsSpecificUnderscoresWithAllowlist() {
358+
CustomizationConfig customization =
359+
CustomizationConfig.create();
360+
customization.setAllowedUnderscoreNames(Arrays.asList("checksumXXHASH3_64", "foo_bar"));
361+
362+
NamingStrategy strategy = new DefaultNamingStrategy(serviceModel, customization);
363+
Metadata metadata = new Metadata();
364+
365+
metadata.setAsyncBuilderInterface("fizz_buzz");
366+
IntermediateModel model = new IntermediateModel();
367+
model.setMetadata(metadata);
368+
assertThatThrownBy(() -> strategy.validateCustomerVisibleNaming(model)).isInstanceOf(RuntimeException.class);
369+
}
370+
340371
@Test
341372
public void getSigningNameForEnvironmentVariables_convertsDashAndUppercases() {
342373
when(serviceModel.getMetadata()).thenReturn(serviceMetadata);

0 commit comments

Comments
 (0)