Skip to content

Commit ec18a7a

Browse files
committed
Normalize map keys for shapes targeted by HeaderPrefix during deserialization.
1 parent 77a8ca7 commit ec18a7a

File tree

8 files changed

+310
-31
lines changed

8 files changed

+310
-31
lines changed

codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/GoWriter.java

Lines changed: 53 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.util.Arrays;
2020
import java.util.Collection;
2121
import java.util.List;
22+
import java.util.Optional;
2223
import java.util.StringJoiner;
2324
import java.util.function.BiFunction;
2425
import java.util.logging.Logger;
@@ -28,10 +29,13 @@
2829
import software.amazon.smithy.codegen.core.SymbolDependency;
2930
import software.amazon.smithy.codegen.core.SymbolDependencyContainer;
3031
import software.amazon.smithy.codegen.core.SymbolReference;
32+
import software.amazon.smithy.go.codegen.knowledge.GoUsageIndex;
3133
import software.amazon.smithy.model.Model;
3234
import software.amazon.smithy.model.shapes.MemberShape;
3335
import software.amazon.smithy.model.shapes.Shape;
36+
import software.amazon.smithy.model.traits.DeprecatedTrait;
3437
import software.amazon.smithy.model.traits.DocumentationTrait;
38+
import software.amazon.smithy.model.traits.HttpPrefixHeadersTrait;
3539
import software.amazon.smithy.model.traits.MediaTypeTrait;
3640
import software.amazon.smithy.model.traits.RequiredTrait;
3741
import software.amazon.smithy.model.traits.StringTrait;
@@ -290,24 +294,60 @@ boolean writePackageShapeDocs(Shape shape) {
290294
* @return Returns true if docs were written.
291295
*/
292296
boolean writeMemberDocs(Model model, MemberShape member) {
293-
return member.getMemberTrait(model, DocumentationTrait.class)
297+
boolean hasDocs;
298+
299+
hasDocs = member.getMemberTrait(model, DocumentationTrait.class)
294300
.map(DocumentationTrait::getValue)
295301
.map(docs -> {
296302
writeDocs(docs);
297-
member.getMemberTrait(model, MediaTypeTrait.class)
298-
.map(StringTrait::getValue)
299-
.ifPresent(mediaType -> writeDocs(
300-
"\n\nThis value conforms to the media type: " + mediaType));
301-
302-
member.getMemberTrait(model, RequiredTrait.class)
303-
.ifPresent((value) -> {
304-
if (docs.length() != 0) {
305-
writeDocs("");
306-
}
307-
writeDocs("This member is required.");
308-
});
309303
return true;
310304
}).orElse(false);
305+
306+
Optional<String> stringOptional = member.getMemberTrait(model, MediaTypeTrait.class)
307+
.map(StringTrait::getValue);
308+
if (stringOptional.isPresent()) {
309+
if (hasDocs) {
310+
writeDocs("");
311+
}
312+
writeDocs("This value conforms to the media type: " + stringOptional.get());
313+
hasDocs = true;
314+
}
315+
316+
GoUsageIndex usageIndex = GoUsageIndex.of(model);
317+
if (usageIndex.isUsedForOutput(member)) {
318+
if (member.getMemberTrait(model,
319+
HttpPrefixHeadersTrait.class).isPresent()) {
320+
if (hasDocs) {
321+
writeDocs("");
322+
}
323+
writeDocs("Map keys will be normalized to lower-case.");
324+
hasDocs = true;
325+
}
326+
}
327+
328+
if (member.getMemberTrait(model, RequiredTrait.class).isPresent()) {
329+
if (hasDocs) {
330+
writeDocs("");
331+
}
332+
writeDocs("This member is required.");
333+
hasDocs = true;
334+
}
335+
336+
Optional<DeprecatedTrait> deprecatedTrait = member.getMemberTrait(model, DeprecatedTrait.class);
337+
if (deprecatedTrait.isPresent()) {
338+
if (hasDocs) {
339+
writeDocs("");
340+
}
341+
final String defaultMessage = "This member has been deprecated.";
342+
writeDocs("Deprecated: " + deprecatedTrait.get().getMessage().map(s -> {
343+
if (s.length() == 0) {
344+
return defaultMessage;
345+
}
346+
return s;
347+
}).orElse(defaultMessage));
348+
}
349+
350+
return hasDocs;
311351
}
312352

313353
@Override

codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/OperationGenerator.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import software.amazon.smithy.model.shapes.OperationShape;
3030
import software.amazon.smithy.model.shapes.ServiceShape;
3131
import software.amazon.smithy.model.shapes.StructureShape;
32+
import software.amazon.smithy.model.traits.DeprecatedTrait;
3233

3334
/**
3435
* Generates a client operation and associated custom shapes.
@@ -91,7 +92,20 @@ public void run() {
9192
Symbol outputSymbol = symbolProvider.toSymbol(outputShape);
9293

9394
// Generate operation method
94-
writer.writeShapeDocs(operation);
95+
final boolean hasDocs = writer.writeShapeDocs(operation);
96+
operation.getTrait(DeprecatedTrait.class)
97+
.ifPresent(trait -> {
98+
if (hasDocs) {
99+
writer.writeDocs("");
100+
}
101+
final String defaultMessage = "This operation has been deprecated.";
102+
writer.writeDocs("Deprecated: " + trait.getMessage().map(s -> {
103+
if (s.length() == 0) {
104+
return defaultMessage;
105+
}
106+
return s;
107+
}).orElse(defaultMessage));
108+
});
95109
Symbol contextSymbol = SymbolUtils.createValueSymbolBuilder("Context", SmithyGoDependency.CONTEXT).build();
96110
writer.openBlock("func (c $P) $T(ctx $T, params $P, optFns ...func(*Options)) ($P, error) {", "}",
97111
serviceSymbol, operationSymbol, contextSymbol, inputSymbol, outputSymbol, () -> {

codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/ShapeValueGenerator.java

Lines changed: 130 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717

1818
package software.amazon.smithy.go.codegen;
1919

20+
import java.util.List;
2021
import java.util.Map;
22+
import java.util.Optional;
2123
import java.util.logging.Logger;
2224
import software.amazon.smithy.codegen.core.CodegenException;
2325
import software.amazon.smithy.codegen.core.Symbol;
@@ -32,24 +34,31 @@
3234
import software.amazon.smithy.model.node.NumberNode;
3335
import software.amazon.smithy.model.node.ObjectNode;
3436
import software.amazon.smithy.model.node.StringNode;
37+
import software.amazon.smithy.model.shapes.MapShape;
3538
import software.amazon.smithy.model.shapes.MemberShape;
3639
import software.amazon.smithy.model.shapes.Shape;
3740
import software.amazon.smithy.model.shapes.ShapeType;
3841
import software.amazon.smithy.model.shapes.SimpleShape;
3942
import software.amazon.smithy.model.shapes.StructureShape;
4043
import software.amazon.smithy.model.shapes.UnionShape;
4144
import software.amazon.smithy.model.traits.EnumTrait;
45+
import software.amazon.smithy.model.traits.HttpPrefixHeadersTrait;
4246
import software.amazon.smithy.model.traits.StreamingTrait;
47+
import software.amazon.smithy.model.traits.Trait;
48+
import software.amazon.smithy.utils.ListUtils;
49+
import software.amazon.smithy.utils.OptionalUtils;
50+
import software.amazon.smithy.utils.SmithyBuilder;
4351

4452
/**
4553
* Generates a shape type declaration based on the parameters provided.
4654
*/
4755
public final class ShapeValueGenerator {
4856
private static final Logger LOGGER = Logger.getLogger(ShapeValueGenerator.class.getName());
4957

50-
protected final Model model;
51-
protected final SymbolProvider symbolProvider;
52-
protected final GoPointableIndex pointableIndex;
58+
private final Model model;
59+
private final SymbolProvider symbolProvider;
60+
private final GoPointableIndex pointableIndex;
61+
private final Config config;
5362

5463
/**
5564
* Initializes a shape value generator.
@@ -58,9 +67,21 @@ public final class ShapeValueGenerator {
5867
* @param symbolProvider the symbol provider.
5968
*/
6069
public ShapeValueGenerator(Model model, SymbolProvider symbolProvider) {
70+
this(model, symbolProvider, Config.builder().build());
71+
}
72+
73+
/**
74+
* Initializes a shape value generator.
75+
*
76+
* @param model the Smithy model references.
77+
* @param symbolProvider the symbol provider.
78+
* @param config the shape value generator config.
79+
*/
80+
public ShapeValueGenerator(Model model, SymbolProvider symbolProvider, Config config) {
6181
this.model = model;
6282
this.symbolProvider = symbolProvider;
6383
this.pointableIndex = GoPointableIndex.of(model);
84+
this.config = config;
6485
}
6586

6687
/**
@@ -79,7 +100,8 @@ public void writePointableStructureShapeValueInline(GoWriter writer, StructureSh
79100
// not within the context of a member shape reference.
80101
Symbol symbol = symbolProvider.toSymbol(shape);
81102
writer.write("&$T{", symbol);
82-
params.accept(new ShapeValueNodeVisitor(writer, this, shape));
103+
params.accept(new ShapeValueNodeVisitor(writer, this, shape, ListUtils.copyOf(shape.getAllTraits().values()),
104+
config));
83105
writer.writeInline("}");
84106
}
85107

@@ -149,7 +171,8 @@ protected void structDeclShapeValue(GoWriter writer, MemberShape member, Node pa
149171

150172
String addr = CodegenUtils.asAddressIfAddressable(model, pointableIndex, member, "");
151173
writer.write("$L$T{", addr, symbol);
152-
params.accept(new ShapeValueNodeVisitor(writer, this, model.expectShape(member.getTarget())));
174+
params.accept(new ShapeValueNodeVisitor(writer, this, model.expectShape(member.getTarget()),
175+
ListUtils.copyOf(member.getAllTraits().values()), config));
153176
writer.writeInline("}");
154177
}
155178

@@ -197,7 +220,8 @@ protected void unionDeclShapeValue(GoWriter writer, MemberShape member, ObjectNo
197220
*/
198221
protected void listDeclShapeValue(GoWriter writer, MemberShape member, Node params) {
199222
writer.write("$P{", symbolProvider.toSymbol(member));
200-
params.accept(new ShapeValueNodeVisitor(writer, this, model.expectShape(member.getTarget())));
223+
params.accept(new ShapeValueNodeVisitor(writer, this, model.expectShape(member.getTarget()),
224+
ListUtils.copyOf(member.getAllTraits().values()), config));
201225
writer.writeInline("}");
202226
}
203227

@@ -210,7 +234,8 @@ protected void listDeclShapeValue(GoWriter writer, MemberShape member, Node para
210234
*/
211235
protected void mapDeclShapeValue(GoWriter writer, MemberShape member, Node params) {
212236
writer.write("$P{", symbolProvider.toSymbol(member));
213-
params.accept(new ShapeValueNodeVisitor(writer, this, model.expectShape(member.getTarget())));
237+
params.accept(new ShapeValueNodeVisitor(writer, this, model.expectShape(member.getTarget()),
238+
ListUtils.copyOf(member.getAllTraits().values()), config));
214239
writer.writeInline("}");
215240
}
216241

@@ -327,17 +352,58 @@ protected void writeScalarValueInline(GoWriter writer, MemberShape member, Node
327352
break;
328353
}
329354

330-
params.accept(new ShapeValueNodeVisitor(writer, this, target));
355+
params.accept(new ShapeValueNodeVisitor(writer, this, target,
356+
ListUtils.copyOf(member.getAllTraits().values()), config));
331357
writer.writeInline(closing);
332358
}
333359

360+
/**
361+
* Configuration that determines how shapes values are generated.
362+
*/
363+
public static final class Config {
364+
private final boolean normalizeHttpPrefixHeaderKeys;
365+
366+
private Config(Builder builder) {
367+
normalizeHttpPrefixHeaderKeys = builder.normalizeHttpPrefixHeaderKeys;
368+
}
369+
370+
public static Builder builder() {
371+
return new Builder();
372+
}
373+
374+
/**
375+
* Returns whether maps with the httpPrefixHeader trait should have their keys normalized.
376+
*
377+
* @return whether to normalize http prefix header keys
378+
*/
379+
public boolean isNormalizeHttpPrefixHeaderKeys() {
380+
return normalizeHttpPrefixHeaderKeys;
381+
}
382+
383+
public static final class Builder implements SmithyBuilder<Config> {
384+
private boolean normalizeHttpPrefixHeaderKeys;
385+
386+
public Builder normalizeHttpPrefixHeaderKeys(boolean normalizeHttpPrefixHeaderKeys) {
387+
this.normalizeHttpPrefixHeaderKeys = normalizeHttpPrefixHeaderKeys;
388+
return this;
389+
}
390+
391+
@Override
392+
public Config build() {
393+
return new Config(this);
394+
}
395+
}
396+
}
397+
334398
/**
335399
* NodeVisitor to walk shape value declarations with node values.
336400
*/
337401
private final class ShapeValueNodeVisitor implements NodeVisitor<Void> {
338-
GoWriter writer;
339-
ShapeValueGenerator valueGen;
340-
Shape currentShape;
402+
private final GoWriter writer;
403+
private final ShapeValueGenerator valueGen;
404+
private final Shape currentShape;
405+
private final List<Trait> traits;
406+
private final Config config;
341407

342408
/**
343409
* Initializes shape value visitor.
@@ -347,9 +413,42 @@ private final class ShapeValueNodeVisitor implements NodeVisitor<Void> {
347413
* @param shape the shape that visiting is relative to.
348414
*/
349415
private ShapeValueNodeVisitor(GoWriter writer, ShapeValueGenerator valueGen, Shape shape) {
416+
this(writer, valueGen, shape, ListUtils.of());
417+
}
418+
419+
/**
420+
* Initializes shape value visitor.
421+
*
422+
* @param writer writer to write generated code with.
423+
* @param valueGen shape value generator.
424+
* @param shape the shape that visiting is relative to.
425+
* @param traits the traits applied to the target shape by a MemberShape.
426+
*/
427+
private ShapeValueNodeVisitor(GoWriter writer, ShapeValueGenerator valueGen, Shape shape, List<Trait> traits) {
428+
this(writer, valueGen, shape, traits, Config.builder().build());
429+
}
430+
431+
/**
432+
* Initializes shape value visitor.
433+
*
434+
* @param writer writer to write generated code with.
435+
* @param valueGen shape value generator.
436+
* @param shape the shape that visiting is relative to.
437+
* @param traits the traits applied to the target shape by a MemberShape.
438+
* @param config the shape value generator config.
439+
*/
440+
private ShapeValueNodeVisitor(
441+
GoWriter writer,
442+
ShapeValueGenerator valueGen,
443+
Shape shape,
444+
List<Trait> traits,
445+
Config config
446+
) {
350447
this.writer = writer;
351448
this.valueGen = valueGen;
352449
this.currentShape = shape;
450+
this.traits = traits;
451+
this.config = config;
353452
}
354453

355454
/**
@@ -395,10 +494,18 @@ public Void objectNode(ObjectNode node) {
395494
break;
396495

397496
case MAP:
398-
member = this.currentShape.asMapShape().get().getValue();
497+
MapShape mapShape = this.currentShape.asMapShape().get();
498+
499+
String keyValue = keyNode.getValue();
500+
if (config.isNormalizeHttpPrefixHeaderKeys()) {
501+
keyValue = OptionalUtils.or(getTrait(HttpPrefixHeadersTrait.class),
502+
() -> mapShape.getTrait(HttpPrefixHeadersTrait.class))
503+
.map(httpPrefixHeadersTrait -> keyNode.getValue().toLowerCase())
504+
.orElse(keyValue);
505+
}
399506

400-
writer.write("$S: ", keyNode.getValue());
401-
valueGen.writeMemberValueInline(writer, member, valueNode);
507+
writer.write("$S: ", keyValue);
508+
valueGen.writeMemberValueInline(writer, mapShape.getValue(), valueNode);
402509
writer.write(",");
403510
break;
404511

@@ -523,6 +630,15 @@ private void writeInlineBigIntegerInit(GoWriter writer, Object value) {
523630
+ "}()",
524631
value, value);
525632
}
633+
634+
private <T extends Trait> Optional<T> getTrait(Class<T> traitClass) {
635+
for (Trait trait : traits) {
636+
if (traitClass.isInstance(trait)) {
637+
return Optional.of((T) trait);
638+
}
639+
}
640+
return Optional.empty();
641+
}
526642
}
527643

528644
}

0 commit comments

Comments
 (0)