Skip to content

Commit

Permalink
Normalize map keys for shapes targeted by HeaderPrefix during deseria…
Browse files Browse the repository at this point in the history
…lization.
  • Loading branch information
skmcgrail committed Dec 17, 2020
1 parent 77a8ca7 commit ec18a7a
Show file tree
Hide file tree
Showing 8 changed files with 310 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.StringJoiner;
import java.util.function.BiFunction;
import java.util.logging.Logger;
Expand All @@ -28,10 +29,13 @@
import software.amazon.smithy.codegen.core.SymbolDependency;
import software.amazon.smithy.codegen.core.SymbolDependencyContainer;
import software.amazon.smithy.codegen.core.SymbolReference;
import software.amazon.smithy.go.codegen.knowledge.GoUsageIndex;
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.shapes.MemberShape;
import software.amazon.smithy.model.shapes.Shape;
import software.amazon.smithy.model.traits.DeprecatedTrait;
import software.amazon.smithy.model.traits.DocumentationTrait;
import software.amazon.smithy.model.traits.HttpPrefixHeadersTrait;
import software.amazon.smithy.model.traits.MediaTypeTrait;
import software.amazon.smithy.model.traits.RequiredTrait;
import software.amazon.smithy.model.traits.StringTrait;
Expand Down Expand Up @@ -290,24 +294,60 @@ boolean writePackageShapeDocs(Shape shape) {
* @return Returns true if docs were written.
*/
boolean writeMemberDocs(Model model, MemberShape member) {
return member.getMemberTrait(model, DocumentationTrait.class)
boolean hasDocs;

hasDocs = member.getMemberTrait(model, DocumentationTrait.class)
.map(DocumentationTrait::getValue)
.map(docs -> {
writeDocs(docs);
member.getMemberTrait(model, MediaTypeTrait.class)
.map(StringTrait::getValue)
.ifPresent(mediaType -> writeDocs(
"\n\nThis value conforms to the media type: " + mediaType));

member.getMemberTrait(model, RequiredTrait.class)
.ifPresent((value) -> {
if (docs.length() != 0) {
writeDocs("");
}
writeDocs("This member is required.");
});
return true;
}).orElse(false);

Optional<String> stringOptional = member.getMemberTrait(model, MediaTypeTrait.class)
.map(StringTrait::getValue);
if (stringOptional.isPresent()) {
if (hasDocs) {
writeDocs("");
}
writeDocs("This value conforms to the media type: " + stringOptional.get());
hasDocs = true;
}

GoUsageIndex usageIndex = GoUsageIndex.of(model);
if (usageIndex.isUsedForOutput(member)) {
if (member.getMemberTrait(model,
HttpPrefixHeadersTrait.class).isPresent()) {
if (hasDocs) {
writeDocs("");
}
writeDocs("Map keys will be normalized to lower-case.");
hasDocs = true;
}
}

if (member.getMemberTrait(model, RequiredTrait.class).isPresent()) {
if (hasDocs) {
writeDocs("");
}
writeDocs("This member is required.");
hasDocs = true;
}

Optional<DeprecatedTrait> deprecatedTrait = member.getMemberTrait(model, DeprecatedTrait.class);
if (deprecatedTrait.isPresent()) {
if (hasDocs) {
writeDocs("");
}
final String defaultMessage = "This member has been deprecated.";
writeDocs("Deprecated: " + deprecatedTrait.get().getMessage().map(s -> {
if (s.length() == 0) {
return defaultMessage;
}
return s;
}).orElse(defaultMessage));
}

return hasDocs;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import software.amazon.smithy.model.shapes.OperationShape;
import software.amazon.smithy.model.shapes.ServiceShape;
import software.amazon.smithy.model.shapes.StructureShape;
import software.amazon.smithy.model.traits.DeprecatedTrait;

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

// Generate operation method
writer.writeShapeDocs(operation);
final boolean hasDocs = writer.writeShapeDocs(operation);
operation.getTrait(DeprecatedTrait.class)
.ifPresent(trait -> {
if (hasDocs) {
writer.writeDocs("");
}
final String defaultMessage = "This operation has been deprecated.";
writer.writeDocs("Deprecated: " + trait.getMessage().map(s -> {
if (s.length() == 0) {
return defaultMessage;
}
return s;
}).orElse(defaultMessage));
});
Symbol contextSymbol = SymbolUtils.createValueSymbolBuilder("Context", SmithyGoDependency.CONTEXT).build();
writer.openBlock("func (c $P) $T(ctx $T, params $P, optFns ...func(*Options)) ($P, error) {", "}",
serviceSymbol, operationSymbol, contextSymbol, inputSymbol, outputSymbol, () -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@

package software.amazon.smithy.go.codegen;

import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.logging.Logger;
import software.amazon.smithy.codegen.core.CodegenException;
import software.amazon.smithy.codegen.core.Symbol;
Expand All @@ -32,24 +34,31 @@
import software.amazon.smithy.model.node.NumberNode;
import software.amazon.smithy.model.node.ObjectNode;
import software.amazon.smithy.model.node.StringNode;
import software.amazon.smithy.model.shapes.MapShape;
import software.amazon.smithy.model.shapes.MemberShape;
import software.amazon.smithy.model.shapes.Shape;
import software.amazon.smithy.model.shapes.ShapeType;
import software.amazon.smithy.model.shapes.SimpleShape;
import software.amazon.smithy.model.shapes.StructureShape;
import software.amazon.smithy.model.shapes.UnionShape;
import software.amazon.smithy.model.traits.EnumTrait;
import software.amazon.smithy.model.traits.HttpPrefixHeadersTrait;
import software.amazon.smithy.model.traits.StreamingTrait;
import software.amazon.smithy.model.traits.Trait;
import software.amazon.smithy.utils.ListUtils;
import software.amazon.smithy.utils.OptionalUtils;
import software.amazon.smithy.utils.SmithyBuilder;

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

protected final Model model;
protected final SymbolProvider symbolProvider;
protected final GoPointableIndex pointableIndex;
private final Model model;
private final SymbolProvider symbolProvider;
private final GoPointableIndex pointableIndex;
private final Config config;

/**
* Initializes a shape value generator.
Expand All @@ -58,9 +67,21 @@ public final class ShapeValueGenerator {
* @param symbolProvider the symbol provider.
*/
public ShapeValueGenerator(Model model, SymbolProvider symbolProvider) {
this(model, symbolProvider, Config.builder().build());
}

/**
* Initializes a shape value generator.
*
* @param model the Smithy model references.
* @param symbolProvider the symbol provider.
* @param config the shape value generator config.
*/
public ShapeValueGenerator(Model model, SymbolProvider symbolProvider, Config config) {
this.model = model;
this.symbolProvider = symbolProvider;
this.pointableIndex = GoPointableIndex.of(model);
this.config = config;
}

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

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

String addr = CodegenUtils.asAddressIfAddressable(model, pointableIndex, member, "");
writer.write("$L$T{", addr, symbol);
params.accept(new ShapeValueNodeVisitor(writer, this, model.expectShape(member.getTarget())));
params.accept(new ShapeValueNodeVisitor(writer, this, model.expectShape(member.getTarget()),
ListUtils.copyOf(member.getAllTraits().values()), config));
writer.writeInline("}");
}

Expand Down Expand Up @@ -197,7 +220,8 @@ protected void unionDeclShapeValue(GoWriter writer, MemberShape member, ObjectNo
*/
protected void listDeclShapeValue(GoWriter writer, MemberShape member, Node params) {
writer.write("$P{", symbolProvider.toSymbol(member));
params.accept(new ShapeValueNodeVisitor(writer, this, model.expectShape(member.getTarget())));
params.accept(new ShapeValueNodeVisitor(writer, this, model.expectShape(member.getTarget()),
ListUtils.copyOf(member.getAllTraits().values()), config));
writer.writeInline("}");
}

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

Expand Down Expand Up @@ -327,17 +352,58 @@ protected void writeScalarValueInline(GoWriter writer, MemberShape member, Node
break;
}

params.accept(new ShapeValueNodeVisitor(writer, this, target));
params.accept(new ShapeValueNodeVisitor(writer, this, target,
ListUtils.copyOf(member.getAllTraits().values()), config));
writer.writeInline(closing);
}

/**
* Configuration that determines how shapes values are generated.
*/
public static final class Config {
private final boolean normalizeHttpPrefixHeaderKeys;

private Config(Builder builder) {
normalizeHttpPrefixHeaderKeys = builder.normalizeHttpPrefixHeaderKeys;
}

public static Builder builder() {
return new Builder();
}

/**
* Returns whether maps with the httpPrefixHeader trait should have their keys normalized.
*
* @return whether to normalize http prefix header keys
*/
public boolean isNormalizeHttpPrefixHeaderKeys() {
return normalizeHttpPrefixHeaderKeys;
}

public static final class Builder implements SmithyBuilder<Config> {
private boolean normalizeHttpPrefixHeaderKeys;

public Builder normalizeHttpPrefixHeaderKeys(boolean normalizeHttpPrefixHeaderKeys) {
this.normalizeHttpPrefixHeaderKeys = normalizeHttpPrefixHeaderKeys;
return this;
}

@Override
public Config build() {
return new Config(this);
}
}
}

/**
* NodeVisitor to walk shape value declarations with node values.
*/
private final class ShapeValueNodeVisitor implements NodeVisitor<Void> {
GoWriter writer;
ShapeValueGenerator valueGen;
Shape currentShape;
private final GoWriter writer;
private final ShapeValueGenerator valueGen;
private final Shape currentShape;
private final List<Trait> traits;
private final Config config;

/**
* Initializes shape value visitor.
Expand All @@ -347,9 +413,42 @@ private final class ShapeValueNodeVisitor implements NodeVisitor<Void> {
* @param shape the shape that visiting is relative to.
*/
private ShapeValueNodeVisitor(GoWriter writer, ShapeValueGenerator valueGen, Shape shape) {
this(writer, valueGen, shape, ListUtils.of());
}

/**
* Initializes shape value visitor.
*
* @param writer writer to write generated code with.
* @param valueGen shape value generator.
* @param shape the shape that visiting is relative to.
* @param traits the traits applied to the target shape by a MemberShape.
*/
private ShapeValueNodeVisitor(GoWriter writer, ShapeValueGenerator valueGen, Shape shape, List<Trait> traits) {
this(writer, valueGen, shape, traits, Config.builder().build());
}

/**
* Initializes shape value visitor.
*
* @param writer writer to write generated code with.
* @param valueGen shape value generator.
* @param shape the shape that visiting is relative to.
* @param traits the traits applied to the target shape by a MemberShape.
* @param config the shape value generator config.
*/
private ShapeValueNodeVisitor(
GoWriter writer,
ShapeValueGenerator valueGen,
Shape shape,
List<Trait> traits,
Config config
) {
this.writer = writer;
this.valueGen = valueGen;
this.currentShape = shape;
this.traits = traits;
this.config = config;
}

/**
Expand Down Expand Up @@ -395,10 +494,18 @@ public Void objectNode(ObjectNode node) {
break;

case MAP:
member = this.currentShape.asMapShape().get().getValue();
MapShape mapShape = this.currentShape.asMapShape().get();

String keyValue = keyNode.getValue();
if (config.isNormalizeHttpPrefixHeaderKeys()) {
keyValue = OptionalUtils.or(getTrait(HttpPrefixHeadersTrait.class),
() -> mapShape.getTrait(HttpPrefixHeadersTrait.class))
.map(httpPrefixHeadersTrait -> keyNode.getValue().toLowerCase())
.orElse(keyValue);
}

writer.write("$S: ", keyNode.getValue());
valueGen.writeMemberValueInline(writer, member, valueNode);
writer.write("$S: ", keyValue);
valueGen.writeMemberValueInline(writer, mapShape.getValue(), valueNode);
writer.write(",");
break;

Expand Down Expand Up @@ -523,6 +630,15 @@ private void writeInlineBigIntegerInit(GoWriter writer, Object value) {
+ "}()",
value, value);
}

private <T extends Trait> Optional<T> getTrait(Class<T> traitClass) {
for (Trait trait : traits) {
if (traitClass.isInstance(trait)) {
return Optional.of((T) trait);
}
}
return Optional.empty();
}
}

}
Loading

0 comments on commit ec18a7a

Please sign in to comment.