Skip to content

Commit 50d7af3

Browse files
authored
TomlTransformer implementation
1 parent c51cc4f commit 50d7af3

File tree

3 files changed

+303
-0
lines changed

3 files changed

+303
-0
lines changed

docs/documentation/schemas.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ It also provides a set of ready to use transformers:
1010
* YAML
1111
* XML
1212
* Java Object
13+
* TOML
1314

1415
## Schema
1516

@@ -526,3 +527,48 @@ Or you can use `net.datafaker.providers.base.BaseFaker.populate(java.lang.Class<
526527
val faker = BaseFaker()
527528
val person = faker.populate(Person::class.java, Schema.of(field("name", Supplier { faker.superhero().name() })))
528529
```
530+
531+
532+
## TOML transformation
533+
TOML transformation is similar to YAML and CSV.
534+
535+
The following is an example on how to use it:
536+
537+
=== "Java"
538+
539+
``` java
540+
final BaseFaker faker = new BaseFaker();
541+
542+
TomlTransformer<Object> transformer = new TomlTransformer<>();
543+
Schema<Object, ?> schema = Schema.of(
544+
field("name", () -> faker.name().firstName()),
545+
field("lastname", () -> faker.name().lastName()),
546+
field("phones", () -> Schema.of(
547+
field("worknumbers", () -> ((Stream<?>) faker.<String>stream().suppliers(() -> faker.phoneNumber().phoneNumber()).maxLen(2).build().get())
548+
.collect(Collectors.toList())),
549+
field("cellphones", () -> ((Stream<?>) faker.<String>stream().suppliers(() -> faker.phoneNumber().cellPhone()).maxLen(3).build().get())
550+
.collect(Collectors.toList()))
551+
)),
552+
field("address", () -> Schema.of(
553+
field("city", () -> faker.address().city()),
554+
field("country", () -> faker.address().country()),
555+
field("streetAddress", () -> faker.address().streetAddress())
556+
))
557+
);
558+
559+
System.out.println(transformer.generate(schema, 1));
560+
```
561+
562+
will generate toml with nested fields:
563+
564+
```
565+
name = "Elaine"
566+
lastname = "King"
567+
[phones]
568+
worknumbers = [ "(806) 207-5920", "(505) 640-6195" ]
569+
cellphones = [ "(214) 287-6337", "(872) 940-4806", "(813) 294-1719" ]
570+
[address]
571+
city = "Lake Caitlin"
572+
country = "Mongolia"
573+
streetAddress = "5111 D'Amore Fall"
574+
```
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
package net.datafaker.transformations;
2+
3+
import net.datafaker.sequence.FakeSequence;
4+
5+
import java.util.*;
6+
import java.util.stream.Collectors;
7+
8+
public class TomlTransformer<IN> implements Transformer<IN, String> {
9+
@Override
10+
public String apply(IN input, Schema<IN, ?> schema) {
11+
if (schema == null) {
12+
return "";
13+
}
14+
StringBuilder sb = new StringBuilder();
15+
writeSchema(sb, input, schema, "");
16+
int len = sb.length();
17+
int sepLen = LINE_SEPARATOR.length();
18+
if (len >= sepLen && sb.lastIndexOf(LINE_SEPARATOR, len - sepLen) >= 0) {
19+
sb.setLength(len - sepLen);
20+
}
21+
return sb.toString();
22+
}
23+
24+
@Override
25+
public String generate(Iterable<IN> input, Schema<IN, ?> schema) {
26+
if (input instanceof FakeSequence<IN> fs && fs.isInfinite()) {
27+
throw new IllegalArgumentException("The sequence should be finite of size: " + fs);
28+
}
29+
StringJoiner joiner = new StringJoiner(LINE_SEPARATOR);
30+
for (IN in : input) {
31+
joiner.add(apply(in, schema));
32+
}
33+
return joiner.toString();
34+
}
35+
36+
@Override
37+
public String generate(Schema<IN, ?> schema, int limit) {
38+
StringBuilder sb = new StringBuilder();
39+
for (int i = 0; i < limit; i++) {
40+
sb.append(apply(null, schema));
41+
if (i < limit - 1) {
42+
sb.append(LINE_SEPARATOR);
43+
}
44+
}
45+
return sb.toString();
46+
}
47+
48+
@Override
49+
public String getStartStream(Schema<IN, ?> schema) {
50+
return "";
51+
}
52+
53+
@Override
54+
public String getEndStream() {
55+
return "";
56+
}
57+
58+
private void writeSchema(StringBuilder sb, IN input, Schema<IN, ?> schema, String pathPrefix) {
59+
Field<IN, ?>[] fields = schema.getFields();
60+
Set<String> seen = new HashSet<>();
61+
for (Field<IN, ?> f : fields) {
62+
String key = f.getName();
63+
if (!seen.add(key)) {
64+
continue;
65+
}
66+
Object val = f.transform(input);
67+
writeValue(sb, key, pathPrefix, val);
68+
}
69+
}
70+
71+
private void writeValue(StringBuilder sb, String key, String pathPrefix, Object val) {
72+
String fullPath = pathPrefix.isEmpty() ? key : pathPrefix + "." + key;
73+
74+
if (val instanceof Schema<?, ?> nested) {
75+
sb.append("[").append(fullPath).append("]").append(LINE_SEPARATOR);
76+
writeSchema(sb, null, (Schema<IN, ?>) nested, fullPath);
77+
return;
78+
}
79+
80+
if (val instanceof Collection<?> col) {
81+
if (col.isEmpty()) {
82+
sb.append(key).append(" = []").append(LINE_SEPARATOR);
83+
return;
84+
}
85+
Object first = col.iterator().next();
86+
if (first instanceof Schema<?, ?>) {
87+
for (Object item : col) {
88+
sb.append("[[").append(fullPath).append("]]").append(LINE_SEPARATOR);
89+
writeSchema(sb, null, (Schema<IN, ?>) item, fullPath);
90+
}
91+
} else {
92+
String array = col.stream().map(this::value2String).collect(Collectors.joining(", "));
93+
sb.append(key).append(" = [ ").append(array).append(" ]").append(LINE_SEPARATOR);
94+
}
95+
return;
96+
}
97+
98+
if (val != null && val.getClass().isArray()) {
99+
handleArray(sb, key, val);
100+
return;
101+
}
102+
103+
// Scalar value
104+
sb.append(key).append(" = ").append(value2String(val)).append(LINE_SEPARATOR);
105+
}
106+
107+
private void handleArray(StringBuilder sb, String key, Object array) {
108+
int length = java.lang.reflect.Array.getLength(array);
109+
if (length == 0) {
110+
sb.append(key).append(" = []").append(LINE_SEPARATOR);
111+
return;
112+
}
113+
List<Object> list = new ArrayList<>(length);
114+
for (int i = 0; i < length; i++) {
115+
list.add(java.lang.reflect.Array.get(array, i));
116+
}
117+
String joined = list.stream().map(this::value2String).collect(Collectors.joining(", "));
118+
sb.append(key).append(" = [ ").append(joined).append(" ]").append(LINE_SEPARATOR);
119+
}
120+
121+
private String value2String(Object val) {
122+
if (val == null) {
123+
return "null";
124+
}
125+
if (val instanceof Number || val instanceof Boolean) {
126+
return val.toString();
127+
}
128+
String s = String.valueOf(val).replace("\\", "\\\\").replace("\"", "\\\"");
129+
return "\"" + s + "\"";
130+
}
131+
}
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
package net.datafaker.formats;
2+
3+
import net.datafaker.providers.base.BaseFaker;
4+
import net.datafaker.providers.base.Name;
5+
import net.datafaker.transformations.Schema;
6+
import net.datafaker.transformations.TomlTransformer;
7+
import org.junit.jupiter.params.ParameterizedTest;
8+
import org.junit.jupiter.params.provider.Arguments;
9+
import org.junit.jupiter.params.provider.MethodSource;
10+
import org.junit.jupiter.params.provider.ValueSource;
11+
12+
import java.math.BigDecimal;
13+
import java.util.Collections;
14+
import java.util.List;
15+
import java.util.stream.Collectors;
16+
import java.util.stream.IntStream;
17+
import java.util.stream.Stream;
18+
19+
import static net.datafaker.transformations.Field.field;
20+
import static org.assertj.core.api.Assertions.assertThat;
21+
import static org.junit.jupiter.params.provider.Arguments.of;
22+
23+
class TomlTest {
24+
25+
@ParameterizedTest
26+
@MethodSource("generateTestSchema")
27+
void simpleTomlTest(Schema<String, String> schema, String expected) {
28+
TomlTransformer<String> tomlTransformer = new TomlTransformer<>();
29+
assertThat(tomlTransformer.generate(schema, 1)).isEqualTo(expected);
30+
}
31+
32+
private static Stream<Arguments> generateTestSchema() {
33+
String sep = System.lineSeparator();
34+
return Stream.of(
35+
of(Schema.of(), ""),
36+
of(Schema.of(field("key", () -> "value")), "key = \"value\""),
37+
of(Schema.of(field("number", () -> 123)), "number = 123"),
38+
of(Schema.of(field("number", () -> BigDecimal.valueOf(123.0))), "number = 123.0"),
39+
of(Schema.of(field("number", () -> BigDecimal.valueOf(123.123))), "number = 123.123"),
40+
of(Schema.of(field("boolean", () -> true)), "boolean = true"),
41+
of(Schema.of(field("nullValue", () -> null)), "nullValue = null"),
42+
of(Schema.of(field("array", () -> new String[]{null, "test", "123"})),
43+
"array = [ null, \"test\", \"123\" ]"),
44+
of(Schema.of(field("array", () -> new Integer[]{123, 456, 789})),
45+
"array = [ 123, 456, 789 ]"),
46+
of(Schema.of(field("array", () -> new Object[]{"test", 456, true})),
47+
"array = [ \"test\", 456, true ]"),
48+
of(Schema.of(field("emptyarray", () -> new Long[]{})), "emptyarray = []"),
49+
of(Schema.of(field("emptyarray", Collections::emptyList)), "emptyarray = []"),
50+
of(Schema.of(
51+
field("key", () -> "value"),
52+
field("nested", () -> Schema.of(field("nestedkey", () -> "nestedvalue")))
53+
), "key = \"value\"" + sep +
54+
"[nested]" + sep +
55+
"nestedkey = \"nestedvalue\""),
56+
of(Schema.of(
57+
field("key", () -> "value"),
58+
field("nested", () -> Schema.of(
59+
field("nestedkey", () -> "nestedvalue"),
60+
field("nested2", () -> Schema.of(
61+
field("nestedkey2", () -> "nestedvalue2")
62+
))
63+
))
64+
), "key = \"value\"" + sep +
65+
"[nested]" + sep +
66+
"nestedkey = \"nestedvalue\"" + sep +
67+
"[nested.nested2]" + sep +
68+
"nestedkey2 = \"nestedvalue2\""),
69+
of(Schema.of(
70+
field("parent", () -> Schema.of(
71+
field("numbers", () -> new Integer[]{1, 2})
72+
))
73+
), "[parent]" + sep +
74+
"numbers = [ 1, 2 ]"),
75+
of(Schema.of(
76+
field("items", () -> List.of(
77+
Schema.of(field("id", () -> 1)),
78+
Schema.of(field("id", () -> 2))
79+
))
80+
), "[[items]]" + sep +
81+
"id = 1" + sep +
82+
"[[items]]" + sep +
83+
"id = 2")
84+
);
85+
}
86+
87+
@ParameterizedTest
88+
@ValueSource(ints = {1, 4, 8})
89+
void generateFromFakeSequence(int limit) {
90+
final BaseFaker faker = new BaseFaker();
91+
Schema<Name, String> schema = Schema.of(field("firstName", Name::firstName));
92+
93+
TomlTransformer<Name> transformer = new TomlTransformer<>();
94+
95+
String toml = transformer.generate(
96+
faker.<Name>collection().suppliers(faker::name).maxLen(limit).build(),
97+
schema);
98+
99+
int numberOfLines = 0;
100+
for (int i = 0; i < toml.length(); i++) {
101+
if (toml.regionMatches(i, System.lineSeparator(), 0, System.lineSeparator().length())) {
102+
numberOfLines++;
103+
}
104+
}
105+
106+
assertThat(numberOfLines).isEqualTo((limit * (schema.getFields().length)) - 1);
107+
}
108+
109+
@ParameterizedTest
110+
@ValueSource(ints = {1, 4, 8})
111+
void generateFromFakeSequenceWithCollection(int limit) {
112+
final BaseFaker faker = new BaseFaker();
113+
Schema<Name, List<String>> schema = Schema.of(field("firstNames", name -> IntStream.rangeClosed(1, limit)
114+
.mapToObj(it -> name.firstName())
115+
.collect(Collectors.toList())));
116+
117+
TomlTransformer<Name> transformer = new TomlTransformer<>();
118+
119+
String toml = transformer.generate(
120+
faker.<Name>collection().suppliers(faker::name).maxLen(1).build(),
121+
schema);
122+
123+
assertThat(toml).matches("firstNames = \\[ \"(.+\",?){" + limit + "} ]");
124+
}
125+
126+
}

0 commit comments

Comments
 (0)