Skip to content

Commit 36c0243

Browse files
committed
Update version to 0.17.0, enhance YAML support with a new YamlBuilder API, improve metadata processing, and introduce comprehensive end-to-end testing infrastructure. Add new examples and documentation for YAML configurations, along with updates to existing sample plans and tasks.
1 parent 4e3aeb7 commit 36c0243

61 files changed

Lines changed: 4824 additions & 1160 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ app/src/test/resources/sample/documentation
3232
app/src/test/resources/sample/java
3333
app/src/test/resources/sample/report
3434
app/src/test/resources/sample/plan-gen
35-
app/src/test/resources/sample/e2e
35+
app/src/test/resources/sample/e2e/generated
36+
app/src/test/resources/sample/e2e/report
37+
app/src/test/resources/sample/e2e/data
3638

3739
api/out
3840
api/src/test/resources/sample/documentation

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ and deep dive into issues [from the generated report](https://data.catering/late
3838

3939
1. Docker
4040
```shell
41-
docker run -d -i -p 9898:9898 -e DEPLOY_MODE=standalone --name datacaterer datacatering/data-caterer:0.16.11
41+
docker run -d -i -p 9898:9898 -e DEPLOY_MODE=standalone --name datacaterer datacatering/data-caterer:0.17.0
4242
```
4343
[Open localhost:9898](http://localhost:9898).
4444
1. [Run Scala/Java examples](#run-scalajava-examples)

api/src/main/java/io/github/datacatering/datacaterer/javaapi/api/PlanRun.java

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,18 @@ public GeneratorBuilder generator() {
124124
return new GeneratorBuilder();
125125
}
126126

127+
/**
128+
* Creates a WeightedValue for weighted field values. Useful for oneOfWeightedJava field method.
129+
*
130+
* @param value The value
131+
* @param weight The weight associated with the value
132+
* @return A WeightedValue containing the value and weight
133+
*/
134+
public static WeightedValue weightedValue(Object value, double weight) {
135+
return new WeightedValue(value, weight);
136+
}
137+
138+
127139
/**
128140
* Creates a CountBuilder instance.
129141
*
@@ -819,6 +831,21 @@ public void execute(
819831
execute(plan(), configuration(), Collections.emptyList(), connectionTaskBuilder, connectionTaskBuilders);
820832
}
821833

834+
/**
835+
* Executes the data catering plan with the provided list of tasks.
836+
*
837+
* @param connectionTaskBuilders A list of ConnectionTaskBuilder objects representing tasks to be executed.
838+
*/
839+
public void execute(List<ConnectionTaskBuilder<?>> connectionTaskBuilders) {
840+
if (connectionTaskBuilders.isEmpty()) {
841+
throw new IllegalArgumentException("At least one ConnectionTaskBuilder must be provided");
842+
}
843+
ConnectionTaskBuilder<?> first = connectionTaskBuilders.get(0);
844+
ConnectionTaskBuilder<?>[] rest = connectionTaskBuilders.subList(1, connectionTaskBuilders.size())
845+
.toArray(new ConnectionTaskBuilder<?>[0]);
846+
execute(first, rest);
847+
}
848+
822849

823850
/**
824851
* Executes the data catering plan with the provided configurations.
@@ -835,6 +862,25 @@ public void execute(
835862
execute(plan(), configurationBuilder, Collections.emptyList(), connectionTaskBuilder, connectionTaskBuilders);
836863
}
837864

865+
/**
866+
* Executes the data catering plan with the provided configuration and list of tasks.
867+
*
868+
* @param configurationBuilder The DataCatererConfigurationBuilder object representing the configuration for the plan execution.
869+
* @param connectionTaskBuilders A list of ConnectionTaskBuilder objects representing tasks to be executed.
870+
*/
871+
public void execute(
872+
DataCatererConfigurationBuilder configurationBuilder,
873+
List<ConnectionTaskBuilder<?>> connectionTaskBuilders
874+
) {
875+
if (connectionTaskBuilders.isEmpty()) {
876+
throw new IllegalArgumentException("At least one ConnectionTaskBuilder must be provided");
877+
}
878+
ConnectionTaskBuilder<?> first = connectionTaskBuilders.get(0);
879+
ConnectionTaskBuilder<?>[] rest = connectionTaskBuilders.subList(1, connectionTaskBuilders.size())
880+
.toArray(new ConnectionTaskBuilder<?>[0]);
881+
execute(configurationBuilder, first, rest);
882+
}
883+
838884
/**
839885
* Executes the data catering plan with the provided configurations.
840886
*
@@ -852,6 +898,27 @@ public void execute(
852898
execute(planBuilder, configurationBuilder, Collections.emptyList(), connectionTaskBuilder, connectionTaskBuilders);
853899
}
854900

901+
/**
902+
* Executes the data catering plan with the provided plan, configuration and list of tasks.
903+
*
904+
* @param planBuilder The PlanBuilder object representing the plan to be executed.
905+
* @param configurationBuilder The DataCatererConfigurationBuilder object representing the configuration for the plan execution.
906+
* @param connectionTaskBuilders A list of ConnectionTaskBuilder objects representing tasks to be executed.
907+
*/
908+
public void execute(
909+
PlanBuilder planBuilder,
910+
DataCatererConfigurationBuilder configurationBuilder,
911+
List<ConnectionTaskBuilder<?>> connectionTaskBuilders
912+
) {
913+
if (connectionTaskBuilders.isEmpty()) {
914+
throw new IllegalArgumentException("At least one ConnectionTaskBuilder must be provided");
915+
}
916+
ConnectionTaskBuilder<?> first = connectionTaskBuilders.get(0);
917+
ConnectionTaskBuilder<?>[] rest = connectionTaskBuilders.subList(1, connectionTaskBuilders.size())
918+
.toArray(new ConnectionTaskBuilder<?>[0]);
919+
execute(planBuilder, configurationBuilder, first, rest);
920+
}
921+
855922
/**
856923
* Executes the data catering plan with the provided configurations.
857924
*
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package io.github.datacatering.datacaterer.javaapi.api;
2+
3+
/**
4+
* A simple container for value-weight pairs used in Java API for oneOfWeighted field method.
5+
*/
6+
public class WeightedValue {
7+
private final Object value;
8+
private final double weight;
9+
10+
/**
11+
* Constructs a new WeightedValue.
12+
*
13+
* @param value The value
14+
* @param weight The weight associated with the value
15+
*/
16+
public WeightedValue(Object value, double weight) {
17+
this.value = value;
18+
this.weight = weight;
19+
}
20+
21+
/**
22+
* Gets the value.
23+
*
24+
* @return The value
25+
*/
26+
public Object value() {
27+
return value;
28+
}
29+
30+
/**
31+
* Gets the weight.
32+
*
33+
* @return The weight
34+
*/
35+
public double weight() {
36+
return weight;
37+
}
38+
39+
/**
40+
* Static factory method for easier creation.
41+
*
42+
* @param value The value
43+
* @param weight The weight associated with the value
44+
* @return A new WeightedValue instance
45+
*/
46+
public static WeightedValue of(Object value, double weight) {
47+
return new WeightedValue(value, weight);
48+
}
49+
}

api/src/main/scala/io/github/datacatering/datacaterer/api/PlanRun.scala

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -641,6 +641,26 @@ trait PlanRun {
641641
execute(configuration, connectionTaskBuilder, connectionTaskBuilders: _*)
642642
}
643643

644+
/**
645+
* Execute with non-default configurations for a set of tasks
646+
*
647+
* @param baseConfiguration Runtime configurations
648+
* @param connectionTaskBuilders Connection and task builders
649+
*/
650+
def execute(
651+
baseConfiguration: DataCatererConfigurationBuilder,
652+
connectionTaskBuilders: List[ConnectionTaskBuilder[_]]
653+
): Unit = {
654+
if (connectionTaskBuilders.isEmpty) {
655+
throw new IllegalArgumentException("At least one ConnectionTaskBuilder must be provided")
656+
}
657+
if (connectionTaskBuilders.tail.isEmpty) {
658+
execute(plan, baseConfiguration, List(), connectionTaskBuilders.head)
659+
} else {
660+
execute(plan, baseConfiguration, List(), connectionTaskBuilders.head, connectionTaskBuilders.tail: _*)
661+
}
662+
}
663+
644664
/**
645665
* Execute with non-default configurations for a set of tasks
646666
*
@@ -673,6 +693,28 @@ trait PlanRun {
673693
execute(planBuilder, baseConfiguration, List(), connectionTaskBuilder, connectionTaskBuilders: _*)
674694
}
675695

696+
/**
697+
* Execute with non-default configurations with validations and tasks
698+
*
699+
* @param planBuilder Plan to set high level task configurations
700+
* @param baseConfiguration Runtime configurations
701+
* @param connectionTaskBuilders Connection and task builders
702+
*/
703+
def execute(
704+
planBuilder: PlanBuilder,
705+
baseConfiguration: DataCatererConfigurationBuilder,
706+
connectionTaskBuilders: List[ConnectionTaskBuilder[_]]
707+
): Unit = {
708+
if (connectionTaskBuilders.isEmpty) {
709+
throw new IllegalArgumentException("At least one ConnectionTaskBuilder must be provided")
710+
}
711+
if (connectionTaskBuilders.tail.isEmpty) {
712+
execute(planBuilder, baseConfiguration, List(), connectionTaskBuilders.head)
713+
} else {
714+
execute(planBuilder, baseConfiguration, List(), connectionTaskBuilders.head, connectionTaskBuilders.tail: _*)
715+
}
716+
}
717+
676718
/**
677719
* Execute with non-default configurations with validations and tasks. Validations have to be enabled before running
678720
* (see [[DataCatererConfigurationBuilder.enableValidation()]].

api/src/main/scala/io/github/datacatering/datacaterer/api/TaskBuilder.scala

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import io.github.datacatering.datacaterer.api.model.Constants._
88
import io.github.datacatering.datacaterer.api.model.{ArrayType, Count, DataType, DoubleType, Field, HeaderType, PerFieldCount, Step, StringType, Task, TaskSummary}
99

1010
import scala.annotation.varargs
11+
import scala.collection.JavaConverters._
1112

1213
/**
1314
* Builds a `TaskSummary` with optional `Task` information.
@@ -147,17 +148,25 @@ case class TaskBuilder(task: Task = Task()) {
147148
@varargs def steps(steps: StepBuilder*): TaskBuilder = this.modify(_.task.steps)(_ ++ steps.map(_.step))
148149

149150
/**
150-
* Create a TaskBuilder that loads base configuration from a YAML file.
151-
* This allows referencing existing YAML task definitions while still being able to override
152-
* specific configurations using the builder pattern.
151+
* Create a TaskBuilder that references configuration from a YAML file.
152+
* This stores YAML metadata information that will be resolved during execution.
153+
* The actual YAML loading will happen during execution when the task is processed.
153154
*
154-
* @param yamlConfig Configuration specifying which YAML file to load
155-
* @return TaskBuilder with YAML task as base configuration
155+
* Note: The YAML configuration is stored as a placeholder step with the YAML metadata
156+
* in its options. The actual YAML task will be loaded and merged during plan execution.
157+
*
158+
* @param yamlConfig Configuration specifying which YAML file to reference
159+
* @return TaskBuilder with YAML reference metadata stored in a placeholder step
156160
*/
157161
def fromYaml(yamlConfig: YamlConfig): TaskBuilder = {
158-
// Add special marker to indicate this task should load from YAML
159-
// The actual YAML loading will happen during execution when the task is processed
160-
this.modify(_.task.name).setTo(s"${task.name}_yaml_${yamlConfig.taskFile.getOrElse("unknown").hashCode}")
162+
// Store YAML config as a placeholder step's options
163+
// This will be resolved during execution by the metadata loading pipeline
164+
val yamlOptions = yamlConfig.toOptionsMap
165+
val placeholderStep = Step(
166+
name = "yaml_placeholder",
167+
options = yamlOptions
168+
)
169+
this.modify(_.task.steps)(_ ++ List(placeholderStep))
161170
}
162171
}
163172

@@ -775,6 +784,38 @@ case class FieldBuilder(field: Field = Field()) {
775784
def oneOfWeighted(values: List[(Any, Double)]): List[FieldBuilder] =
776785
if (values.nonEmpty) oneOfWeighted(values.head, values.tail: _*) else List(this)
777786

787+
/**
788+
* Java-friendly overload for oneOfWeighted method using WeightedValue objects.
789+
* Sets the field to use weighted random values from the provided weighted values.
790+
* Returns an array to work seamlessly with Java varargs in fields() method.
791+
*
792+
* @param value The first value-weight pair (required)
793+
* @param values Additional value-weight pairs
794+
* @return An array of FieldBuilder instances (the weighted field and a weight field)
795+
*/
796+
@varargs def oneOfWeightedJava(value: io.github.datacatering.datacaterer.javaapi.api.WeightedValue, values: io.github.datacatering.datacaterer.javaapi.api.WeightedValue*): Array[FieldBuilder] = {
797+
val scalaValues = values.map(wv => (wv.value, wv.weight))
798+
oneOfWeighted((value.value, value.weight), scalaValues: _*).toArray
799+
}
800+
801+
/**
802+
* Java-friendly overload for oneOfWeighted method using a List of WeightedValue.
803+
* Sets the field to use weighted random values from the provided weighted values.
804+
* Returns an array to work seamlessly with Java varargs in fields() method.
805+
*
806+
* @param values A Java List of WeightedValue objects containing value-weight pairs
807+
* @return An array of FieldBuilder instances (the weighted field and a weight field)
808+
*/
809+
def oneOfWeightedJava(values: java.util.List[io.github.datacatering.datacaterer.javaapi.api.WeightedValue]): Array[FieldBuilder] = {
810+
if (values.isEmpty) {
811+
Array(this)
812+
} else {
813+
val first = values.get(0)
814+
val rest = values.subList(1, values.size()).asScala
815+
oneOfWeightedJava(first, rest: _*)
816+
}
817+
}
818+
778819
/**
779820
* Sets the options for the field generator.
780821
*

api/src/main/scala/io/github/datacatering/datacaterer/api/YamlBuilder.scala

Lines changed: 41 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -28,55 +28,77 @@ case class YamlBuilder(yamlConfig: YamlConfig = YamlConfig()) {
2828
}
2929

3030
/**
31-
* Load from a YAML task file. This creates a task builder that references the existing YAML task
32-
* and allows overriding specific configurations.
31+
* Load from a YAML task file. Creates a task builder that references the existing YAML task.
32+
*
33+
* WARNING: If the YAML task file contains multiple tasks or a task with multiple steps,
34+
* you may encounter schema ambiguity. Consider using stepByFile() if you need a specific step.
3335
*
3436
* @param taskFile Path to the YAML task file
3537
* @return TaskBuilder with YAML task as base
3638
*/
37-
def task(taskFile: String): TaskBuilder = {
39+
def taskByFile(taskFile: String): TaskBuilder = {
3840
val updatedConfig = this.modify(_.yamlConfig.taskFile).setTo(Some(taskFile))
3941
TaskBuilder().fromYaml(updatedConfig.yamlConfig)
4042
}
4143

4244
/**
43-
* Load from a YAML task file with specific task name filter.
45+
* Load from a YAML task by name. Creates a task builder that references the existing YAML task
46+
* identified by the task name.
47+
*
48+
* WARNING: If the specified task has multiple steps, you may encounter schema ambiguity.
49+
* Consider using stepByName() if you need a specific step.
4450
*
45-
* @param taskFile Path to the YAML task file
46-
* @param taskName Name of the specific task to use from the YAML file
51+
* @param taskName Name of the specific task to use
4752
* @return TaskBuilder with filtered YAML task as base
4853
*/
49-
def task(taskFile: String, taskName: String): TaskBuilder = {
50-
val updatedConfig = this.modify(_.yamlConfig.taskFile).setTo(Some(taskFile))
51-
.modify(_.yamlConfig.taskName).setTo(Some(taskName))
54+
def taskByName(taskName: String): TaskBuilder = {
55+
val updatedConfig = this.modify(_.yamlConfig.taskName).setTo(Some(taskName))
5256
TaskBuilder().fromYaml(updatedConfig.yamlConfig)
5357
}
5458

5559
/**
56-
* Load from a YAML task file with specific task and step name filters.
60+
* Load a specific step from a YAML task file. Creates a task builder that references a specific step
61+
* within a YAML task file.
5762
*
5863
* @param taskFile Path to the YAML task file
59-
* @param taskName Name of the specific task to use from the YAML file
6064
* @param stepName Name of the specific step to use from the task
61-
* @return TaskBuilder with filtered YAML task as base
65+
* @return TaskBuilder with filtered YAML step as base
6266
*/
63-
def task(taskFile: String, taskName: String, stepName: String): TaskBuilder = {
67+
def stepByFile(taskFile: String, stepName: String): TaskBuilder = {
6468
val updatedConfig = this.modify(_.yamlConfig.taskFile).setTo(Some(taskFile))
65-
.modify(_.yamlConfig.taskName).setTo(Some(taskName))
6669
.modify(_.yamlConfig.stepName).setTo(Some(stepName))
6770
TaskBuilder().fromYaml(updatedConfig.yamlConfig)
6871
}
6972

7073
/**
71-
* Java API - Load from a YAML task file with specific task name filter.
74+
* Load a specific step from a YAML task by name. Creates a task builder that references a specific step
75+
* within a named YAML task.
76+
*
77+
* @param taskName Name of the specific task to use
78+
* @param stepName Name of the specific step to use from the task
79+
* @return TaskBuilder with filtered YAML step as base
7280
*/
73-
def taskJava(taskFile: String, taskName: String): TaskBuilder = task(taskFile, taskName)
81+
def stepByName(taskName: String, stepName: String): TaskBuilder = {
82+
val updatedConfig = this.modify(_.yamlConfig.taskName).setTo(Some(taskName))
83+
.modify(_.yamlConfig.stepName).setTo(Some(stepName))
84+
TaskBuilder().fromYaml(updatedConfig.yamlConfig)
85+
}
7486

7587
/**
76-
* Java API - Load from a YAML task file with specific task and step name filters.
88+
* Load a specific step from a YAML task file with task name filter. Creates a task builder that references
89+
* a specific step within a specific task from a YAML file.
90+
*
91+
* @param taskFile Path to the YAML task file
92+
* @param taskName Name of the specific task to use from the YAML file
93+
* @param stepName Name of the specific step to use from the task
94+
* @return TaskBuilder with filtered YAML step as base
7795
*/
78-
def taskJava(taskFile: String, taskName: String, stepName: String): TaskBuilder =
79-
task(taskFile, taskName, stepName)
96+
def stepByFileAndName(taskFile: String, taskName: String, stepName: String): TaskBuilder = {
97+
val updatedConfig = this.modify(_.yamlConfig.taskFile).setTo(Some(taskFile))
98+
.modify(_.yamlConfig.taskName).setTo(Some(taskName))
99+
.modify(_.yamlConfig.stepName).setTo(Some(stepName))
100+
TaskBuilder().fromYaml(updatedConfig.yamlConfig)
101+
}
80102
}
81103

82104
/**

0 commit comments

Comments
 (0)