1515
1616package software .amazon .smithy .aws .typescript .codegen ;
1717
18+ import static software .amazon .smithy .typescript .codegen .integration .RuntimeClientPlugin .Convention .HAS_CONFIG ;
19+ import static software .amazon .smithy .typescript .codegen .integration .RuntimeClientPlugin .Convention .HAS_MIDDLEWARE ;
20+
1821import java .util .Collections ;
22+ import java .util .List ;
1923import java .util .Map ;
24+ import java .util .Set ;
2025import java .util .function .Consumer ;
2126
2227import software .amazon .smithy .aws .traits .ServiceTrait ;
2328import software .amazon .smithy .codegen .core .SymbolProvider ;
2429import software .amazon .smithy .model .Model ;
25- import software .amazon .smithy .model .shapes .ServiceShape ;
30+ import software .amazon .smithy .model .knowledge .OperationIndex ;
31+ import software .amazon .smithy .model .shapes .OperationShape ;
32+ import software .amazon .smithy .model .shapes .Shape ;
2633import software .amazon .smithy .typescript .codegen .LanguageTarget ;
2734import software .amazon .smithy .typescript .codegen .TypeScriptDependency ;
2835import software .amazon .smithy .typescript .codegen .TypeScriptSettings ;
2936import software .amazon .smithy .typescript .codegen .TypeScriptWriter ;
37+ import software .amazon .smithy .typescript .codegen .integration .RuntimeClientPlugin ;
3038import software .amazon .smithy .typescript .codegen .integration .TypeScriptIntegration ;
39+ import software .amazon .smithy .utils .ListUtils ;
3140import software .amazon .smithy .utils .MapUtils ;
41+ import software .amazon .smithy .utils .SetUtils ;
3242
3343/**
3444 * AWS S3 client configuration.
3545 */
3646public final class AddS3Config implements TypeScriptIntegration {
3747
48+ private static final Set <String > S3_MD5_OPERATIONS = SetUtils .of (
49+ "DeleteObjects" ,
50+ "PutBucketCors" ,
51+ "PutBucketLifecycle" ,
52+ "PutBucketLifecycleConfiguration" ,
53+ "PutBucketPolicy" ,
54+ "PutBucketTagging" ,
55+ "PutBucketReplication"
56+ );
57+
58+ private static final Set <String > SSEC_OPERATIONS = SetUtils .of ("SSECustomerKey" , "CopySourceSSECustomerKey" );
59+
60+ private static final Set <String > NON_BUCKET_ENDPOINT_OPERATIONS = SetUtils .of (
61+ "CreateBucket" ,
62+ "DeleteBucket" ,
63+ "ListBuckets"
64+ );
65+
3866 @ Override
3967 public void addConfigInterfaceFields (TypeScriptSettings settings , Model model , SymbolProvider symbolProvider ,
4068 TypeScriptWriter writer ) {
41- if (!needsS3Config (settings .getService (model ))) {
69+ if (!testServiceId (settings .getService (model ))) {
4270 return ;
4371 }
4472 writer .writeDocs ("Whether to escape request path when signing the request." )
@@ -53,7 +81,7 @@ public void addConfigInterfaceFields(TypeScriptSettings settings, Model model, S
5381 @ Override
5482 public Map <String , Consumer <TypeScriptWriter >> getRuntimeConfigWriters (TypeScriptSettings settings , Model model ,
5583 SymbolProvider symbolProvider , LanguageTarget target ) {
56- if (!needsS3Config (settings .getService (model ))) {
84+ if (!testServiceId (settings .getService (model ))) {
5785 return Collections .emptyMap ();
5886 }
5987 switch (target ) {
@@ -77,8 +105,71 @@ public Map<String, Consumer<TypeScriptWriter>> getRuntimeConfigWriters(TypeScrip
77105 }
78106 }
79107
80- private static boolean needsS3Config (ServiceShape service ) {
81- String serviceId = service .getTrait (ServiceTrait .class ).map (ServiceTrait ::getSdkId ).orElse ("" );
82- return serviceId .equals ("S3" );
108+ @ Override
109+ public List <RuntimeClientPlugin > getClientPlugins () {
110+ return ListUtils .of (
111+ RuntimeClientPlugin .builder ()
112+ .withConventions (AwsDependency .S3_MIDDLEWARE .dependency , "ValidateBucketName" ,
113+ HAS_MIDDLEWARE )
114+ .servicePredicate ((m , s ) -> testServiceId (s ))
115+ .build (),
116+ RuntimeClientPlugin .builder ()
117+ .withConventions (AwsDependency .S3_MIDDLEWARE .dependency , "UseRegionalEndpoint" ,
118+ HAS_MIDDLEWARE )
119+ .servicePredicate ((m , s ) -> testServiceId (s ))
120+ .build (),
121+ RuntimeClientPlugin .builder ()
122+ .withConventions (AwsDependency .ADD_EXPECT_CONTINUE .dependency , "AddExpectContinue" ,
123+ HAS_MIDDLEWARE )
124+ .servicePredicate ((m , s ) -> testServiceId (s ))
125+ .build (),
126+ RuntimeClientPlugin .builder ()
127+ .withConventions (AwsDependency .SSEC_MIDDLEWARE .dependency , "Ssec" , HAS_MIDDLEWARE )
128+ .operationPredicate ((m , s , o ) -> testInputContainsMember (m , o , SSEC_OPERATIONS )
129+ && testServiceId (s ))
130+ .build (),
131+ RuntimeClientPlugin .builder ()
132+ .withConventions (AwsDependency .LOCATION_CONSTRAINT .dependency , "LocationConstraint" ,
133+ HAS_MIDDLEWARE )
134+ .operationPredicate ((m , s , o ) -> o .getId ().getName ().equals ("CreateBucket" )
135+ && testServiceId (s ))
136+ .build (),
137+ /**
138+ * BUCKET_ENDPOINT_MIDDLEWARE needs two separate plugins. The first resolves the config in the client.
139+ * The second applies the middleware to bucket endpoint operations.
140+ */
141+ RuntimeClientPlugin .builder ()
142+ .withConventions (AwsDependency .BUCKET_ENDPOINT_MIDDLEWARE .dependency , "BucketEndpoint" ,
143+ HAS_CONFIG )
144+ .servicePredicate ((m , s ) -> testServiceId (s ))
145+ .build (),
146+ RuntimeClientPlugin .builder ()
147+ .withConventions (AwsDependency .BUCKET_ENDPOINT_MIDDLEWARE .dependency , "BucketEndpoint" ,
148+ HAS_MIDDLEWARE )
149+ .operationPredicate ((m , s , o ) -> !NON_BUCKET_ENDPOINT_OPERATIONS .contains (o .getId ().getName ())
150+ && testServiceId (s ))
151+ .build (),
152+ RuntimeClientPlugin .builder ()
153+ .withConventions (AwsDependency .BODY_CHECKSUM .dependency , "ApplyMd5BodyChecksum" ,
154+ HAS_MIDDLEWARE )
155+ .operationPredicate ((m , s , o ) -> S3_MD5_OPERATIONS .contains (o .getId ().getName ())
156+ && testServiceId (s ))
157+ .build ()
158+ );
159+ }
160+
161+ private static boolean testInputContainsMember (
162+ Model model ,
163+ OperationShape operationShape ,
164+ Set <String > expectedMemberNames
165+ ) {
166+ OperationIndex operationIndex = OperationIndex .of (model );
167+ return operationIndex .getInput (operationShape )
168+ .filter (input -> input .getMemberNames ().stream ().anyMatch (expectedMemberNames ::contains ))
169+ .isPresent ();
170+ }
171+
172+ private static boolean testServiceId (Shape serviceShape ) {
173+ return serviceShape .getTrait (ServiceTrait .class ).map (ServiceTrait ::getSdkId ).orElse ("" ).equals ("S3" );
83174 }
84175}
0 commit comments