Skip to content

Commit 73efec8

Browse files
authored
Added pipeline API for chained use of exclude and obfuscate (#11)
1 parent 67c2abb commit 73efec8

File tree

6 files changed

+247
-0
lines changed

6 files changed

+247
-0
lines changed

README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,35 @@ Map<String, Object> result = new DotPathQL().obfuscate(userObject, List.of(
7575
));
7676
```
7777

78+
### Pipeline Usage
79+
80+
The pipeline feature allows you to chain multiple operations using a fluent API. Supports combining `exclude` and `obfuscate` operations:
81+
82+
```java
83+
DotPathQL dotPathQL = new DotPathQL();
84+
85+
// Chain exclude and obfuscate operations in a single pipeline
86+
Map<String, Object> result = dotPathQL.pipeline(userObject)
87+
.exclude(List.of("additionalInfo.lastLogin"))
88+
.obfuscate(List.of("address.zipCode", "phoneNumber"))
89+
.execute();
90+
91+
// Using default paths for exclude and obfuscate
92+
dotPathQL.addDefaultExcludePaths(List.of("password"));
93+
dotPathQL.addDefaultObfuscatePaths(List.of("ssn", "creditCard.number"));
94+
95+
Map<String, Object> result = dotPathQL.pipeline(userObject)
96+
.exclude() // Uses default exclude paths
97+
.obfuscate() // Uses default obfuscate paths
98+
.execute();
99+
```
100+
101+
#### Benefits of Pipeline API
102+
- **Fluent Interface**: More readable and intuitive method chaining
103+
- **Single Execution**: Apply multiple transformations in one operation
104+
- **Performance**: Avoids multiple object traversals
105+
- **Consistency**: Predictable processing order for combined operations
106+
78107
## Supported Data Structures
79108

80109
- Simple Properties (primitive and object types)

src/main/java/ca/trackerforce/DotPathQL.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,4 +135,15 @@ public String toJson(Map<String, Object> sourceMap, boolean prettier) {
135135
return pathPrinter.toJson(sourceMap, prettier);
136136
}
137137

138+
/**
139+
* Creates a pipeline for the given source object that allows chaining multiple operations.
140+
*
141+
* @param <T> the type of the source object
142+
* @param source the source object to process
143+
* @return a Pipeline instance for method chaining
144+
*/
145+
public <T> Pipeline<T> pipeline(T source) {
146+
return new Pipeline<>(source, pathExclude, pathObfuscate);
147+
}
148+
138149
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package ca.trackerforce;
2+
3+
import ca.trackerforce.path.api.DotPath;
4+
5+
import java.util.ArrayList;
6+
import java.util.List;
7+
import java.util.Map;
8+
9+
/**
10+
* Pipeline for chaining multiple operations on an object using a fluent API.
11+
*
12+
* @author petruki
13+
* @since 2025-09-24
14+
*/
15+
public class Pipeline<T> {
16+
17+
private final T source;
18+
private final DotPath pathExclude;
19+
private final DotPath pathObfuscate;
20+
private final List<String> excludePaths;
21+
private final List<String> obfuscatePaths;
22+
23+
/**
24+
* Creates a new Pipeline instance for the given source object.
25+
*
26+
* @param source the source object to be processed by the pipeline operations
27+
* @param pathExclude the DotPath instance for exclude operations from DotPathQL
28+
* @param pathObfuscate the DotPath instance for obfuscate operations from DotPathQL
29+
*/
30+
public Pipeline(T source, DotPath pathExclude, DotPath pathObfuscate) {
31+
this.source = source;
32+
this.pathExclude = pathExclude;
33+
this.pathObfuscate = pathObfuscate;
34+
this.excludePaths = new ArrayList<>();
35+
this.obfuscatePaths = new ArrayList<>();
36+
}
37+
38+
/**
39+
* Adds paths to be excluded from the final result.
40+
*
41+
* @param paths the list of paths to exclude
42+
* @return this Pipeline instance for method chaining
43+
*/
44+
public Pipeline<T> exclude(List<String> paths) {
45+
this.excludePaths.addAll(paths);
46+
return this;
47+
}
48+
49+
/**
50+
* Triggers exclusion with no paths. Used with default exclude paths if any.
51+
*
52+
* @return this Pipeline instance for method chaining
53+
*/
54+
public Pipeline<T> exclude() {
55+
return exclude(List.of());
56+
}
57+
58+
/**
59+
* Adds paths to be obfuscated in the final result.
60+
*
61+
* @param paths the list of paths to obfuscate
62+
* @return this Pipeline instance for method chaining
63+
*/
64+
public Pipeline<T> obfuscate(List<String> paths) {
65+
this.obfuscatePaths.addAll(paths);
66+
return this;
67+
}
68+
69+
/**
70+
* Triggers obfuscation with no paths. Used with default obfuscate paths if any.
71+
*
72+
* @return this Pipeline instance for method chaining
73+
*/
74+
public Pipeline<T> obfuscate() {
75+
return obfuscate(List.of());
76+
}
77+
78+
/**
79+
* Executes the pipeline operations and returns the final result.
80+
*
81+
* @return a map containing the processed object
82+
*/
83+
public Map<String, Object> execute() {
84+
Map<String, Object> result = pathExclude.run(source, excludePaths);
85+
86+
if (pathObfuscate.hasDefaultPaths() || !obfuscatePaths.isEmpty()) {
87+
return pathObfuscate.run(result, obfuscatePaths);
88+
}
89+
90+
return result;
91+
}
92+
}

src/main/java/ca/trackerforce/path/PathCommon.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ public void addDefaultPaths(List<String> paths) {
4444
defaultPaths.addAll(paths);
4545
}
4646

47+
@Override
48+
public boolean hasDefaultPaths() {
49+
return !defaultPaths.isEmpty();
50+
}
51+
4752
/**
4853
* Executes the path processing logic for the given source object.
4954
*

src/main/java/ca/trackerforce/path/api/DotPath.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,11 @@ public interface DotPath {
2525
* @param paths the list of paths to add as default paths
2626
*/
2727
void addDefaultPaths(List<String> paths);
28+
29+
/**
30+
* Checks if there are any default paths set.
31+
*
32+
* @return true if there are default paths, false otherwise
33+
*/
34+
boolean hasDefaultPaths();
2835
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package ca.trackerforce;
2+
3+
import org.junit.jupiter.params.ParameterizedTest;
4+
import org.junit.jupiter.params.provider.Arguments;
5+
import org.junit.jupiter.params.provider.MethodSource;
6+
7+
import java.util.List;
8+
import java.util.stream.Stream;
9+
10+
import static org.junit.jupiter.api.Assertions.*;
11+
12+
class PipelineTypeClassRecordTest {
13+
14+
DotPathQL dotPathQL = new DotPathQL();
15+
16+
static Stream<Arguments> userDetailProvider() {
17+
return Stream.of(
18+
Arguments.of("Record type", ca.trackerforce.fixture.record.UserDetail.of()),
19+
Arguments.of("Class type", ca.trackerforce.fixture.clazz.UserDetail.of())
20+
);
21+
}
22+
23+
@ParameterizedTest(name = "{0}")
24+
@MethodSource("userDetailProvider")
25+
void shouldPipeObfuscateFields(String implementation, Object userDetail) {
26+
// Given
27+
var result = dotPathQL.pipeline(userDetail)
28+
.obfuscate(List.of("address.zipCode"))
29+
.execute();
30+
31+
// Then
32+
var address = DotUtils.mapFrom(result, "address");
33+
assertNotNull(address);
34+
assertEquals("****", address.get("zipCode"));
35+
assertTrue(address.containsKey("street"));
36+
assertTrue(address.containsKey("city"));
37+
}
38+
39+
@ParameterizedTest(name = "{0}")
40+
@MethodSource("userDetailProvider")
41+
void shouldPipeExcludeFields(String implementation, Object userDetail) {
42+
// Given
43+
var result = dotPathQL.pipeline(userDetail)
44+
.exclude(List.of("additionalInfo.lastLogin"))
45+
.execute();
46+
47+
// Then
48+
var addInfo = DotUtils.mapFrom(result, "additionalInfo");
49+
assertNotNull(addInfo);
50+
assertFalse(addInfo.containsKey("lastLogin")); // Excluded
51+
assertTrue(addInfo.containsKey("subscriptionStatus"));
52+
assertTrue(addInfo.containsKey("preferredLanguage"));
53+
}
54+
55+
@ParameterizedTest(name = "{0}")
56+
@MethodSource("userDetailProvider")
57+
void shouldPipeObfuscateAndExcludeFields(String implementation, Object userDetail) {
58+
// Given
59+
var result = dotPathQL.pipeline(userDetail)
60+
.exclude(List.of("additionalInfo.lastLogin"))
61+
.obfuscate(List.of("address.zipCode"))
62+
.execute();
63+
64+
// Then
65+
var address = DotUtils.mapFrom(result, "address");
66+
assertNotNull(address);
67+
assertEquals("****", address.get("zipCode"));
68+
assertTrue(address.containsKey("street"));
69+
assertTrue(address.containsKey("city"));
70+
71+
var addInfo = DotUtils.mapFrom(result, "additionalInfo");
72+
assertNotNull(addInfo);
73+
assertFalse(addInfo.containsKey("lastLogin")); // Excluded
74+
assertTrue(addInfo.containsKey("subscriptionStatus"));
75+
assertTrue(addInfo.containsKey("preferredLanguage"));
76+
}
77+
78+
@ParameterizedTest(name = "{0}")
79+
@MethodSource("userDetailProvider")
80+
void shouldPipeObfuscateAndExcludeFieldsUsingDefaultPaths(String implementation, Object userDetail) {
81+
// Given
82+
dotPathQL.addDefaultExcludePaths(List.of("additionalInfo.lastLogin"));
83+
dotPathQL.addDefaultObfuscatePaths(List.of("address.zipCode"));
84+
85+
var result = dotPathQL.pipeline(userDetail)
86+
.exclude()
87+
.obfuscate()
88+
.execute();
89+
90+
// Then
91+
var address = DotUtils.mapFrom(result, "address");
92+
assertNotNull(address);
93+
assertEquals("****", address.get("zipCode"));
94+
assertTrue(address.containsKey("street"));
95+
assertTrue(address.containsKey("city"));
96+
97+
var addInfo = DotUtils.mapFrom(result, "additionalInfo");
98+
assertNotNull(addInfo);
99+
assertFalse(addInfo.containsKey("lastLogin")); // Excluded
100+
assertTrue(addInfo.containsKey("subscriptionStatus"));
101+
assertTrue(addInfo.containsKey("preferredLanguage"));
102+
}
103+
}

0 commit comments

Comments
 (0)