Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,35 @@ Map<String, Object> result = new DotPathQL().obfuscate(userObject, List.of(
));
```

### Pipeline Usage

The pipeline feature allows you to chain multiple operations using a fluent API. Supports combining `exclude` and `obfuscate` operations:

```java
DotPathQL dotPathQL = new DotPathQL();

// Chain exclude and obfuscate operations in a single pipeline
Map<String, Object> result = dotPathQL.pipeline(userObject)
.exclude(List.of("additionalInfo.lastLogin"))
.obfuscate(List.of("address.zipCode", "phoneNumber"))
.execute();

// Using default paths for exclude and obfuscate
dotPathQL.addDefaultExcludePaths(List.of("password"));
dotPathQL.addDefaultObfuscatePaths(List.of("ssn", "creditCard.number"));

Map<String, Object> result = dotPathQL.pipeline(userObject)
.exclude() // Uses default exclude paths
.obfuscate() // Uses default obfuscate paths
.execute();
```

#### Benefits of Pipeline API
- **Fluent Interface**: More readable and intuitive method chaining
- **Single Execution**: Apply multiple transformations in one operation
- **Performance**: Avoids multiple object traversals
- **Consistency**: Predictable processing order for combined operations

## Supported Data Structures

- Simple Properties (primitive and object types)
Expand Down
11 changes: 11 additions & 0 deletions src/main/java/ca/trackerforce/DotPathQL.java
Original file line number Diff line number Diff line change
Expand Up @@ -135,4 +135,15 @@ public String toJson(Map<String, Object> sourceMap, boolean prettier) {
return pathPrinter.toJson(sourceMap, prettier);
}

/**
* Creates a pipeline for the given source object that allows chaining multiple operations.
*
* @param <T> the type of the source object
* @param source the source object to process
* @return a Pipeline instance for method chaining
*/
public <T> Pipeline<T> pipeline(T source) {
return new Pipeline<>(source, pathExclude, pathObfuscate);
}

}
92 changes: 92 additions & 0 deletions src/main/java/ca/trackerforce/Pipeline.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package ca.trackerforce;

import ca.trackerforce.path.api.DotPath;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
* Pipeline for chaining multiple operations on an object using a fluent API.
*
* @author petruki
* @since 2025-09-24
*/
public class Pipeline<T> {

private final T source;
private final DotPath pathExclude;
private final DotPath pathObfuscate;
private final List<String> excludePaths;
private final List<String> obfuscatePaths;

/**
* Creates a new Pipeline instance for the given source object.
*
* @param source the source object to be processed by the pipeline operations
* @param pathExclude the DotPath instance for exclude operations from DotPathQL
* @param pathObfuscate the DotPath instance for obfuscate operations from DotPathQL
*/
public Pipeline(T source, DotPath pathExclude, DotPath pathObfuscate) {
this.source = source;
this.pathExclude = pathExclude;
this.pathObfuscate = pathObfuscate;
this.excludePaths = new ArrayList<>();
this.obfuscatePaths = new ArrayList<>();
}

/**
* Adds paths to be excluded from the final result.
*
* @param paths the list of paths to exclude
* @return this Pipeline instance for method chaining
*/
public Pipeline<T> exclude(List<String> paths) {
this.excludePaths.addAll(paths);
return this;
}

/**
* Triggers exclusion with no paths. Used with default exclude paths if any.
*
* @return this Pipeline instance for method chaining
*/
public Pipeline<T> exclude() {
return exclude(List.of());
}

/**
* Adds paths to be obfuscated in the final result.
*
* @param paths the list of paths to obfuscate
* @return this Pipeline instance for method chaining
*/
public Pipeline<T> obfuscate(List<String> paths) {
this.obfuscatePaths.addAll(paths);
return this;
}

/**
* Triggers obfuscation with no paths. Used with default obfuscate paths if any.
*
* @return this Pipeline instance for method chaining
*/
public Pipeline<T> obfuscate() {
return obfuscate(List.of());
}

/**
* Executes the pipeline operations and returns the final result.
*
* @return a map containing the processed object
*/
public Map<String, Object> execute() {
Map<String, Object> result = pathExclude.run(source, excludePaths);

if (pathObfuscate.hasDefaultPaths() || !obfuscatePaths.isEmpty()) {
return pathObfuscate.run(result, obfuscatePaths);
}

return result;
}
}
5 changes: 5 additions & 0 deletions src/main/java/ca/trackerforce/path/PathCommon.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ public void addDefaultPaths(List<String> paths) {
defaultPaths.addAll(paths);
}

@Override
public boolean hasDefaultPaths() {
return !defaultPaths.isEmpty();
}

/**
* Executes the path processing logic for the given source object.
*
Expand Down
7 changes: 7 additions & 0 deletions src/main/java/ca/trackerforce/path/api/DotPath.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,11 @@ public interface DotPath {
* @param paths the list of paths to add as default paths
*/
void addDefaultPaths(List<String> paths);

/**
* Checks if there are any default paths set.
*
* @return true if there are default paths, false otherwise
*/
boolean hasDefaultPaths();
}
103 changes: 103 additions & 0 deletions src/test/java/ca/trackerforce/PipelineTypeClassRecordTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package ca.trackerforce;

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

import java.util.List;
import java.util.stream.Stream;

import static org.junit.jupiter.api.Assertions.*;

class PipelineTypeClassRecordTest {

DotPathQL dotPathQL = new DotPathQL();

static Stream<Arguments> userDetailProvider() {
return Stream.of(
Arguments.of("Record type", ca.trackerforce.fixture.record.UserDetail.of()),
Arguments.of("Class type", ca.trackerforce.fixture.clazz.UserDetail.of())
);
}

@ParameterizedTest(name = "{0}")
@MethodSource("userDetailProvider")
void shouldPipeObfuscateFields(String implementation, Object userDetail) {
// Given
var result = dotPathQL.pipeline(userDetail)
.obfuscate(List.of("address.zipCode"))
.execute();

// Then
var address = DotUtils.mapFrom(result, "address");
assertNotNull(address);
assertEquals("****", address.get("zipCode"));
assertTrue(address.containsKey("street"));
assertTrue(address.containsKey("city"));
}

@ParameterizedTest(name = "{0}")
@MethodSource("userDetailProvider")
void shouldPipeExcludeFields(String implementation, Object userDetail) {
// Given
var result = dotPathQL.pipeline(userDetail)
.exclude(List.of("additionalInfo.lastLogin"))
.execute();

// Then
var addInfo = DotUtils.mapFrom(result, "additionalInfo");
assertNotNull(addInfo);
assertFalse(addInfo.containsKey("lastLogin")); // Excluded
assertTrue(addInfo.containsKey("subscriptionStatus"));
assertTrue(addInfo.containsKey("preferredLanguage"));
}

@ParameterizedTest(name = "{0}")
@MethodSource("userDetailProvider")
void shouldPipeObfuscateAndExcludeFields(String implementation, Object userDetail) {
// Given
var result = dotPathQL.pipeline(userDetail)
.exclude(List.of("additionalInfo.lastLogin"))
.obfuscate(List.of("address.zipCode"))
.execute();

// Then
var address = DotUtils.mapFrom(result, "address");
assertNotNull(address);
assertEquals("****", address.get("zipCode"));
assertTrue(address.containsKey("street"));
assertTrue(address.containsKey("city"));

var addInfo = DotUtils.mapFrom(result, "additionalInfo");
assertNotNull(addInfo);
assertFalse(addInfo.containsKey("lastLogin")); // Excluded
assertTrue(addInfo.containsKey("subscriptionStatus"));
assertTrue(addInfo.containsKey("preferredLanguage"));
}

@ParameterizedTest(name = "{0}")
@MethodSource("userDetailProvider")
void shouldPipeObfuscateAndExcludeFieldsUsingDefaultPaths(String implementation, Object userDetail) {
// Given
dotPathQL.addDefaultExcludePaths(List.of("additionalInfo.lastLogin"));
dotPathQL.addDefaultObfuscatePaths(List.of("address.zipCode"));

var result = dotPathQL.pipeline(userDetail)
.exclude()
.obfuscate()
.execute();

// Then
var address = DotUtils.mapFrom(result, "address");
assertNotNull(address);
assertEquals("****", address.get("zipCode"));
assertTrue(address.containsKey("street"));
assertTrue(address.containsKey("city"));

var addInfo = DotUtils.mapFrom(result, "additionalInfo");
assertNotNull(addInfo);
assertFalse(addInfo.containsKey("lastLogin")); // Excluded
assertTrue(addInfo.containsKey("subscriptionStatus"));
assertTrue(addInfo.containsKey("preferredLanguage"));
}
}
Loading