diff --git a/contrib/src/main/java/gov/nasa/jpl/aerie/contrib/serialization/mappers/RecordValueMapper.java b/contrib/src/main/java/gov/nasa/jpl/aerie/contrib/serialization/mappers/RecordValueMapper.java index 0ac95bd950..c529300f2d 100644 --- a/contrib/src/main/java/gov/nasa/jpl/aerie/contrib/serialization/mappers/RecordValueMapper.java +++ b/contrib/src/main/java/gov/nasa/jpl/aerie/contrib/serialization/mappers/RecordValueMapper.java @@ -8,6 +8,7 @@ import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.RecordComponent; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; @@ -36,11 +37,15 @@ public record Component( @Override public ValueSchema getValueSchema() { final var valueSchemas = new HashMap(); + final var metadata = new ArrayList(); for (final var component : this.components) { + metadata.add(SerializedValue.of(component.name)); valueSchemas.put(component.name, component.mapper.getValueSchema()); } - return ValueSchema.ofStruct(valueSchemas); + return components.isEmpty() ? + ValueSchema.ofStruct(valueSchemas) : + ValueSchema.withMeta("item_order", SerializedValue.of(metadata), ValueSchema.ofStruct(valueSchemas)); } @Override diff --git a/contrib/src/test/java/gov/nasa/jpl/aerie/contrib/serialization/mappers/RecordValueMapperTest.java b/contrib/src/test/java/gov/nasa/jpl/aerie/contrib/serialization/mappers/RecordValueMapperTest.java index e29af6ee29..581fa9fb39 100644 --- a/contrib/src/test/java/gov/nasa/jpl/aerie/contrib/serialization/mappers/RecordValueMapperTest.java +++ b/contrib/src/test/java/gov/nasa/jpl/aerie/contrib/serialization/mappers/RecordValueMapperTest.java @@ -16,6 +16,7 @@ record EmptyRecord() {} record StringRecord(String aString) {} record MultiValueRecord(String aString, Map> fancy) {} + record NestedMultiValueRecord(String aString, Map> fancy, MultiValueRecord record) {} @Test void getValueSchema_emptyRecord() { @@ -32,12 +33,16 @@ void getValueSchema_stringRecord() { StringRecord::aString, new StringValueMapper() ))).getValueSchema(); - assertEquals(ValueSchema.ofStruct(Map.of("aString", ValueSchema.STRING)), valueSchema); + assertEquals(ValueSchema.withMeta( + "item_order", + SerializedValue.of(List.of(SerializedValue.of("aString"))), + ValueSchema.ofStruct(Map.of("aString", ValueSchema.STRING))), + valueSchema); + } - @Test - void getValueSchema_multiValueRecord() { - final var valueSchema = new RecordValueMapper<>( + final RecordValueMapper getMultiValueRecordValueMapper() { + return new RecordValueMapper<>( MultiValueRecord.class, List.of( new RecordValueMapper.Component<>( @@ -49,15 +54,65 @@ void getValueSchema_multiValueRecord() { "fancy", MultiValueRecord::fancy, new MapValueMapper<>(new StringValueMapper(), new ListValueMapper<>(new BooleanValueMapper())) + ))); + } + + @Test + void getValueSchema_multiValueRecord() { + final var valueSchema = getMultiValueRecordValueMapper().getValueSchema(); + assertEquals( + ValueSchema.withMeta( + "item_order", + SerializedValue.of(List.of(SerializedValue.of("aString"), SerializedValue.of("fancy"))), + ValueSchema.ofStruct(Map.of( + "aString", ValueSchema.STRING, + "fancy", ValueSchema.ofSeries(ValueSchema.ofStruct(Map.of( + "key", ValueSchema.STRING, + "value", ValueSchema.ofSeries(ValueSchema.BOOLEAN))))))), + valueSchema); + } + + @Test + void getValueSchema_nested() { + final var valueSchema = new RecordValueMapper<>( + NestedMultiValueRecord.class, + List.of( + new RecordValueMapper.Component<>( + "aString", + NestedMultiValueRecord::aString, + new StringValueMapper() + ), + new RecordValueMapper.Component<>( + "fancy", + NestedMultiValueRecord::fancy, + new MapValueMapper<>(new StringValueMapper(), new ListValueMapper<>(new BooleanValueMapper())) + ), + new RecordValueMapper.Component<>( + "record", + NestedMultiValueRecord::record, + getMultiValueRecordValueMapper() ))).getValueSchema(); - assertEquals(ValueSchema.ofStruct(Map.of( - "aString", ValueSchema.STRING, - "fancy", ValueSchema.ofSeries( + assertEquals( + ValueSchema.withMeta( + "item_order", + SerializedValue.of(List.of( + SerializedValue.of("aString"), + SerializedValue.of("fancy"), + SerializedValue.of("record"))), ValueSchema.ofStruct(Map.of( - "key", ValueSchema.STRING, - "value", ValueSchema.ofSeries(ValueSchema.BOOLEAN) - )) - ))), valueSchema); + "aString", ValueSchema.STRING, + "fancy", ValueSchema.ofSeries(ValueSchema.ofStruct(Map.of( + "key", ValueSchema.STRING, + "value", ValueSchema.ofSeries(ValueSchema.BOOLEAN)))), + "record", ValueSchema.withMeta( + "item_order", + SerializedValue.of(List.of(SerializedValue.of("aString"), SerializedValue.of("fancy"))), + ValueSchema.ofStruct(Map.of( + "aString", ValueSchema.STRING, + "fancy", ValueSchema.ofSeries(ValueSchema.ofStruct(Map.of( + "key", ValueSchema.STRING, + "value", ValueSchema.ofSeries(ValueSchema.BOOLEAN)))))))))), + valueSchema); } @Test diff --git a/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/MissionModelTests.java b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/MissionModelTests.java index e9e87c81e0..ab79ea92c3 100644 --- a/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/MissionModelTests.java +++ b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/MissionModelTests.java @@ -7,14 +7,17 @@ import gov.nasa.jpl.aerie.e2e.types.ValueSchema; import gov.nasa.jpl.aerie.e2e.utils.GatewayRequests; import gov.nasa.jpl.aerie.e2e.utils.HasuraRequests; +import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import javax.json.Json; +import javax.json.JsonObject; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Map; @@ -119,16 +122,21 @@ private ArrayList expectedActivityTypesBanananation() { activityTypes.add(new ActivityType("BananaNap", Map.of())); activityTypes.add(new ActivityType( "BiteBanana", - Map.of("biteSize", new Parameter(0, - new ValueSchemaMeta( - Map.of( - "unit", Json.createObjectBuilder(Map.of("value", "m")).build(), - "description", Json.createObjectBuilder(Map.of("value", "The size of the bite in meters")).build()), - VALUE_SCHEMA_REAL))), - new ValueSchemaStruct(Map.of("biteSizeWasBig", new ValueSchemaMeta( - Map.of( - "description", Json.createObjectBuilder(Map.of("value", "Big Bite")).build()), - VALUE_SCHEMA_BOOLEAN), "newFlag", new ValueSchemaVariant(List.of(new Variant("A", "A"), new Variant("B", "B"))))), + Map.of("biteSize", new Parameter( + 0, + new ValueSchemaMeta(Map.of( + "unit", + Json.createObjectBuilder(Map.of("value", "m")).build(), + "description", + Json.createObjectBuilder(Map.of("value", "The size of the bite in meters")).build() + ), VALUE_SCHEMA_REAL))), + new ValueSchemaMeta( + Map.of("item_order", Json.createArrayBuilder().add("biteSizeWasBig").add("newFlag").build()), + new ValueSchemaStruct(Map.of("biteSizeWasBig", new ValueSchemaMeta( + Map.of( + "description", Json.createObjectBuilder(Map.of("value", "Big Bite")).build()), + VALUE_SCHEMA_BOOLEAN), "newFlag", new ValueSchemaVariant(List.of(new Variant("A", "A"), new Variant("B", "B"))))) + ), "Eat", "Takes a bite out of the banana" )); @@ -147,17 +155,81 @@ private ArrayList expectedActivityTypesBanananation() { activityTypes.add(new ActivityType( "DurationParameterActivity", Map.of("duration", new Parameter(0, VALUE_SCHEMA_DURATION)), - new ValueSchemaStruct(Map.of( - "duration", VALUE_SCHEMA_DURATION, - "durationInSeconds", new ValueSchemaMeta(Map.of("unit", Json.createObjectBuilder(Map.of("value", "s")).build()), VALUE_SCHEMA_REAL))), - null - )); + new ValueSchemaMeta( + Map.of("item_order", Json.createArrayBuilder().add("duration").add("durationInSeconds").build()), + new ValueSchemaStruct(Map.of("duration", VALUE_SCHEMA_DURATION, + "durationInSeconds", new ValueSchemaMeta(Map.of("unit", Json.createObjectBuilder(Map.of("value", "s")).build()), VALUE_SCHEMA_REAL)))), + null)); activityTypes.add(new ActivityType("ExceptionActivity", Map.of("throwException", new Parameter(0, VALUE_SCHEMA_BOOLEAN)))); activityTypes.add(new ActivityType("grandchild", Map.of("counter", new Parameter(0, VALUE_SCHEMA_INT)))); activityTypes.add(new ActivityType("GrowBanana", Map.of( "quantity", new Parameter(0, VALUE_SCHEMA_INT), "growingDuration", new Parameter(1, VALUE_SCHEMA_DURATION)))); activityTypes.add(new ActivityType("LineCount", Map.of("path", new Parameter(0, VALUE_SCHEMA_PATH)))); + final var allSubs = new String[] {"primitiveDouble", + "primitiveFloat", + "primitiveByte", + "primitiveShort", + "primitiveInt", + "primitiveLong", + "primitiveChar", + "primitiveBoolean", + "boxedDouble", + "boxedFloat", + "boxedByte", + "boxedShort", + "boxedInt", + "boxedLong", + "boxedChar", + "boxedBoolean", + "string", + "doubleArray", + "floatArray", + "byteArray", + "shortArray", + "intArray", + "longArray", + "charArray", + "booleanArray", + "stringArray", + "primDoubleArray", + "primFloatArray", + "primByteArray", + "primShortArray", + "primIntArray", + "primLongArray", + "primCharArray", + "primBooleanArray", + "doubleList", + "floatList", + "byteList", + "shortList", + "intList", + "longList", + "charList", + "booleanList", + "stringList", + "doubleMap", + "floatMap", + "byteMap", + "shortMap", + "intMap", + "longMap", + "charMap", + "booleanMap", + "stringMap", + "testDuration", + "testEnum", + "mappyBoi", + "doublePrimIntArray", + "intListArrayArray", + "obnoxious", + "nested", + "genericParameter"}; + final var parameterTestItemOrder = Json.createArrayBuilder(); + for(final var param: allSubs){ + parameterTestItemOrder.add(param); + } activityTypes.add(new ActivityType( "ParameterTest", //region ParameterTest Parameters @@ -167,15 +239,17 @@ private ArrayList expectedActivityTypesBanananation() { entry("record", new Parameter( 58, - new ValueSchemaStruct(Map.ofEntries( + new ValueSchemaMeta(Map.of("item_order", parameterTestItemOrder.build()), new ValueSchemaStruct(Map.ofEntries( entry("intMap", new ValueSchemaSeries(new ValueSchemaStruct(Map.of( "key", VALUE_SCHEMA_INT, "value", VALUE_SCHEMA_INT)))), - entry("nested", new ValueSchemaStruct(Map.of( + entry("nested", new ValueSchemaMeta( + Map.of( "item_order", Json.createArrayBuilder().add("a").add("b").build()), + new ValueSchemaStruct(Map.of( "a", VALUE_SCHEMA_STRING, "b", new ValueSchemaSeries(new ValueSchemaStruct(Map.of( "key", VALUE_SCHEMA_INT, - "value", VALUE_SCHEMA_STRING)))))), + "value", VALUE_SCHEMA_STRING))))))), entry("string", VALUE_SCHEMA_STRING), entry("byteMap", new ValueSchemaSeries(new ValueSchemaStruct(Map.of( "key", VALUE_SCHEMA_INT, @@ -258,7 +332,7 @@ private ArrayList expectedActivityTypesBanananation() { entry("primBooleanArray", new ValueSchemaSeries(VALUE_SCHEMA_BOOLEAN)), entry("primitiveBoolean", VALUE_SCHEMA_BOOLEAN), entry("intListArrayArray", new ValueSchemaSeries(new ValueSchemaSeries(new ValueSchemaSeries(VALUE_SCHEMA_INT)))), - entry("doublePrimIntArray", new ValueSchemaSeries(new ValueSchemaSeries(VALUE_SCHEMA_INT))))))), + entry("doublePrimIntArray", new ValueSchemaSeries(new ValueSchemaSeries(VALUE_SCHEMA_INT)))))))), //endregion record entry("string", new Parameter(16, VALUE_SCHEMA_STRING)), entry("byteMap", new Parameter(45,new ValueSchemaSeries(new ValueSchemaStruct(Map.of( diff --git a/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/types/ActivityType.java b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/types/ActivityType.java index 864ef115cd..822582bf04 100644 --- a/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/types/ActivityType.java +++ b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/types/ActivityType.java @@ -24,10 +24,6 @@ public ActivityType(final String name, final Map parameters, this(name, parameters, computedAttributes, subsystem, null); } - public ActivityType(final String name, final Map parameters, final ValueSchema computedAttributes) { - this(name, parameters, new ValueSchema.ValueSchemaStruct(Map.of()), null, null); - } - public static ActivityType fromJSON(JsonObject json) { final var parameters = json.getJsonObject("parameters"); final var parameterMap = new HashMap();