From 292a9bb5456816c5eb8b0e28aae1885649d3bec4 Mon Sep 17 00:00:00 2001 From: George Fu Date: Wed, 22 Mar 2023 21:01:13 +0000 Subject: [PATCH] move model to initialization in SensitiveDataFinder --- .../typescript/codegen/CommandGenerator.java | 331 +++++++++--------- .../codegen/StructureGenerator.java | 5 +- .../codegen/StructuredMemberWriter.java | 183 +++++----- .../typescript/codegen/UnionGenerator.java | 21 +- .../validation/SensitiveDataFinder.java | 13 +- .../validation/SensitiveDataFinderTest.java | 324 ++++++++--------- 6 files changed, 443 insertions(+), 434 deletions(-) diff --git a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/CommandGenerator.java b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/CommandGenerator.java index 910daaf681b..f24077fc3bc 100644 --- a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/CommandGenerator.java +++ b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/CommandGenerator.java @@ -74,7 +74,7 @@ final class CommandGenerator implements Runnable { private final Symbol outputType; private final ProtocolGenerator protocolGenerator; private final ApplicationProtocol applicationProtocol; - private final SensitiveDataFinder sensitiveDataFinder = new SensitiveDataFinder(); + private final SensitiveDataFinder sensitiveDataFinder; CommandGenerator( TypeScriptSettings settings, @@ -84,8 +84,7 @@ final class CommandGenerator implements Runnable { TypeScriptWriter writer, List runtimePlugins, ProtocolGenerator protocolGenerator, - ApplicationProtocol applicationProtocol - ) { + ApplicationProtocol applicationProtocol) { this.settings = settings; this.model = model; this.service = settings.getService(model); @@ -97,6 +96,7 @@ final class CommandGenerator implements Runnable { .collect(Collectors.toList()); this.protocolGenerator = protocolGenerator; this.applicationProtocol = applicationProtocol; + sensitiveDataFinder = new SensitiveDataFinder(model); symbol = symbolProvider.toSymbol(operation); operationIndex = OperationIndex.of(model); @@ -127,65 +127,62 @@ private void generateClientCommand() { String name = symbol.getName(); StringBuilder additionalDocs = new StringBuilder() - .append("\n") - .append(getCommandExample( - serviceSymbol.getName(), configType, name, inputType.getName(), outputType.getName() - )) - .append("\n") - .append(getThrownExceptions()); + .append("\n") + .append(getCommandExample( + serviceSymbol.getName(), configType, name, inputType.getName(), outputType.getName())) + .append("\n") + .append(getThrownExceptions()); writer.writeShapeDocs( - operation, - shapeDoc -> shapeDoc + additionalDocs - ); + operation, + shapeDoc -> shapeDoc + additionalDocs); writer.openBlock( - "export class $L extends $$Command<$T, $T, $L> {", "}", - name, inputType, outputType, - configType, () -> { - - // Section for adding custom command properties. - writer.write("// Start section: $L", COMMAND_PROPERTIES_SECTION); - writer.pushState(COMMAND_PROPERTIES_SECTION).popState(); - writer.write("// End section: $L", COMMAND_PROPERTIES_SECTION); - writer.write(""); - generateEndpointParameterInstructionProvider(); - writer.write(""); - - generateCommandConstructor(); - writer.write(""); - generateCommandMiddlewareResolver(configType); - writeSerde(); - - // Hook for adding more methods to the command. - writer.write("// Start section: $L", COMMAND_BODY_EXTRA_SECTION) - .pushState(COMMAND_BODY_EXTRA_SECTION) - .popState() - .write("// End section: $L", COMMAND_BODY_EXTRA_SECTION); - } - ); + "export class $L extends $$Command<$T, $T, $L> {", "}", + name, inputType, outputType, + configType, () -> { + + // Section for adding custom command properties. + writer.write("// Start section: $L", COMMAND_PROPERTIES_SECTION); + writer.pushState(COMMAND_PROPERTIES_SECTION).popState(); + writer.write("// End section: $L", COMMAND_PROPERTIES_SECTION); + writer.write(""); + generateEndpointParameterInstructionProvider(); + writer.write(""); + + generateCommandConstructor(); + writer.write(""); + generateCommandMiddlewareResolver(configType); + writeSerde(); + + // Hook for adding more methods to the command. + writer.write("// Start section: $L", COMMAND_BODY_EXTRA_SECTION) + .pushState(COMMAND_BODY_EXTRA_SECTION) + .popState() + .write("// End section: $L", COMMAND_BODY_EXTRA_SECTION); + }); } private String getCommandExample(String serviceName, String configName, String commandName, String commandInput, - String commandOutput) { + String commandOutput) { String packageName = settings.getPackageName(); return "@example\n" - + "Use a bare-bones client and the command you need to make an API call.\n" - + "```javascript\n" - + String.format("import { %s, %s } from \"%s\"; // ES Modules import%n", serviceName, commandName, - packageName) - + String.format("// const { %s, %s } = require(\"%s\"); // CommonJS import%n", serviceName, commandName, - packageName) - + String.format("const client = new %s(config);%n", serviceName) - + String.format("const command = new %s(input);%n", commandName) - + "const response = await client.send(command);\n" - + "```\n" - + "\n" - + String.format("@param %s - {@link %s}%n", commandInput, commandInput) - + String.format("@returns {@link %s}%n", commandOutput) - + String.format("@see {@link %s} for command's `input` shape.%n", commandInput) - + String.format("@see {@link %s} for command's `response` shape.%n", commandOutput) - + String.format("@see {@link %s | config} for %s's `config` shape.%n", configName, serviceName); + + "Use a bare-bones client and the command you need to make an API call.\n" + + "```javascript\n" + + String.format("import { %s, %s } from \"%s\"; // ES Modules import%n", serviceName, commandName, + packageName) + + String.format("// const { %s, %s } = require(\"%s\"); // CommonJS import%n", serviceName, commandName, + packageName) + + String.format("const client = new %s(config);%n", serviceName) + + String.format("const command = new %s(input);%n", commandName) + + "const response = await client.send(command);\n" + + "```\n" + + "\n" + + String.format("@param %s - {@link %s}%n", commandInput, commandInput) + + String.format("@returns {@link %s}%n", commandOutput) + + String.format("@see {@link %s} for command's `input` shape.%n", commandInput) + + String.format("@see {@link %s} for command's `response` shape.%n", commandOutput) + + String.format("@see {@link %s | config} for %s's `config` shape.%n", configName, serviceName); } private String getThrownExceptions() { @@ -198,10 +195,10 @@ private String getThrownExceptions() { if (doc.isPresent()) { buffer.append(String.format("@throws {@link %s} (%s fault)%n %s", - error.getName(), errorTrait.getValue(), doc.get().getValue())); + error.getName(), errorTrait.getValue(), doc.get().getValue())); } else { buffer.append(String.format("@throws {@link %s} (%s fault)", - error.getName(), errorTrait.getValue())); + error.getName(), errorTrait.getValue())); } buffer.append("\n\n"); } @@ -210,14 +207,14 @@ private String getThrownExceptions() { private void generateCommandConstructor() { writer.writeDocs("@public") - .openBlock("constructor(readonly input: $T) {", "}", inputType, () -> { - // The constructor can be intercepted and changed. - writer.write("// Start section: $L", COMMAND_CONSTRUCTOR_SECTION) - .pushState(COMMAND_CONSTRUCTOR_SECTION) - .write("super();") - .popState() - .write("// End section: $L", COMMAND_CONSTRUCTOR_SECTION); - }); + .openBlock("constructor(readonly input: $T) {", "}", inputType, () -> { + // The constructor can be intercepted and changed. + writer.write("// Start section: $L", COMMAND_CONSTRUCTOR_SECTION) + .pushState(COMMAND_CONSTRUCTOR_SECTION) + .write("super();") + .popState() + .write("// End section: $L", COMMAND_CONSTRUCTOR_SECTION); + }); } private void generateEndpointParameterInstructionProvider() { @@ -226,52 +223,46 @@ private void generateEndpointParameterInstructionProvider() { } writer.addImport("EndpointParameterInstructions", null, "@aws-sdk/middleware-endpoint"); writer.openBlock( - "public static getEndpointParameterInstructions(): EndpointParameterInstructions {", "}", - () -> { - writer.openBlock( - "return {", "};", - () -> { - RuleSetParameterFinder parameterFinder = new RuleSetParameterFinder(service); - Set paramNames = new HashSet<>(); - - parameterFinder.getStaticContextParamValues(operation).forEach((name, value) -> { - writer.write( - "$L: { type: \"staticContextParams\", value: $L },", - name, value - ); - }); - - Shape operationInput = model.getShape(operation.getInputShape()).get(); - parameterFinder.getContextParams(operationInput).forEach((name, type) -> { - writer.write( - "$L: { type: \"contextParams\", name: \"$L\" },", - name, name - ); - }); - - parameterFinder.getClientContextParams().forEach((name, type) -> { - if (!paramNames.contains(name)) { - writer.write( - "$L: { type: \"clientContextParams\", name: \"$L\" },", - name, EndpointsParamNameMap.getLocalName(name) - ); - } - paramNames.add(name); - }); - - parameterFinder.getBuiltInParams().forEach((name, type) -> { - if (!paramNames.contains(name)) { - writer.write( - "$L: { type: \"builtInParams\", name: \"$L\" },", - name, EndpointsParamNameMap.getLocalName(name) - ); - } - paramNames.add(name); - }); - } - ); - } - ); + "public static getEndpointParameterInstructions(): EndpointParameterInstructions {", "}", + () -> { + writer.openBlock( + "return {", "};", + () -> { + RuleSetParameterFinder parameterFinder = new RuleSetParameterFinder(service); + Set paramNames = new HashSet<>(); + + parameterFinder.getStaticContextParamValues(operation).forEach((name, value) -> { + writer.write( + "$L: { type: \"staticContextParams\", value: $L },", + name, value); + }); + + Shape operationInput = model.getShape(operation.getInputShape()).get(); + parameterFinder.getContextParams(operationInput).forEach((name, type) -> { + writer.write( + "$L: { type: \"contextParams\", name: \"$L\" },", + name, name); + }); + + parameterFinder.getClientContextParams().forEach((name, type) -> { + if (!paramNames.contains(name)) { + writer.write( + "$L: { type: \"clientContextParams\", name: \"$L\" },", + name, EndpointsParamNameMap.getLocalName(name)); + } + paramNames.add(name); + }); + + parameterFinder.getBuiltInParams().forEach((name, type) -> { + if (!paramNames.contains(name)) { + writer.write( + "$L: { type: \"builtInParams\", name: \"$L\" },", + name, EndpointsParamNameMap.getLocalName(name)); + } + paramNames.add(name); + }); + }); + }); } private void generateCommandMiddlewareResolver(String configType) { @@ -290,17 +281,15 @@ private void generateCommandMiddlewareResolver(String configType) { // EndpointsV2 if (service.hasTrait(EndpointRuleSetTrait.class)) { writer.addImport( - "getEndpointPlugin", - "getEndpointPlugin", - "@aws-sdk/middleware-endpoint" - ); + "getEndpointPlugin", + "getEndpointPlugin", + "@aws-sdk/middleware-endpoint"); writer.openBlock( - "this.middlewareStack.use(getEndpointPlugin(configuration, ", - "));", - () -> { - writer.write("$L.getEndpointParameterInstructions()", symbol.getName()); - } - ); + "this.middlewareStack.use(getEndpointPlugin(configuration, ", + "));", + () -> { + writer.write("$L.getEndpointParameterInstructions()", symbol.getName()); + }); } // Add customizations. @@ -317,46 +306,44 @@ private void generateCommandMiddlewareResolver(String configType) { writer.write("commandName,"); writer.openBlock("inputFilterSensitiveLog: ", ",", () -> { OptionalUtils.ifPresentOrElse(operationIndex.getInput(operation), - input -> { - if (sensitiveDataFinder.findsSensitiveData(input, model)) { - Symbol inputSymbol = symbolProvider.toSymbol(input); - String filterFunctionName = inputSymbol.getName() + "FilterSensitiveLog"; - writer.addImport( - filterFunctionName, - filterFunctionName, - inputSymbol.getNamespace() - ); - writer.writeInline(filterFunctionName); - } else { - writer.writeInline("(_: any) => _"); - } - }, - () -> writer.writeInline("(_: any) => _")); + input -> { + if (sensitiveDataFinder.findsSensitiveDataIn(input)) { + Symbol inputSymbol = symbolProvider.toSymbol(input); + String filterFunctionName = inputSymbol.getName() + "FilterSensitiveLog"; + writer.addImport( + filterFunctionName, + filterFunctionName, + inputSymbol.getNamespace()); + writer.writeInline(filterFunctionName); + } else { + writer.writeInline("(_: any) => _"); + } + }, + () -> writer.writeInline("(_: any) => _")); }); writer.openBlock("outputFilterSensitiveLog: ", ",", () -> { OptionalUtils.ifPresentOrElse(operationIndex.getOutput(operation), - output -> { - if (sensitiveDataFinder.findsSensitiveData(output, model)) { - Symbol outputSymbol = symbolProvider.toSymbol(output); - String filterFunctionName = outputSymbol.getName() + "FilterSensitiveLog"; - writer.addImport( - filterFunctionName, - filterFunctionName, - outputSymbol.getNamespace() - ); - writer.writeInline(filterFunctionName); - } else { - writer.writeInline("(_: any) => _"); - } - }, - () -> writer.writeInline("(_: any) => _")); + output -> { + if (sensitiveDataFinder.findsSensitiveDataIn(output)) { + Symbol outputSymbol = symbolProvider.toSymbol(output); + String filterFunctionName = outputSymbol.getName() + "FilterSensitiveLog"; + writer.addImport( + filterFunctionName, + filterFunctionName, + outputSymbol.getNamespace()); + writer.writeInline(filterFunctionName); + } else { + writer.writeInline("(_: any) => _"); + } + }, + () -> writer.writeInline("(_: any) => _")); }); }); writer.write("const { requestHandler } = configuration;"); writer.openBlock("return stack.resolve(", ");", () -> { writer.write("(request: FinalizeHandlerArguments) => "); writer.write(" requestHandler.handle(request.request as $T, options || {}),", - applicationProtocol.getRequestType()); + applicationProtocol.getRequestType()); writer.write("handlerExecutionContext"); }); }); @@ -417,8 +404,8 @@ private void addCommandSpecificPlugins() { List additionalParameters = CodegenUtils.getFunctionParametersList(paramsMap); String additionalParamsString = additionalParameters.isEmpty() - ? "" - : ", { " + String.join(", ", additionalParameters) + "}"; + ? "" + : ", { " + String.join(", ", additionalParameters) + "}"; writer.write("this.middlewareStack.use($T(configuration$L));", symbol, additionalParamsString); }); @@ -427,28 +414,27 @@ private void addCommandSpecificPlugins() { private void writeSerde() { writer.write("") - .writeDocs("@internal") - .write("private serialize(") - .indent() + .writeDocs("@internal") + .write("private serialize(") + .indent() .write("input: $T,", inputType) .write("context: $L", CodegenUtils.getOperationSerializerContextType(writer, model, operation)) - .dedent() - .openBlock( - "): Promise<$T> {", "}", - applicationProtocol.getRequestType(), - () -> writeSerdeDispatcher(true) - ); + .dedent() + .openBlock( + "): Promise<$T> {", "}", + applicationProtocol.getRequestType(), + () -> writeSerdeDispatcher(true)); writer.write("") - .writeDocs("@internal") - .write("private deserialize(") - .indent() + .writeDocs("@internal") + .write("private deserialize(") + .indent() .write("output: $T,", applicationProtocol.getResponseType()) .write("context: $L", CodegenUtils.getOperationDeserializerContextType(settings, writer, model, operation)) - .dedent() - .openBlock("): Promise<$T> {", "}", outputType, () -> writeSerdeDispatcher(false)) - .write(""); + .dedent() + .openBlock("): Promise<$T> {", "}", outputType, () -> writeSerdeDispatcher(false)) + .write(""); } private void writeSerdeDispatcher(boolean isInput) { @@ -461,8 +447,8 @@ private void writeSerdeDispatcher(boolean isInput) { ? ProtocolGenerator.getSerFunctionName(symbol, protocolGenerator.getName()) : ProtocolGenerator.getDeserFunctionName(symbol, protocolGenerator.getName()); writer.addImport(serdeFunctionName, serdeFunctionName, - Paths.get(".", CodegenUtils.SOURCE_FOLDER, ProtocolGenerator.PROTOCOLS_FOLDER, - ProtocolGenerator.getSanitizedName(protocolGenerator.getName())).toString()); + Paths.get(".", CodegenUtils.SOURCE_FOLDER, ProtocolGenerator.PROTOCOLS_FOLDER, + ProtocolGenerator.getSanitizedName(protocolGenerator.getName())).toString()); writer.write("return $L($L, context);", serdeFunctionName, isInput ? "input" : "output"); } } @@ -471,8 +457,7 @@ static void writeIndex( Model model, ServiceShape service, SymbolProvider symbolProvider, - FileManifest fileManifest - ) { + FileManifest fileManifest) { TypeScriptWriter writer = new TypeScriptWriter(""); TopDownIndex topDownIndex = TopDownIndex.of(model); @@ -482,7 +467,7 @@ static void writeIndex( } fileManifest.writeFile( - Paths.get(CodegenUtils.SOURCE_FOLDER, CommandGenerator.COMMANDS_FOLDER, "index.ts").toString(), - writer.toString()); + Paths.get(CodegenUtils.SOURCE_FOLDER, CommandGenerator.COMMANDS_FOLDER, "index.ts").toString(), + writer.toString()); } } diff --git a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/StructureGenerator.java b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/StructureGenerator.java index fad21abea74..9856553de0c 100644 --- a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/StructureGenerator.java +++ b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/StructureGenerator.java @@ -73,7 +73,7 @@ final class StructureGenerator implements Runnable { private final StructureShape shape; private final boolean includeValidation; private final RequiredMemberMode requiredMemberMode; - private final SensitiveDataFinder sensitiveDataFinder = new SensitiveDataFinder(); + private final SensitiveDataFinder sensitiveDataFinder; /** * sets 'includeValidation' to 'false' and requiredMemberMode @@ -96,6 +96,7 @@ final class StructureGenerator implements Runnable { this.shape = shape; this.includeValidation = includeValidation; this.requiredMemberMode = requiredMemberMode; + sensitiveDataFinder = new SensitiveDataFinder(model); } @Override @@ -189,7 +190,7 @@ private void renderStructureNamespace(StructuredMemberWriter structuredMemberWri Symbol symbol = symbolProvider.toSymbol(shape); String objectParam = "obj"; - if (sensitiveDataFinder.findsSensitiveData(shape, model)) { + if (sensitiveDataFinder.findsSensitiveDataIn(shape)) { writer.writeDocs("@internal"); writer.openBlock("export const $LFilterSensitiveLog = ($L: $L): any => ({", "})", symbol.getName(), diff --git a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/StructuredMemberWriter.java b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/StructuredMemberWriter.java index 749e14c5d61..c336e7c0f1e 100644 --- a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/StructuredMemberWriter.java +++ b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/StructuredMemberWriter.java @@ -76,7 +76,7 @@ final class StructuredMemberWriter { StructuredMemberWriter(Model model, SymbolProvider symbolProvider, Collection members, RequiredMemberMode requiredMemberMode) { - this(model, symbolProvider, members, requiredMemberMode, new SensitiveDataFinder()); + this(model, symbolProvider, members, requiredMemberMode, new SensitiveDataFinder(model)); } StructuredMemberWriter( @@ -104,9 +104,9 @@ void writeMembers(TypeScriptWriter writer, Shape shape) { String memberName = getSanitizedMemberName(member); String optionalSuffix = shape.isUnionShape() || !isRequiredMember(member) ? "?" : ""; String typeSuffix = requiredMemberMode == RequiredMemberMode.NULLABLE - && isRequiredMember(member) ? " | undefined" : ""; + && isRequiredMember(member) ? " | undefined" : ""; writer.write("${L}${L}${L}: ${T}${L};", memberPrefix, memberName, optionalSuffix, - symbolProvider.toSymbol(member), typeSuffix); + symbolProvider.toSymbol(member), typeSuffix); if (wroteDocs && position < members.size() - 1) { writer.write(""); @@ -143,8 +143,7 @@ void writeMemberFilterSensitiveLog(TypeScriptWriter writer, MemberShape member, writeMapFilterSensitiveLog(writer, mapMember, memberParam); } else { throw new CodegenException(String.format( - "MemberFilterSensitiveLog attempted for %s", memberTarget.getType() - )); + "MemberFilterSensitiveLog attempted for %s", memberTarget.getType())); } } @@ -189,8 +188,7 @@ private void writeSensitiveString(TypeScriptWriter writer) { private void writeStructureFilterSensitiveLog( TypeScriptWriter writer, Shape structureTarget, - String structureParam - ) { + String structureParam) { if (structureTarget.hasTrait(SensitiveTrait.class)) { writeSensitiveString(writer); } else if (structureTarget.hasTrait(StreamingTrait.class) && structureTarget.isUnionShape()) { @@ -200,7 +198,7 @@ private void writeStructureFilterSensitiveLog( // Sensitive logs are not filtered from errors. writer.write("$L", structureParam); } else { - if (sensitiveDataFinder.findsSensitiveData(structureTarget, model)) { + if (sensitiveDataFinder.findsSensitiveDataIn(structureTarget)) { // Call filterSensitiveLog on Structure. Symbol symbol = symbolProvider.toSymbol(structureTarget); String filterFunctionName = symbol.getName() + "FilterSensitiveLog"; @@ -220,8 +218,7 @@ private void writeStructureFilterSensitiveLog( private void writeCollectionFilterSensitiveLog( TypeScriptWriter writer, MemberShape collectionMember, - String collectionParam - ) { + String collectionParam) { if (collectionMember.getMemberTrait(model, SensitiveTrait.class).isPresent()) { writeSensitiveString(writer); } else if (model.expectShape(collectionMember.getTarget()) instanceof SimpleShape) { @@ -250,22 +247,22 @@ private void writeMapFilterSensitiveLog(TypeScriptWriter writer, MemberShape map // Reducer is common to all shapes. writer.openBlock("Object.entries($L).reduce(($L: any, [$L, $L]: [string, $T]) => (", "), {})", - mapParam, accParam, keyParam, valueParam, symbolProvider.toSymbol(mapMember), () -> { - writer.openBlock("$L[$L] =", "", accParam, keyParam, () -> { - writeMemberFilterSensitiveLog(writer, mapMember, valueParam); - writer.writeInline(","); + mapParam, accParam, keyParam, valueParam, symbolProvider.toSymbol(mapMember), () -> { + writer.openBlock("$L[$L] =", "", accParam, keyParam, () -> { + writeMemberFilterSensitiveLog(writer, mapMember, valueParam); + writer.writeInline(","); + }); + writer.write(accParam); }); - writer.write(accParam); - } - ); } } /** * Identifies if member needs to be overwritten in filterSensitiveLog. * - * @param member a {@link MemberShape} to check if overwrite is required. - * @param parents a set of membernames which are parents of existing member to avoid unending recursion. + * @param member a {@link MemberShape} to check if overwrite is required. + * @param parents a set of membernames which are parents of existing member to + * avoid unending recursion. * @return Returns true if the overwrite is required on member. */ private boolean isMemberOverwriteRequired(MemberShape member, Set parents) { @@ -281,7 +278,7 @@ private boolean isMemberOverwriteRequired(MemberShape member, Set parent if (!parents.contains(symbolProvider.toMemberName(member))) { parents.add(symbolProvider.toMemberName(member)); Collection structureMemberList = ((StructureShape) memberTarget).getAllMembers().values(); - for (MemberShape structureMember: structureMemberList) { + for (MemberShape structureMember : structureMemberList) { if (!parents.contains(symbolProvider.toMemberName(structureMember)) && isMemberOverwriteRequired(structureMember, parents)) { return true; @@ -320,19 +317,25 @@ private String getSanitizedMemberName(MemberShape member) { * @param member The member being generated for. * @return If the interface member should be treated as required. * - * @see Smithy idempotencyToken trait. + * @see Smithy + * idempotencyToken trait. */ private boolean isRequiredMember(MemberShape member) { return member.isRequired() && !member.hasTrait(IdempotencyTokenTrait.class); } /** - * Writes an empty cache into the namespace for use by the member validator factory. + * Writes an empty cache into the namespace for use by the member validator + * factory. * - * Due to the fact that validation references can be circular, we need to defer retrieval of validators - * to runtime, but we do not want to reinstantiate each validator every time it is needed. + * Due to the fact that validation references can be circular, we need to defer + * retrieval of validators + * to runtime, but we do not want to reinstantiate each validator every time it + * is needed. * - * @param writer the writer for the type, currently positioned in the type's exported namespace + * @param writer the writer for the type, currently positioned in the type's + * exported namespace * @param cacheName the name of the in-scope cache for the validators */ void writeMemberValidatorCache(TypeScriptWriter writer, String cacheName) { @@ -351,45 +354,46 @@ void writeMemberValidatorCache(TypeScriptWriter writer, String cacheName) { } /** - * Writes a member validator factory method that will reuse cached validators, or create new ones if this is + * Writes a member validator factory method that will reuse cached validators, + * or create new ones if this is * the first instance of validation. * - * @param writer the writer for the type, currently positioned in the type's validate method + * @param writer the writer for the type, currently positioned in the type's + * validate method * @param cacheName the name of the in-scope cache for the validators */ void writeMemberValidatorFactory(TypeScriptWriter writer, String cacheName) { writer.openBlock("function getMemberValidator(member: T): " - + "NonNullable {", - "}", - cacheName, - () -> { - writer.openBlock("if ($L[member] === undefined) {", "}", cacheName, () -> { - writer.openBlock("switch (member) {", "}", () -> { - for (MemberShape member : members) { - final Shape targetShape = model.expectShape(member.getTarget()); - Collection constraintTraits = getConstraintTraits(member); - writer.openBlock("case $S: {", "}", getSanitizedMemberName(member), () -> { - writer.writeInline("$L[$S] = ", cacheName, getSanitizedMemberName(member)); - if (member.getMemberTrait(model, SensitiveTrait.class).isPresent()) { - writeSensitiveWrappedMemberValidator(writer, targetShape, constraintTraits); - } else { - writeMemberValidator(writer, targetShape, constraintTraits, ";"); - } - writer.write("break;"); - }); - } + + "NonNullable {", + "}", + cacheName, + () -> { + writer.openBlock("if ($L[member] === undefined) {", "}", cacheName, () -> { + writer.openBlock("switch (member) {", "}", () -> { + for (MemberShape member : members) { + final Shape targetShape = model.expectShape(member.getTarget()); + Collection constraintTraits = getConstraintTraits(member); + writer.openBlock("case $S: {", "}", getSanitizedMemberName(member), () -> { + writer.writeInline("$L[$S] = ", cacheName, getSanitizedMemberName(member)); + if (member.getMemberTrait(model, SensitiveTrait.class).isPresent()) { + writeSensitiveWrappedMemberValidator(writer, targetShape, constraintTraits); + } else { + writeMemberValidator(writer, targetShape, constraintTraits, ";"); + } + writer.write("break;"); + }); + } + }); }); + writer.write("return $L[member]!!;", cacheName); }); - writer.write("return $L[member]!!;", cacheName); - } - ); } /** * Writes the validate method contents. * * @param writer the writer, positioned within the validate method - * @param param the parameter name of the object being validated + * @param param the parameter name of the object being validated */ void writeValidateMethodContents(TypeScriptWriter writer, String param) { writer.openBlock("return [", "];", () -> { @@ -397,7 +401,8 @@ void writeValidateMethodContents(TypeScriptWriter writer, String param) { String optionalSuffix = ""; if (member.getMemberTrait(model, MediaTypeTrait.class).isPresent() && model.expectShape(member.getTarget()) instanceof StringShape) { - // lazy JSON wrapper validation should be done based on the serialized form of the object + // lazy JSON wrapper validation should be done based on the serialized form of + // the object optionalSuffix = "?.toString()"; } writer.write("...getMemberValidator($1S).validate($2L.$1L$4L, `$${path}/$3L`),", @@ -407,11 +412,12 @@ void writeValidateMethodContents(TypeScriptWriter writer, String param) { } /** - * Writes a SensitiveConstraintValidator enclosing the shape validator for a sensitive member. + * Writes a SensitiveConstraintValidator enclosing the shape validator for a + * sensitive member. */ private void writeSensitiveWrappedMemberValidator(TypeScriptWriter writer, - Shape targetShape, - Collection constraintTraits) { + Shape targetShape, + Collection constraintTraits) { writer.addImport("SensitiveConstraintValidator", "__SensitiveConstraintValidator", "@aws-smithy/server-common"); @@ -423,15 +429,18 @@ private void writeSensitiveWrappedMemberValidator(TypeScriptWriter writer, /** * Writes the validator for the member of a structure or union. - * @param writer the writer - * @param shape the shape targeted by the member - * @param constraintTraits the traits applied to the targeted shape and the member - * @param trailer what to append to the output (such as a comma or semicolon) + * + * @param writer the writer + * @param shape the shape targeted by the member + * @param constraintTraits the traits applied to the targeted shape and the + * member + * @param trailer what to append to the output (such as a comma or + * semicolon) */ private void writeMemberValidator(TypeScriptWriter writer, - Shape shape, - Collection constraintTraits, - String trailer) { + Shape shape, + Collection constraintTraits, + String trailer) { if (shape instanceof SimpleShape) { writeShapeValidator(writer, shape, constraintTraits, trailer); return; @@ -446,8 +455,7 @@ private void writeMemberValidator(TypeScriptWriter writer, () -> { writeShapeValidator(writer, shape, constraintTraits, ","); writer.write("$T.validate", symbolProvider.toSymbol(shape)); - } - ); + }); } else if (shape.isListShape() || shape.isSetShape()) { writer.addImport("CompositeCollectionValidator", "__CompositeCollectionValidator", @@ -462,8 +470,7 @@ private void writeMemberValidator(TypeScriptWriter writer, collectionMemberTargetShape, getConstraintTraits(collectionMemberShape), ""); - } - ); + }); } else if (shape.isMapShape()) { writer.addImport("CompositeMapValidator", "__CompositeMapValidator", "@aws-smithy/server-common"); @@ -490,18 +497,22 @@ private void writeMemberValidator(TypeScriptWriter writer, } /** - * Writes a validator for a shape, aggregating all of the constraints applied to it. This shape could be a member - * target, or the target of a shape (such as the member of a list or the value of a map). + * Writes a validator for a shape, aggregating all of the constraints applied to + * it. This shape could be a member + * target, or the target of a shape (such as the member of a list or the value + * of a map). * - * @param writer the writer - * @param shape the shape being validated - * @param constraints the constraints relevant to this shape (includes member traits for member targets) - * @param trailer what to append to the output (for instance, a comma or semicolon) + * @param writer the writer + * @param shape the shape being validated + * @param constraints the constraints relevant to this shape (includes member + * traits for member targets) + * @param trailer what to append to the output (for instance, a comma or + * semicolon) */ private void writeShapeValidator(TypeScriptWriter writer, - Shape shape, - Collection constraints, - String trailer) { + Shape shape, + Collection constraints, + String trailer) { boolean shouldWriteIntEnumValidator = shape.isIntEnumShape(); if (constraints.isEmpty() && !shouldWriteIntEnumValidator) { @@ -541,8 +552,7 @@ private void writeShapeValidator(TypeScriptWriter writer, for (Trait t : constraints) { writeSingleConstraintValidator(writer, t); } - } - ); + }); } /** @@ -587,7 +597,8 @@ private void writeSingleConstraintValidator(TypeScriptWriter writer, Trait trait } /** - * Writes the type that is being validated (the TS type corresponding to the target shape) that is used as a + * Writes the type that is being validated (the TS type corresponding to the + * target shape) that is used as a * type arg for MultiConstraintValidator. */ private void writeConstraintValidatorType(TypeScriptWriter writer, Shape shape) { @@ -608,8 +619,10 @@ private void writeConstraintValidatorType(TypeScriptWriter writer, Shape shape) } /** - * @return returns the value type for a validator. For maps, this is the type of the value; for lists, this is - * the member type. This type is loosened by {@link #getSymbolForValidatedType(Shape)} + * @return returns the value type for a validator. For maps, this is the type of + * the value; for lists, this is + * the member type. This type is loosened by + * {@link #getSymbolForValidatedType(Shape)} */ private Symbol getValidatorValueType(Shape shape) { if (shape.isStructureShape() || shape.isUnionShape()) { @@ -629,10 +642,13 @@ private Symbol getValidatorValueType(Shape shape) { } /** - * If we return the direct symbol for the validated type, then TypeScript will not pass type checks when we pass - * raw deserialized values into our validators. This is particularly problematic for enums. + * If we return the direct symbol for the validated type, then TypeScript will + * not pass type checks when we pass + * raw deserialized values into our validators. This is particularly problematic + * for enums. * - * @return a looser supertype of the modeled value type (generally, string instead of a subtype of string) + * @return a looser supertype of the modeled value type (generally, string + * instead of a subtype of string) */ private Symbol getSymbolForValidatedType(Shape shape) { if (shape instanceof StringShape) { @@ -641,7 +657,8 @@ private Symbol getSymbolForValidatedType(Shape shape) { return symbolProvider.toSymbol(model.expectShape(ShapeId.from("smithy.api#Integer"))); } - // Streaming blob inputs can also take string, Uint8Array and Buffer, so we widen the symbol + // Streaming blob inputs can also take string, Uint8Array and Buffer, so we + // widen the symbol if (shape.isBlobShape() && shape.hasTrait(StreamingTrait.class)) { return symbolProvider.toSymbol(shape) .toBuilder() diff --git a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/UnionGenerator.java b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/UnionGenerator.java index 5b54ec48dd6..87d7f2e79a3 100644 --- a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/UnionGenerator.java +++ b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/UnionGenerator.java @@ -141,7 +141,7 @@ final class UnionGenerator implements Runnable { private final UnionShape shape; private final Map variantMap; private final boolean includeValidation; - private final SensitiveDataFinder sensitiveDataFinder = new SensitiveDataFinder(); + private final SensitiveDataFinder sensitiveDataFinder; /** * sets 'includeValidation' to 'false' for backwards compatibility. @@ -161,6 +161,7 @@ final class UnionGenerator implements Runnable { this.symbolProvider = symbolProvider; this.writer = writer; this.includeValidation = includeValidation; + sensitiveDataFinder = new SensitiveDataFinder(model); variantMap = new TreeMap<>(); for (MemberShape member : shape.getAllMembers().values()) { @@ -183,14 +184,14 @@ public void run() { // Write out the namespace that contains each variant and visitor. writer.writeDocs("@public") - .openBlock("export namespace $L {", "}", symbol.getName(), () -> { - writeUnionMemberInterfaces(); - writeVisitorType(); - writeVisitorFunction(); - if (includeValidation) { - writeValidate(); - } - }); + .openBlock("export namespace $L {", "}", symbol.getName(), () -> { + writeUnionMemberInterfaces(); + writeVisitorType(); + writeVisitorFunction(); + if (includeValidation) { + writeValidate(); + } + }); writeFilterSensitiveLog(symbol.getName()); } @@ -251,7 +252,7 @@ private void writeVisitorFunction() { } private void writeFilterSensitiveLog(String namespace) { - if (sensitiveDataFinder.findsSensitiveData(shape, model)) { + if (sensitiveDataFinder.findsSensitiveDataIn(shape)) { String objectParam = "obj"; writer.writeDocs("@internal"); writer.openBlock("export const $LFilterSensitiveLog = ($L: $L): any => {", "}", diff --git a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/validation/SensitiveDataFinder.java b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/validation/SensitiveDataFinder.java index 1c2fc7e59f3..5a9bce1ac66 100644 --- a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/validation/SensitiveDataFinder.java +++ b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/validation/SensitiveDataFinder.java @@ -23,19 +23,24 @@ */ public class SensitiveDataFinder { private Map cache = new HashMap<>(); + private final Model model; + + public SensitiveDataFinder(Model model) { + this.model = model; + } /** * @param shape - the shape in question. * @param model - model context for the shape, containing its related shapes. * @return whether a sensitive field exists in the shape and its downstream shapes. */ - public boolean findsSensitiveData(Shape shape, Model model) { - boolean found = findRecursive(shape, model); + public boolean findsSensitiveDataIn(Shape shape) { + boolean found = findRecursive(shape); cache.put(shape, found); return found; } - private boolean findRecursive(Shape shape, Model model) { + private boolean findRecursive(Shape shape) { if (cache.containsKey(shape)) { return cache.get(shape); } @@ -66,7 +71,7 @@ private boolean findRecursive(Shape shape, Model model) { if (shape instanceof MapShape) { MemberShape keyMember = ((MapShape) shape).getKey(); MemberShape valMember = ((MapShape) shape).getValue(); - return findRecursive(keyMember, model) || findRecursive(valMember, model); + return findRecursive(keyMember) || findRecursive(valMember); } cache.put(shape, false); diff --git a/smithy-typescript-codegen/src/test/java/software/amazon/smithy/typescript/codegen/validation/SensitiveDataFinderTest.java b/smithy-typescript-codegen/src/test/java/software/amazon/smithy/typescript/codegen/validation/SensitiveDataFinderTest.java index 0d68ce175d9..c7992955cb2 100644 --- a/smithy-typescript-codegen/src/test/java/software/amazon/smithy/typescript/codegen/validation/SensitiveDataFinderTest.java +++ b/smithy-typescript-codegen/src/test/java/software/amazon/smithy/typescript/codegen/validation/SensitiveDataFinderTest.java @@ -15,166 +15,166 @@ import software.amazon.smithy.model.traits.SensitiveTrait; public class SensitiveDataFinderTest { - private SensitiveDataFinder sensitiveDataFinder = new SensitiveDataFinder(); - - StringShape sensitiveString = StringShape.builder() - .addTrait(new SensitiveTrait()) - .id("foo.bar#sensitiveString") - .build(); - - StringShape dullString = StringShape.builder() - .id("foo.bar#dullString") - .build(); - - MemberShape memberWithSensitiveData = MemberShape.builder() - .id("foo.bar#sensitive$member") - .target(sensitiveString.getId()) - .build(); - - MemberShape memberWithDullData = MemberShape.builder() - .id("foo.bar#dull$member") - .target(dullString.getId()) - .build(); - - MemberShape listMemberWithSensitiveData = MemberShape.builder() - .id("foo.bar#listSensitive$member") - .target(sensitiveString.getId()) - .build(); - - MemberShape listMemberWithDullData = MemberShape.builder() - .id("foo.bar#listDull$member") - .target(dullString.getId()) - .build(); - - MemberShape mapMemberWithSensitiveKeyData = MemberShape.builder() - .id("foo.bar#mapSensitiveKey$member") - .target(sensitiveString.getId()) - .build(); - - MemberShape mapMemberWithSensitiveValueData = MemberShape.builder() - .id("foo.bar#mapSensitiveValue$member") - .target(sensitiveString.getId()) - .build(); - - StructureShape structureShapeSensitive = StructureShape.builder() - .id("foo.bar#sensitive") - .members( - Collections.singleton(memberWithSensitiveData)) - .build(); - - StructureShape structureShapeDull = StructureShape.builder() - .id("foo.bar#dull") - .members( - Collections.singleton(memberWithDullData)) - .build(); - - CollectionShape collectionSensitive = ListShape.builder() - .id("foo.bar#listSensitive") - .addMember(listMemberWithSensitiveData) - .build(); - - CollectionShape collectionDull = ListShape.builder() - .id("foo.bar#listDull") - .addMember(listMemberWithDullData) - .build(); - - MapShape mapSensitiveKey = MapShape.builder() - .id("foo.bar#mapSensitiveKey") - .key(mapMemberWithSensitiveKeyData) - .value(MemberShape.builder() - .id("foo.bar#mapSensitiveKey$member") - .target(dullString.getId()) - .build()) - .build(); - - MapShape mapSensitiveValue = MapShape.builder() - .id("foo.bar#mapSensitiveValue") - .key(MemberShape.builder() - .id("foo.bar#mapSensitiveValue$member") - .target(dullString.getId()) - .build()) - .value(mapMemberWithSensitiveValueData) - .build(); - - MapShape mapDull = MapShape.builder() - .id("foo.bar#mapDull") - .key(MemberShape.builder() - .id("foo.bar#mapDull$member") - .target(dullString.getId()) - .build()) - .value(MemberShape.builder() - .id("foo.bar#mapDull$member") - .target(dullString.getId()) - .build()) - .build(); - - MapShape nested2 = MapShape.builder() - .id("foo.bar#mapNested2") - .key(MemberShape.builder() - .id("foo.bar#mapNested2$member") - .target(dullString.getId()) - .build()) - .value(MemberShape.builder() - .id("foo.bar#mapNested2$member") - .target(mapSensitiveValue) - .build()) - .build(); - - MapShape nested = MapShape.builder() - .id("foo.bar#mapNested") - .key(MemberShape.builder() - .id("foo.bar#mapNested$member") - .target(dullString.getId()) - .build()) - .value(MemberShape.builder() - .id("foo.bar#mapNested$member") - .target(nested2) - .build()) - .build(); - - private Model model = Model.builder() - .addShapes( - sensitiveString, dullString, - memberWithSensitiveData, memberWithDullData, - structureShapeSensitive, structureShapeDull, - collectionSensitive, collectionDull, - mapSensitiveKey, mapSensitiveValue, mapDull, - nested, nested2) - .build(); - - @Test - public void findsSensitiveData_inShapes() { - assertThat(sensitiveDataFinder.findsSensitiveData(sensitiveString, model), equalTo(true)); - assertThat(sensitiveDataFinder.findsSensitiveData(dullString, model), equalTo(false)); - } - - @Test - public void findsSensitiveData_inTargetShapes() { - assertThat(sensitiveDataFinder.findsSensitiveData(memberWithSensitiveData, model), equalTo(true)); - assertThat(sensitiveDataFinder.findsSensitiveData(memberWithDullData, model), equalTo(false)); - } - - @Test - public void findsSensitiveData_inStructures() { - assertThat(sensitiveDataFinder.findsSensitiveData(structureShapeSensitive, model), equalTo(true)); - assertThat(sensitiveDataFinder.findsSensitiveData(structureShapeDull, model), equalTo(false)); - } - - @Test - public void findsSensitiveData_inCollections() { - assertThat(sensitiveDataFinder.findsSensitiveData(collectionSensitive, model), equalTo(true)); - assertThat(sensitiveDataFinder.findsSensitiveData(collectionDull, model), equalTo(false)); - } - - @Test - public void findsSensitiveData_inMaps() { - assertThat(sensitiveDataFinder.findsSensitiveData(mapSensitiveKey, model), equalTo(true)); - assertThat(sensitiveDataFinder.findsSensitiveData(mapSensitiveValue, model), equalTo(true)); - assertThat(sensitiveDataFinder.findsSensitiveData(mapDull, model), equalTo(false)); - } - - @Test - public void findsSensitiveData_deeplyNested() { - assertThat(sensitiveDataFinder.findsSensitiveData(nested, model), equalTo(true)); - } + StringShape sensitiveString = StringShape.builder() + .addTrait(new SensitiveTrait()) + .id("foo.bar#sensitiveString") + .build(); + + StringShape dullString = StringShape.builder() + .id("foo.bar#dullString") + .build(); + + MemberShape memberWithSensitiveData = MemberShape.builder() + .id("foo.bar#sensitive$member") + .target(sensitiveString.getId()) + .build(); + + MemberShape memberWithDullData = MemberShape.builder() + .id("foo.bar#dull$member") + .target(dullString.getId()) + .build(); + + MemberShape listMemberWithSensitiveData = MemberShape.builder() + .id("foo.bar#listSensitive$member") + .target(sensitiveString.getId()) + .build(); + + MemberShape listMemberWithDullData = MemberShape.builder() + .id("foo.bar#listDull$member") + .target(dullString.getId()) + .build(); + + MemberShape mapMemberWithSensitiveKeyData = MemberShape.builder() + .id("foo.bar#mapSensitiveKey$member") + .target(sensitiveString.getId()) + .build(); + + MemberShape mapMemberWithSensitiveValueData = MemberShape.builder() + .id("foo.bar#mapSensitiveValue$member") + .target(sensitiveString.getId()) + .build(); + + StructureShape structureShapeSensitive = StructureShape.builder() + .id("foo.bar#sensitive") + .members( + Collections.singleton(memberWithSensitiveData)) + .build(); + + StructureShape structureShapeDull = StructureShape.builder() + .id("foo.bar#dull") + .members( + Collections.singleton(memberWithDullData)) + .build(); + + CollectionShape collectionSensitive = ListShape.builder() + .id("foo.bar#listSensitive") + .addMember(listMemberWithSensitiveData) + .build(); + + CollectionShape collectionDull = ListShape.builder() + .id("foo.bar#listDull") + .addMember(listMemberWithDullData) + .build(); + + MapShape mapSensitiveKey = MapShape.builder() + .id("foo.bar#mapSensitiveKey") + .key(mapMemberWithSensitiveKeyData) + .value(MemberShape.builder() + .id("foo.bar#mapSensitiveKey$member") + .target(dullString.getId()) + .build()) + .build(); + + MapShape mapSensitiveValue = MapShape.builder() + .id("foo.bar#mapSensitiveValue") + .key(MemberShape.builder() + .id("foo.bar#mapSensitiveValue$member") + .target(dullString.getId()) + .build()) + .value(mapMemberWithSensitiveValueData) + .build(); + + MapShape mapDull = MapShape.builder() + .id("foo.bar#mapDull") + .key(MemberShape.builder() + .id("foo.bar#mapDull$member") + .target(dullString.getId()) + .build()) + .value(MemberShape.builder() + .id("foo.bar#mapDull$member") + .target(dullString.getId()) + .build()) + .build(); + + MapShape nested2 = MapShape.builder() + .id("foo.bar#mapNested2") + .key(MemberShape.builder() + .id("foo.bar#mapNested2$member") + .target(dullString.getId()) + .build()) + .value(MemberShape.builder() + .id("foo.bar#mapNested2$member") + .target(mapSensitiveValue) + .build()) + .build(); + + MapShape nested = MapShape.builder() + .id("foo.bar#mapNested") + .key(MemberShape.builder() + .id("foo.bar#mapNested$member") + .target(dullString.getId()) + .build()) + .value(MemberShape.builder() + .id("foo.bar#mapNested$member") + .target(nested2) + .build()) + .build(); + + private Model model = Model.builder() + .addShapes( + sensitiveString, dullString, + memberWithSensitiveData, memberWithDullData, + structureShapeSensitive, structureShapeDull, + collectionSensitive, collectionDull, + mapSensitiveKey, mapSensitiveValue, mapDull, + nested, nested2) + .build(); + + private SensitiveDataFinder sensitiveDataFinder = new SensitiveDataFinder(model); + + @Test + public void findsSensitiveData_inShapes() { + assertThat(sensitiveDataFinder.findsSensitiveDataIn(sensitiveString), equalTo(true)); + assertThat(sensitiveDataFinder.findsSensitiveDataIn(dullString), equalTo(false)); + } + + @Test + public void findsSensitiveData_inTargetShapes() { + assertThat(sensitiveDataFinder.findsSensitiveDataIn(memberWithSensitiveData), equalTo(true)); + assertThat(sensitiveDataFinder.findsSensitiveDataIn(memberWithDullData), equalTo(false)); + } + + @Test + public void findsSensitiveData_inStructures() { + assertThat(sensitiveDataFinder.findsSensitiveDataIn(structureShapeSensitive), equalTo(true)); + assertThat(sensitiveDataFinder.findsSensitiveDataIn(structureShapeDull), equalTo(false)); + } + + @Test + public void findsSensitiveData_inCollections() { + assertThat(sensitiveDataFinder.findsSensitiveDataIn(collectionSensitive), equalTo(true)); + assertThat(sensitiveDataFinder.findsSensitiveDataIn(collectionDull), equalTo(false)); + } + + @Test + public void findsSensitiveData_inMaps() { + assertThat(sensitiveDataFinder.findsSensitiveDataIn(mapSensitiveKey), equalTo(true)); + assertThat(sensitiveDataFinder.findsSensitiveDataIn(mapSensitiveValue), equalTo(true)); + assertThat(sensitiveDataFinder.findsSensitiveDataIn(mapDull), equalTo(false)); + } + + @Test + public void findsSensitiveData_deeplyNested() { + assertThat(sensitiveDataFinder.findsSensitiveDataIn(nested), equalTo(true)); + } }