mirror of
https://github.com/digital-asset/daml.git
synced 2024-09-20 01:07:18 +03:00
Support record up/downgrades in Java codegen (#17486)
* Support record up/downgrades in Java codegen This adds enough upgrading support to the Java codegen to use it against the current upgrading PoCs. This is backwards compatible so I enabled it in all cases instead of trying to add a flag somewhere.
This commit is contained in:
parent
839ebddd20
commit
ba60571a52
@ -79,6 +79,7 @@ da_java_library(
|
|||||||
"@maven//:io_grpc_grpc_stub",
|
"@maven//:io_grpc_grpc_stub",
|
||||||
"@maven//:javax_annotation_javax_annotation_api",
|
"@maven//:javax_annotation_javax_annotation_api",
|
||||||
"@maven//:org_checkerframework_checker_qual",
|
"@maven//:org_checkerframework_checker_qual",
|
||||||
|
"@maven//:org_slf4j_slf4j_api",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -6,9 +6,12 @@ package com.daml.ledger.javaapi.data.codegen;
|
|||||||
import com.daml.ledger.javaapi.data.*;
|
import com.daml.ledger.javaapi.data.*;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link ValueDecoder}s for Daml types that are not code-generated.
|
* {@link ValueDecoder}s for Daml types that are not code-generated.
|
||||||
@ -16,6 +19,9 @@ import java.util.Optional;
|
|||||||
* @see ValueDecoder
|
* @see ValueDecoder
|
||||||
*/
|
*/
|
||||||
public final class PrimitiveValueDecoders {
|
public final class PrimitiveValueDecoders {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(PrimitiveValueDecoders.class);
|
||||||
|
|
||||||
// constructing not allowed
|
// constructing not allowed
|
||||||
private PrimitiveValueDecoders() {}
|
private PrimitiveValueDecoders() {}
|
||||||
|
|
||||||
@ -116,7 +122,7 @@ public final class PrimitiveValueDecoders {
|
|||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
public static List<com.daml.ledger.javaapi.data.DamlRecord.Field> recordCheck(
|
public static List<com.daml.ledger.javaapi.data.DamlRecord.Field> recordCheck(
|
||||||
int expectedFields, Value maybeRecord) {
|
int expectedFields, int trailingOptionalFields, Value maybeRecord) {
|
||||||
var record =
|
var record =
|
||||||
maybeRecord
|
maybeRecord
|
||||||
.asRecord()
|
.asRecord()
|
||||||
@ -124,9 +130,65 @@ public final class PrimitiveValueDecoders {
|
|||||||
() -> new IllegalArgumentException("Contracts must be constructed from Records"));
|
() -> new IllegalArgumentException("Contracts must be constructed from Records"));
|
||||||
var fields = record.getFields();
|
var fields = record.getFields();
|
||||||
var numberOfFields = fields.size();
|
var numberOfFields = fields.size();
|
||||||
if (numberOfFields != expectedFields)
|
if (numberOfFields == expectedFields) {
|
||||||
throw new IllegalArgumentException(
|
return fields;
|
||||||
"Expected " + expectedFields + " arguments, got " + numberOfFields);
|
}
|
||||||
|
if (numberOfFields > expectedFields) {
|
||||||
|
// Downgrade, check that the additional fields are empty optionals.
|
||||||
|
for (var i = expectedFields; i < numberOfFields; i++) {
|
||||||
|
final var field = fields.get(i);
|
||||||
|
final var optValue = field.getValue().asOptional();
|
||||||
|
if (optValue.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Expected "
|
||||||
|
+ expectedFields
|
||||||
|
+ " arguments, got "
|
||||||
|
+ numberOfFields
|
||||||
|
+ " and field "
|
||||||
|
+ i
|
||||||
|
+ " is not optional: "
|
||||||
|
+ field);
|
||||||
|
}
|
||||||
|
final var value = optValue.get();
|
||||||
|
if (!value.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Expected "
|
||||||
|
+ expectedFields
|
||||||
|
+ " arguments, got "
|
||||||
|
+ numberOfFields
|
||||||
|
+ " and field "
|
||||||
|
+ i
|
||||||
|
+ " is Optional but not empty: "
|
||||||
|
+ field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.trace(
|
||||||
|
"Downgrading record, dropping {} trailing optional fields",
|
||||||
|
numberOfFields - expectedFields);
|
||||||
|
return fields.subList(0, expectedFields);
|
||||||
|
}
|
||||||
|
if (numberOfFields < expectedFields) {
|
||||||
|
// Upgrade, add empty optionals to the end.
|
||||||
|
if ((expectedFields - numberOfFields) <= trailingOptionalFields) {
|
||||||
|
final var newFields = new ArrayList<>(fields);
|
||||||
|
for (var i = 0; i < expectedFields - numberOfFields; i++) {
|
||||||
|
newFields.add(new com.daml.ledger.javaapi.data.DamlRecord.Field(DamlOptional.EMPTY));
|
||||||
|
}
|
||||||
|
logger.trace(
|
||||||
|
"Upgrading record, appending {} empty optional fields",
|
||||||
|
expectedFields - numberOfFields);
|
||||||
|
return newFields;
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Expected "
|
||||||
|
+ expectedFields
|
||||||
|
+ " arguments, got "
|
||||||
|
+ numberOfFields
|
||||||
|
+ " and only the last "
|
||||||
|
+ trailingOptionalFields
|
||||||
|
+ " of the expected type are optionals");
|
||||||
|
}
|
||||||
|
}
|
||||||
return fields;
|
return fields;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,3 +18,4 @@ import Tests.TextMapTest()
|
|||||||
import Tests.GenMapTest()
|
import Tests.GenMapTest()
|
||||||
import Tests.ContractKeys()
|
import Tests.ContractKeys()
|
||||||
import Tests.ParametersOrder()
|
import Tests.ParametersOrder()
|
||||||
|
import Tests.UpgradeTest()
|
||||||
|
@ -18,3 +18,4 @@ import Tests.TextMapTest()
|
|||||||
import Tests.GenMapTest()
|
import Tests.GenMapTest()
|
||||||
import Tests.ContractKeys()
|
import Tests.ContractKeys()
|
||||||
import Tests.ParametersOrder()
|
import Tests.ParametersOrder()
|
||||||
|
import Tests.UpgradeTest()
|
||||||
|
@ -16,3 +16,4 @@ import Tests.Escape()
|
|||||||
import Tests.TextMapTest()
|
import Tests.TextMapTest()
|
||||||
import Tests.ContractKeys()
|
import Tests.ContractKeys()
|
||||||
import Tests.ParametersOrder()
|
import Tests.ParametersOrder()
|
||||||
|
import Tests.UpgradeTest()
|
||||||
|
@ -17,3 +17,4 @@ import Tests.Escape()
|
|||||||
import Tests.TextMapTest()
|
import Tests.TextMapTest()
|
||||||
import Tests.ContractKeys()
|
import Tests.ContractKeys()
|
||||||
import Tests.ParametersOrder()
|
import Tests.ParametersOrder()
|
||||||
|
import Tests.UpgradeTest()
|
||||||
|
@ -17,3 +17,4 @@ import Tests.Escape()
|
|||||||
import Tests.TextMapTest()
|
import Tests.TextMapTest()
|
||||||
import Tests.ContractKeys()
|
import Tests.ContractKeys()
|
||||||
import Tests.ParametersOrder()
|
import Tests.ParametersOrder()
|
||||||
|
import Tests.UpgradeTest()
|
||||||
|
@ -18,3 +18,4 @@ import Tests.TextMapTest()
|
|||||||
import Tests.GenMapTest()
|
import Tests.GenMapTest()
|
||||||
import Tests.ContractKeys()
|
import Tests.ContractKeys()
|
||||||
import Tests.ParametersOrder()
|
import Tests.ParametersOrder()
|
||||||
|
import Tests.UpgradeTest()
|
||||||
|
@ -0,0 +1,29 @@
|
|||||||
|
-- Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
||||||
|
-- SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
|
||||||
|
module Tests.UpgradeTest where
|
||||||
|
|
||||||
|
data NoOptional = NoOptional
|
||||||
|
with
|
||||||
|
a : Text
|
||||||
|
b : Text
|
||||||
|
deriving (Show, Eq)
|
||||||
|
|
||||||
|
data OptionalAtEnd = OptionalAtEnd
|
||||||
|
with
|
||||||
|
a : Text
|
||||||
|
b : Text
|
||||||
|
c : Optional Text
|
||||||
|
d : Optional Text
|
||||||
|
deriving (Show, Eq)
|
||||||
|
|
||||||
|
-- Not part of the test but the codegen filters out
|
||||||
|
-- data definitions which are not used in a template
|
||||||
|
template UpgradeTestTemplate
|
||||||
|
with
|
||||||
|
p : Party
|
||||||
|
noOptional : NoOptional
|
||||||
|
optionalAtEnd : OptionalAtEnd
|
||||||
|
where
|
||||||
|
signatory p
|
@ -21,5 +21,6 @@ import org.junit.runners.Suite;
|
|||||||
TemplateMethodTest.class,
|
TemplateMethodTest.class,
|
||||||
TextMapTest.class,
|
TextMapTest.class,
|
||||||
VariantTest.class,
|
VariantTest.class,
|
||||||
|
UpgradeTest.class,
|
||||||
})
|
})
|
||||||
public class AllGenericTests {}
|
public class AllGenericTests {}
|
||||||
|
@ -0,0 +1,92 @@
|
|||||||
|
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package com.daml;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
import com.daml.ledger.javaapi.data.*;
|
||||||
|
import java.util.Optional;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.platform.runner.JUnitPlatform;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import tests.upgradetest.*;
|
||||||
|
|
||||||
|
@RunWith(JUnitPlatform.class)
|
||||||
|
public class UpgradeTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void exactMatch() {
|
||||||
|
DamlRecord record =
|
||||||
|
new DamlRecord(
|
||||||
|
new DamlRecord.Field(new Text("abc")), new DamlRecord.Field(new Text("def")));
|
||||||
|
NoOptional actual = NoOptional.fromValue(record);
|
||||||
|
|
||||||
|
NoOptional expected = new NoOptional("abc", "def");
|
||||||
|
|
||||||
|
assertEquals(actual, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void downgradeEmptyOptional() {
|
||||||
|
DamlRecord record =
|
||||||
|
new DamlRecord(
|
||||||
|
new DamlRecord.Field(new Text("abc")),
|
||||||
|
new DamlRecord.Field(new Text("def")),
|
||||||
|
new DamlRecord.Field(DamlOptional.EMPTY),
|
||||||
|
new DamlRecord.Field(DamlOptional.EMPTY));
|
||||||
|
NoOptional actual = NoOptional.fromValue(record);
|
||||||
|
|
||||||
|
NoOptional expected = new NoOptional("abc", "def");
|
||||||
|
|
||||||
|
assertEquals(actual, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void downgradeNonEmptyOptionalFails() {
|
||||||
|
DamlRecord record =
|
||||||
|
new DamlRecord(
|
||||||
|
new DamlRecord.Field(new Text("abc")),
|
||||||
|
new DamlRecord.Field(new Text("def")),
|
||||||
|
new DamlRecord.Field(DamlOptional.of(Unit.getInstance())));
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> NoOptional.fromValue(record));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void downgradeNonOptionalFails() {
|
||||||
|
DamlRecord record =
|
||||||
|
new DamlRecord(
|
||||||
|
new DamlRecord.Field(new Text("abc")),
|
||||||
|
new DamlRecord.Field(new Text("def")),
|
||||||
|
new DamlRecord.Field(Unit.getInstance()));
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> NoOptional.fromValue(record));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void upgradeOptionalFieldsTwoMissingOptionals() {
|
||||||
|
DamlRecord record =
|
||||||
|
new DamlRecord(
|
||||||
|
new DamlRecord.Field(new Text("abc")), new DamlRecord.Field(new Text("def")));
|
||||||
|
OptionalAtEnd actual = OptionalAtEnd.fromValue(record);
|
||||||
|
OptionalAtEnd expected = new OptionalAtEnd("abc", "def", Optional.empty(), Optional.empty());
|
||||||
|
assertEquals(actual, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void upgradeOptionalFieldsOneMissingOptional() {
|
||||||
|
DamlRecord record =
|
||||||
|
new DamlRecord(
|
||||||
|
new DamlRecord.Field(new Text("abc")),
|
||||||
|
new DamlRecord.Field(new Text("def")),
|
||||||
|
new DamlRecord.Field(DamlOptional.of(new Text("ghi"))));
|
||||||
|
OptionalAtEnd actual = OptionalAtEnd.fromValue(record);
|
||||||
|
OptionalAtEnd expected = new OptionalAtEnd("abc", "def", Optional.of("ghi"), Optional.empty());
|
||||||
|
assertEquals(actual, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void upgradeNonOptionalFields() {
|
||||||
|
DamlRecord record = new DamlRecord(new DamlRecord.Field(new Text("abc")));
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> NoOptional.fromValue(record));
|
||||||
|
}
|
||||||
|
}
|
@ -87,15 +87,24 @@ private[inner] object FromValueGenerator extends StrictLogging {
|
|||||||
.generate(typeParameters)
|
.generate(typeParameters)
|
||||||
.valueDecoderParameterSpecs
|
.valueDecoderParameterSpecs
|
||||||
|
|
||||||
|
def isOptional(t: Type) =
|
||||||
|
t match {
|
||||||
|
case TypePrim(PrimTypeOptional, _) => true
|
||||||
|
case _ => false
|
||||||
|
}
|
||||||
|
|
||||||
|
val optionalFieldsSize = fields.reverse.takeWhile(f => isOptional(f.damlType)).size
|
||||||
|
|
||||||
val fromValueCode = CodeBlock
|
val fromValueCode = CodeBlock
|
||||||
.builder()
|
.builder()
|
||||||
.add(recordValueExtractor("value$", "recordValue$"))
|
.add(recordValueExtractor("value$", "recordValue$"))
|
||||||
.addStatement(
|
.addStatement(
|
||||||
"$T fields$$ = $T.recordCheck($L,$WrecordValue$$)",
|
"$T fields$$ = $T.recordCheck($L,$L,$WrecordValue$$)",
|
||||||
ParameterizedTypeName
|
ParameterizedTypeName
|
||||||
.get(classOf[java.util.List[_]], classOf[javaapi.data.DamlRecord.Field]),
|
.get(classOf[java.util.List[_]], classOf[javaapi.data.DamlRecord.Field]),
|
||||||
classOf[PrimitiveValueDecoders],
|
classOf[PrimitiveValueDecoders],
|
||||||
fields.size,
|
fields.size,
|
||||||
|
optionalFieldsSize,
|
||||||
)
|
)
|
||||||
|
|
||||||
fields.iterator.zip(accessors).foreach { case (FieldInfo(_, damlType, javaName, _), accessor) =>
|
fields.iterator.zip(accessors).foreach { case (FieldInfo(_, damlType, javaName, _), accessor) =>
|
||||||
|
Loading…
Reference in New Issue
Block a user