Skip to content

Commit c013df1

Browse files
committed
Add utility to fill shapes with random values
1 parent b1fad45 commit c013df1

File tree

10 files changed

+295
-247
lines changed

10 files changed

+295
-247
lines changed

aws/aws-event-streams/src/main/java/software/amazon/smithy/java/aws/events/AwsEventShapeEncoder.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@
1818
import software.amazon.eventstream.Message;
1919
import software.amazon.smithy.java.core.error.ModeledException;
2020
import software.amazon.smithy.java.core.schema.Schema;
21-
import software.amazon.smithy.java.core.schema.SchemaUtils;
2221
import software.amazon.smithy.java.core.schema.SerializableStruct;
22+
import software.amazon.smithy.java.core.schema.ShapeUtils;
2323
import software.amazon.smithy.java.core.schema.TraitKey;
2424
import software.amazon.smithy.java.core.serde.Codec;
2525
import software.amazon.smithy.java.core.serde.ShapeSerializer;
@@ -75,7 +75,7 @@ private byte[] encodeInput(
7575
typeHolder.set(initialEventType.value());
7676
var os = new ByteArrayOutputStream();
7777
try (var baseSerializer = possibleTypes.get(initialEventType.value()).apply(os, headers)) {
78-
SchemaUtils.withFilteredMembers(item.schema(), item, AwsEventShapeEncoder::excludeEventStreamMember)
78+
ShapeUtils.withFilteredMembers(item.schema(), item, AwsEventShapeEncoder::excludeEventStreamMember)
7979
.serialize(baseSerializer);
8080
}
8181
return os.toByteArray();
@@ -202,14 +202,14 @@ public EventSerializer(EventHeaderSerializer headerSerializer, ShapeSerializer b
202202
@Override
203203
public void writeStruct(Schema schema, SerializableStruct struct) {
204204
if (hasEventPayloadMember(schema)) {
205-
SchemaUtils.withFilteredMembers(schema, struct, this::isEventPayload)
205+
ShapeUtils.withFilteredMembers(schema, struct, this::isEventPayload)
206206
.serializeMembers(baseSerializer);
207207

208208
} else {
209-
SchemaUtils.withFilteredMembers(schema, struct, this::isPayloadMember)
209+
ShapeUtils.withFilteredMembers(schema, struct, this::isPayloadMember)
210210
.serialize(baseSerializer);
211211
}
212-
SchemaUtils.withFilteredMembers(schema, struct, this::isHeadersMember)
212+
ShapeUtils.withFilteredMembers(schema, struct, this::isHeadersMember)
213213
.serialize(headerSerializer);
214214
}
215215

client/client-core/src/main/java/software/amazon/smithy/java/client/core/pagination/PaginationInputSetter.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77

88
import software.amazon.smithy.java.core.schema.ApiOperation;
99
import software.amazon.smithy.java.core.schema.Schema;
10-
import software.amazon.smithy.java.core.schema.SchemaUtils;
1110
import software.amazon.smithy.java.core.schema.SerializableStruct;
11+
import software.amazon.smithy.java.core.schema.ShapeUtils;
1212

1313
/**
1414
* Replaces values of a top-level structure members with values for pagination.
@@ -35,7 +35,7 @@ final class PaginationInputSetter<I extends SerializableStruct> {
3535

3636
I create(String token, Integer maxResults) {
3737
var builder = operation.inputBuilder();
38-
SchemaUtils.copyShape(input, builder);
38+
ShapeUtils.copyShape(input, builder);
3939
if (token != null) {
4040
builder.setMemberValue(inputTokenSchema, token);
4141
}

client/client-core/src/main/java/software/amazon/smithy/java/client/core/plugins/InjectIdempotencyTokenPlugin.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111
import software.amazon.smithy.java.client.core.interceptors.ClientInterceptor;
1212
import software.amazon.smithy.java.client.core.interceptors.InputHook;
1313
import software.amazon.smithy.java.context.Context;
14-
import software.amazon.smithy.java.core.schema.SchemaUtils;
1514
import software.amazon.smithy.java.core.schema.SerializableStruct;
15+
import software.amazon.smithy.java.core.schema.ShapeUtils;
1616
import software.amazon.smithy.java.logging.InternalLogger;
1717
import software.amazon.smithy.utils.SmithyInternalApi;
1818

@@ -53,7 +53,7 @@ public <I extends SerializableStruct> I modifyBeforeSerialization(InputHook<I, ?
5353

5454
if (value == null) {
5555
var builder = operation.inputBuilder();
56-
SchemaUtils.copyShape(hook.input(), builder);
56+
ShapeUtils.copyShape(hook.input(), builder);
5757
var tokenProvider = hook.context().get(IDEMPOTENCY_TOKEN_PROVIDER);
5858
var tokenValue = tokenProvider != null ? tokenProvider : UUID.randomUUID().toString();
5959
builder.setMemberValue(tokenMember, tokenValue);

core/src/main/java/software/amazon/smithy/java/core/schema/SchemaUtils.java

Lines changed: 0 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -5,50 +5,10 @@
55

66
package software.amazon.smithy.java.core.schema;
77

8-
import java.util.function.Predicate;
9-
import software.amazon.smithy.java.core.serde.InterceptingSerializer;
10-
import software.amazon.smithy.java.core.serde.ShapeSerializer;
11-
128
public final class SchemaUtils {
139

1410
private SchemaUtils() {}
1511

16-
/**
17-
* Create a serializable struct that only serializes members that pass the given predicate.
18-
*
19-
* @param schema Structure schema.
20-
* @param struct Struct to serialize.
21-
* @param memberPredicate Predicate that takes a member schema.
22-
* @return the filtered struct.
23-
*/
24-
public static SerializableStruct withFilteredMembers(
25-
Schema schema,
26-
SerializableStruct struct,
27-
Predicate<Schema> memberPredicate
28-
) {
29-
return new SerializableStruct() {
30-
@Override
31-
public Schema schema() {
32-
return schema;
33-
}
34-
35-
@Override
36-
public void serializeMembers(ShapeSerializer serializer) {
37-
struct.serializeMembers(new InterceptingSerializer() {
38-
@Override
39-
protected ShapeSerializer before(Schema schema) {
40-
return memberPredicate.test(schema) ? serializer : ShapeSerializer.nullSerializer();
41-
}
42-
});
43-
}
44-
45-
@Override
46-
public <T> T getMemberValue(Schema member) {
47-
return memberPredicate.test(schema()) ? struct.getMemberValue(member) : null;
48-
}
49-
};
50-
}
51-
5212
/**
5313
* Ensures that {@code member} is contained in {@code parent}, and if so returns {@code value}.
5414
*
@@ -91,19 +51,4 @@ public static <T> T validateSameMember(Schema expected, Schema actual, T value)
9151
+ ": " + actual.id());
9252
}
9353

94-
/**
95-
* Attempts to copy the values from a struct into a shape builder.
96-
*
97-
* @param source The shape to copy from.
98-
* @param sink The builder to copy into.
99-
* @throws IllegalArgumentException if the two shapes are incompatible and don't use the same schemas.
100-
*/
101-
public static void copyShape(SerializableStruct source, ShapeBuilder<?> sink) {
102-
for (var member : source.schema().members()) {
103-
var value = source.getMemberValue(member);
104-
if (value != null) {
105-
sink.setMemberValue(member, value);
106-
}
107-
}
108-
}
10954
}
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package software.amazon.smithy.java.core.schema;
7+
8+
import java.math.BigDecimal;
9+
import java.math.BigInteger;
10+
import java.math.RoundingMode;
11+
import java.nio.ByteBuffer;
12+
import java.time.Instant;
13+
import java.util.HexFormat;
14+
import java.util.concurrent.ThreadLocalRandom;
15+
import java.util.function.Predicate;
16+
import java.util.stream.IntStream;
17+
import software.amazon.smithy.java.core.serde.InterceptingSerializer;
18+
import software.amazon.smithy.java.core.serde.ShapeDeserializer;
19+
import software.amazon.smithy.java.core.serde.ShapeSerializer;
20+
import software.amazon.smithy.java.core.serde.document.Document;
21+
import software.amazon.smithy.java.io.ByteBufferUtils;
22+
import software.amazon.smithy.model.shapes.ShapeType;
23+
24+
public class ShapeUtils {
25+
26+
private ShapeUtils() {
27+
28+
}
29+
30+
/**
31+
* Create a serializable struct that only serializes members that pass the given predicate.
32+
*
33+
* @param schema Structure schema.
34+
* @param struct Struct to serialize.
35+
* @param memberPredicate Predicate that takes a member schema.
36+
* @return the filtered struct.
37+
*/
38+
public static SerializableStruct withFilteredMembers(
39+
Schema schema,
40+
SerializableStruct struct,
41+
Predicate<Schema> memberPredicate
42+
) {
43+
return new SerializableStruct() {
44+
@Override
45+
public Schema schema() {
46+
return schema;
47+
}
48+
49+
@Override
50+
public void serializeMembers(ShapeSerializer serializer) {
51+
struct.serializeMembers(new InterceptingSerializer() {
52+
@Override
53+
protected ShapeSerializer before(Schema schema) {
54+
return memberPredicate.test(schema) ? serializer : ShapeSerializer.nullSerializer();
55+
}
56+
});
57+
}
58+
59+
@Override
60+
public <T> T getMemberValue(Schema member) {
61+
return memberPredicate.test(schema()) ? struct.getMemberValue(member) : null;
62+
}
63+
};
64+
}
65+
66+
/**
67+
* Attempts to copy the values from a struct into a shape builder.
68+
*
69+
* @param source The shape to copy from.
70+
* @param sink The builder to copy into.
71+
* @throws IllegalArgumentException if the two shapes are incompatible and don't use the same schemas.
72+
*/
73+
public static void copyShape(SerializableStruct source, ShapeBuilder<?> sink) {
74+
for (var member : source.schema().members()) {
75+
var value = source.getMemberValue(member);
76+
if (value != null) {
77+
sink.setMemberValue(member, value);
78+
}
79+
}
80+
}
81+
82+
/**
83+
* Generates a random instance of a shape using the provided builder.
84+
*
85+
* <p>This method populates the shape with randomly generated values for all its members.
86+
* Required members are always populated, while optional members have a 50% chance of being set.
87+
* For union types, exactly one member is randomly selected and populated.
88+
*
89+
* <p>This is useful for fuzz testing and generating test data.
90+
*
91+
* @param shapeBuilder The builder to use for constructing the shape.
92+
* @param <T> The type of shape to generate.
93+
* @return A randomly generated instance of the shape.
94+
*/
95+
public static <T extends SerializableShape> T generateRandom(ShapeBuilder<T> shapeBuilder) {
96+
return shapeBuilder.deserialize(new RandomShapeGenerator()).build();
97+
}
98+
99+
private static final class RandomShapeGenerator implements ShapeDeserializer {
100+
@Override
101+
public boolean readBoolean(Schema schema) {
102+
return random().nextBoolean();
103+
}
104+
105+
@Override
106+
public ByteBuffer readBlob(Schema schema) {
107+
var bytes = new byte[randomInt(100)];
108+
random().nextBytes(bytes);
109+
return ByteBuffer.wrap(bytes);
110+
}
111+
112+
@Override
113+
public byte readByte(Schema schema) {
114+
return (byte) random().nextInt(Byte.MIN_VALUE, Byte.MAX_VALUE + 1);
115+
}
116+
117+
@Override
118+
public short readShort(Schema schema) {
119+
return (short) random().nextInt(Short.MIN_VALUE, Short.MAX_VALUE + 1);
120+
}
121+
122+
@Override
123+
public int readInteger(Schema schema) {
124+
return random().nextInt();
125+
}
126+
127+
@Override
128+
public long readLong(Schema schema) {
129+
return random().nextLong();
130+
}
131+
132+
@Override
133+
public float readFloat(Schema schema) {
134+
return random().nextFloat();
135+
}
136+
137+
@Override
138+
public double readDouble(Schema schema) {
139+
return random().nextDouble();
140+
}
141+
142+
@Override
143+
public BigInteger readBigInteger(Schema schema) {
144+
int times = randomInt(3);
145+
var bigInt = BigInteger.valueOf(random().nextLong());
146+
for (int i = 0; i < times; i++) {
147+
bigInt = bigInt.multiply(BigInteger.valueOf(random().nextLong(Long.MAX_VALUE - 100, Long.MAX_VALUE)));
148+
}
149+
return bigInt;
150+
}
151+
152+
@Override
153+
public BigDecimal readBigDecimal(Schema schema) {
154+
var bigDecimal = new BigDecimal(readBigInteger(schema));
155+
int times = randomInt();
156+
while (times-- > 0) {
157+
bigDecimal = bigDecimal.divide(new BigDecimal(readBigInteger(schema)), RoundingMode.CEILING);
158+
}
159+
return bigDecimal;
160+
}
161+
162+
@Override
163+
public String readString(Schema schema) {
164+
return HexFormat.of().formatHex(ByteBufferUtils.getBytes(readBlob(schema)));
165+
}
166+
167+
@Override
168+
public Document readDocument() {
169+
//TODO add more shapes.
170+
return Document.of(readString(null));
171+
}
172+
173+
@Override
174+
public Instant readTimestamp(Schema schema) {
175+
return Instant.now();
176+
}
177+
178+
@Override
179+
public <T> void readStruct(Schema schema, T state, StructMemberConsumer<T> consumer) {
180+
if (schema.type().isShapeType(ShapeType.UNION)) {
181+
var member = schema.members().get(randomInt(schema.members().size()));
182+
consumer.accept(state, member, this);
183+
} else {
184+
for (var member : schema.members()) {
185+
if (member.hasTrait(TraitKey.REQUIRED_TRAIT) || random().nextBoolean()) {
186+
consumer.accept(state, member, this);
187+
}
188+
}
189+
}
190+
}
191+
192+
@Override
193+
public <T> void readList(Schema schema, T state, ListMemberConsumer<T> consumer) {
194+
IntStream.range(0, randomInt()).forEach(i -> consumer.accept(state, this));
195+
}
196+
197+
@Override
198+
public <T> void readStringMap(Schema schema, T state, MapMemberConsumer<String, T> consumer) {
199+
IntStream.range(0, randomInt()).forEach(i -> consumer.accept(state, readString(schema), this));
200+
}
201+
202+
@Override
203+
public boolean isNull() {
204+
return random().nextBoolean();
205+
}
206+
207+
private static ThreadLocalRandom random() {
208+
return ThreadLocalRandom.current();
209+
}
210+
211+
private static int randomInt() {
212+
return random().nextInt(10);
213+
}
214+
215+
private static int randomInt(int bound) {
216+
return random().nextInt(bound);
217+
}
218+
}
219+
}

0 commit comments

Comments
 (0)