Skip to content

Commit 6c9340b

Browse files
authored
Merge pull request #16 from pettermahlen/redacting-tostring
add support for redacting field values in toString methods
2 parents f57f4fd + 5ab36aa commit 6c9340b

8 files changed

Lines changed: 108 additions & 18 deletions

File tree

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,11 @@ if (message.isLogin()) {
250250
the `First` class doesn't exist until the annotation processor has run, so the `Second_dataenum` spec
251251
must reference the `First_dataenum` spec. If `First_dataenum` is in a different package than
252252
`Second_dataenum`, it must of course be public.
253-
253+
- If you have sensitive information in a field and don't want the generated `toString` method to
254+
print that information, you can use the `@Redacted` annotation:
255+
```java
256+
dataenum_case UserInfo(String name, @Redacted String password);
257+
```
254258

255259
## Configuration
256260

dataenum-integration-test/src/test/java/com/spotify/dataenum/it/OuterIntegrationTest.java

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import static org.assertj.core.api.Assertions.assertThat;
2525
import static org.assertj.core.api.Assertions.assertThatThrownBy;
2626

27+
import com.spotify.dataenum.it.Testing.RedactedValue;
2728
import com.spotify.dataenum.it.Testing.Three;
2829
import com.spotify.dataenum.it.Testing.Two;
2930
import org.junit.Before;
@@ -41,29 +42,35 @@ public void setUp() throws Exception {
4142
@Test
4243
public void shouldAllowMapping() throws Exception {
4344
// should compile and not show warnings
44-
String s = t.map(one -> String.valueOf(one.i()), Two::s, Three::s);
45+
String s = t.map(one -> String.valueOf(one.i()), Two::s, Three::s, RedactedValue::shouldShow);
4546

4647
// use s to ensure this test class has zero warnings
4748
assertThat(s).isEqualTo("1");
4849
}
4950

5051
@Test
5152
public void shouldAllowConvenientTodoForMap() throws Exception {
52-
assertThatThrownBy(() -> t.map(one -> todo(), Two::s, Three::s))
53+
assertThatThrownBy(() -> t.map(one -> todo(), Two::s, Three::s, RedactedValue::shouldShow))
5354
.isInstanceOf(UnsupportedOperationException.class)
5455
.hasMessageContaining("TODO");
5556
}
5657

5758
@Test
5859
public void shouldAllowConvenientTodoForMatch() throws Exception {
59-
assertThatThrownBy(() -> t.match(one -> todo(), two -> {}, three -> {}))
60+
assertThatThrownBy(() -> t.match(one -> todo(), two -> {}, three -> {}, value -> {}))
6061
.isInstanceOf(UnsupportedOperationException.class)
6162
.hasMessageContaining("TODO");
6263
}
6364

6465
@Test
6566
public void shouldAllowConvenientIllegalStateForMap() throws Exception {
66-
assertThatThrownBy(() -> t.map(one -> illegal("nono: " + one.i()), Two::s, Three::s))
67+
assertThatThrownBy(
68+
() ->
69+
t.map(
70+
one -> illegal("nono: " + one.i()),
71+
Two::s,
72+
Three::s,
73+
RedactedValue::shouldShow))
6774
.isInstanceOf(IllegalStateException.class)
6875
.hasMessage("nono: 1");
6976
}
@@ -73,8 +80,18 @@ public void shouldAllowConvenientIllegalStateForMatch() throws Exception {
7380
assertThatThrownBy(
7481
() ->
7582
t.match(
76-
one -> illegal("nope, should be: " + (one.i() + 1)), two -> {}, three -> {}))
83+
one -> illegal("nope, should be: " + (one.i() + 1)),
84+
two -> {},
85+
three -> {},
86+
value -> {}))
7787
.isInstanceOf(IllegalStateException.class)
7888
.hasMessage("nope, should be: 2");
7989
}
90+
91+
@Test
92+
public void shouldRedactAnnotatedFieldsInToString() {
93+
assertThat(Testing.redactedValue("i want to see this", 866).toString())
94+
.contains("i want to see this")
95+
.doesNotContain("866");
96+
}
8097
}

dataenum-integration-test/src/test/java/com/spotify/dataenum/it/Testing_dataenum.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
package com.spotify.dataenum.it;
2121

2222
import com.spotify.dataenum.DataEnum;
23+
import com.spotify.dataenum.Redacted;
2324
import com.spotify.dataenum.dataenum_case;
2425

2526
/** For use in integration-type tests. */
@@ -30,4 +31,6 @@ interface Testing_dataenum {
3031
dataenum_case Two(String s);
3132

3233
dataenum_case Three(String s);
34+
35+
dataenum_case RedactedValue(String shouldShow, @Redacted Integer redacted);
3336
}

dataenum-processor/src/main/java/com/spotify/dataenum/processor/data/Parameter.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,13 @@ public class Parameter {
2525
private final String name;
2626
private final TypeName type;
2727
private final boolean nullable;
28+
private final boolean redacted;
2829

29-
public Parameter(String name, TypeName type, boolean nullable) {
30+
public Parameter(String name, TypeName type, boolean nullable, boolean redacted) {
3031
this.name = name;
3132
this.type = type;
3233
this.nullable = nullable;
34+
this.redacted = redacted;
3335
}
3436

3537
public String name() {
@@ -43,4 +45,8 @@ public TypeName type() {
4345
public boolean canBeNull() {
4446
return nullable;
4547
}
48+
49+
public boolean redacted() {
50+
return redacted;
51+
}
4652
}

dataenum-processor/src/main/java/com/spotify/dataenum/processor/generator/data/OutputValueFactory.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,9 @@ static OutputValue create(Value value, ClassName specOutputClass, Spec spec)
5252
if (isDataEnumParameter(rawParamType)) {
5353
TypeName paramOutputType =
5454
withParametersFromOther(toOutputClass(rawParamType), parameter.type());
55-
parameters.add(new Parameter(parameter.name(), paramOutputType, parameter.canBeNull()));
55+
parameters.add(
56+
new Parameter(
57+
parameter.name(), paramOutputType, parameter.canBeNull(), parameter.redacted()));
5658
} else {
5759
parameters.add(parameter);
5860
}

dataenum-processor/src/main/java/com/spotify/dataenum/processor/generator/value/ValueTypeFactory.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -267,11 +267,17 @@ private static MethodSpec createToString(OutputValue value) {
267267
for (Parameter parameter : value.parameters()) {
268268
String fieldName = parameter.name();
269269

270+
String valueFormat = parameter.redacted() ? "\"***\"" : "$1L";
271+
270272
if (first) {
271273
first = false;
272-
result.addStatement("builder.append(\"$2L{$1N=\").append($1L)", fieldName, value.name());
274+
result.addStatement(
275+
String.format("builder.append(\"$2L{$1N=\").append(%s)", valueFormat),
276+
fieldName,
277+
value.name());
273278
} else {
274-
result.addStatement("builder.append(\", $1N=\").append($1L)", fieldName);
279+
result.addStatement(
280+
String.format("builder.append(\", $1N=\").append(%s)", valueFormat), fieldName);
275281
}
276282
}
277283

dataenum-processor/src/main/java/com/spotify/dataenum/processor/parser/ValueParser.java

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919
*/
2020
package com.spotify.dataenum.processor.parser;
2121

22+
import com.spotify.dataenum.Redacted;
2223
import com.spotify.dataenum.dataenum_case;
24+
import com.spotify.dataenum.function.Function;
2325
import com.spotify.dataenum.processor.data.Parameter;
2426
import com.spotify.dataenum.processor.data.Value;
2527
import com.squareup.javapoet.TypeName;
@@ -87,21 +89,38 @@ static Value parse(Element element, ProcessingEnvironment processingEnv) {
8789
String parameterName = parameterElement.getSimpleName().toString();
8890
TypeName parameterType = TypeName.get(parameterElement.asType());
8991

90-
boolean nullable = false;
91-
for (AnnotationMirror annotation : parameterElement.getAnnotationMirrors()) {
92-
if (isNullableAnnotation(annotation)) {
93-
nullable = true;
94-
break;
95-
}
96-
}
92+
boolean nullable = isAnnotationPresent(parameterElement, ValueParser::isNullableAnnotation);
93+
boolean redacted =
94+
isAnnotationPresent(parameterElement, ValueParser.isRedactedAnnotation(processingEnv));
9795

98-
parameters.add(new Parameter(parameterName, parameterType, nullable));
96+
parameters.add(new Parameter(parameterName, parameterType, nullable, redacted));
9997
}
10098

10199
String valueSimpleName = methodElement.getSimpleName().toString();
102100
return new Value(valueSimpleName, parameters);
103101
}
104102

103+
private static Function<AnnotationMirror, Boolean> isRedactedAnnotation(
104+
ProcessingEnvironment processingEnv) {
105+
final Types types = processingEnv.getTypeUtils();
106+
final Elements elements = processingEnv.getElementUtils();
107+
108+
return annotationMirror ->
109+
types.isSameType(
110+
annotationMirror.getAnnotationType(),
111+
elements.getTypeElement(Redacted.class.getCanonicalName()).asType());
112+
}
113+
114+
private static boolean isAnnotationPresent(
115+
VariableElement parameterElement, Function<AnnotationMirror, Boolean> criterion) {
116+
for (AnnotationMirror annotation : parameterElement.getAnnotationMirrors()) {
117+
if (criterion.apply(annotation)) {
118+
return true;
119+
}
120+
}
121+
return false;
122+
}
123+
105124
private static boolean isValueSpecMarker(
106125
TypeMirror returnType, ProcessingEnvironment processingEnvironment) {
107126
Types types = processingEnvironment.getTypeUtils();
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* -\-\-
3+
* DataEnum
4+
* --
5+
* Copyright (c) 2017 Spotify AB
6+
* --
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
* -/-/-
19+
*/
20+
package com.spotify.dataenum;
21+
22+
import java.lang.annotation.ElementType;
23+
import java.lang.annotation.Retention;
24+
import java.lang.annotation.RetentionPolicy;
25+
import java.lang.annotation.Target;
26+
27+
/**
28+
* Marks a dataenum field for redaction, meaning that the generated toString() method for that case
29+
* will not output the contents of that field.
30+
*/
31+
@Retention(RetentionPolicy.SOURCE)
32+
@Target(ElementType.PARAMETER)
33+
public @interface Redacted {}

0 commit comments

Comments
 (0)