navigator: add enum types (#1529)

* navigator: add enum types

* Address Stephen's comments

* do not serialize enum types

* add comment dropped in previous commit
This commit is contained in:
Remy 2019-06-06 18:24:42 +02:00 committed by mergify[bot]
parent 504337b5b2
commit c76274585a
21 changed files with 324 additions and 98 deletions

View File

@ -135,13 +135,14 @@ private[engine] class CommandPreprocessor(compiledPackages: ConcurrentCompiledPa
case (key0, value0) => go(newNesting, elemType, value0).map(key0 -> _)
}
.map(l => SMap(HashMap(l.toSeq: _*)))
// variants
case (TTyConApp(tyCon, tyConArgs), ValueVariant(mbVariantId, constructorName, val0)) =>
val variantId = tyCon
mbVariantId match {
case Some(variantId_) if variantId != variantId_ =>
fail(
s"Mismatching variant id, the types tell us $variantId, but the value tells us $variantId_")
s"Mismatching variant id, the type tells us $variantId, but the value tells us $variantId_")
case _ =>
compiledPackages.getPackage(variantId.packageId) match {
// if the package is not there, look it up and restart. stack safe since this will be done
@ -179,7 +180,7 @@ private[engine] class CommandPreprocessor(compiledPackages: ConcurrentCompiledPa
mbRecordId match {
case Some(recordId_) if recordId != recordId_ =>
fail(
s"Mismatching record id, the types tell us $recordId, but the value tells us $recordId_")
s"Mismatching record id, the type tells us $recordId, but the value tells us $recordId_")
case _ =>
compiledPackages.getPackage(recordId.packageId) match {
// if the package is not there, look it up and restart. stack safe since this will be done
@ -243,6 +244,33 @@ private[engine] class CommandPreprocessor(compiledPackages: ConcurrentCompiledPa
}
}
case (TTyCon(id), ValueEnum(mbId, constructor)) =>
mbId match {
case Some(id_) if id_ != id =>
fail(s"Mismatching enum id, the type tells us $id, but the value tells us $id_")
case _ =>
compiledPackages.getPackage(id.packageId) match {
// if the package is not there, look it up and restart. stack safe since this will be done
// very few times as the cache gets warm. this is also why we do not use the `Result.needDataType`, which
// would consume stack regardless
case None =>
Result.needPackage(
id.packageId,
compiledPackages.addPackage(id.packageId, _).flatMap(_ => restart)
)
case Some(pkg) =>
PackageLookup.lookupEnum(pkg, id.qualifiedName) match {
case Left(err) => ResultError(err)
case Right(DataEnum(constructors)) =>
if (!constructors.toSeq.contains(constructor))
fail(
s"Couldn't find provided variant constructor $constructor in enum $id")
ResultDone(SEnum(id, constructor))
}
}
}
// every other pairs of types and values are invalid
case (otherType, otherValue) =>
fail(s"mismatching type: $otherType and value: $otherValue")

View File

@ -66,8 +66,8 @@ object DataType {
Traverse[Record].traverse(r)(f).widen
case v @ Variant(_) =>
Traverse[Variant].traverse(v)(g).widen
case Enum(vs) =>
Applicative[G].pure(Enum(vs))
case e @ Enum(_) =>
Applicative[G].pure(e)
}
}

View File

@ -7,7 +7,7 @@ package reader
import ErrorFormatter._
import com.digitalasset.daml_lf.{DamlLf, DamlLf1}
import scalaz._
import scalaz.{Enum => _, _}
import scalaz.syntax.std.either._
import scalaz.std.tuple._
import scalaz.syntax.apply._
@ -57,6 +57,9 @@ object InterfaceReader {
def addVariant(k: QualifiedName, tyVars: ImmArraySeq[Ref.Name], a: Variant.FWT): State =
this.copy(typeDecls = this.typeDecls.updated(k, InterfaceType.Normal(DefDataType(tyVars, a))))
def addEnum(k: QualifiedName, tyVars: ImmArraySeq[Ref.Name], a: Enum) =
this.copy(typeDecls = this.typeDecls.updated(k, InterfaceType.Normal(DefDataType(tyVars, a))))
def removeRecord(k: QualifiedName): Option[(Record.FWT, State)] =
this.typeDecls.get(k).flatMap {
case InterfaceType.Normal(DefDataType(_, rec: Record.FWT)) =>
@ -124,10 +127,12 @@ object InterfaceReader {
case (_, (k, typVars, a)) => (k, InterfaceType.Normal(DefDataType(typVars, a)))
}, errors = partitions.errorTree |+| recordErrs)
val z1 = partitions.templates.foldLeft(z0)(foldTemplate(n, ctx))
foldVariants(
val z2 = foldVariants(
z1,
partitionIndexedErrs(partitions.variants)(variant(n, ctx)) rightMap (_.values))
.alterErrors(_ locate n)
foldEnums(z2, partitionIndexedErrs(partitions.enums)(enum(n)) rightMap (_.values))
.alterErrors(_ locate n)
}
)
}
@ -170,6 +175,23 @@ object InterfaceReader {
(k, tyVars.toSeq, Variant(fields.toSeq))
}
private def foldEnums(
state: State,
a: (InterfaceReaderError.Tree, Iterable[(QualifiedName, ImmArraySeq[Ref.Name], Enum)]))
: State =
addPartitionToState(state, a) {
case (st, (k, typVars, enum)) => st.addEnum(k, typVars, enum)
}
private[reader] def enum(m: ModuleName)(a: DamlLf1.DefDataType)
: InterfaceReaderError.Tree \/ (QualifiedName, ImmArraySeq[Ref.Name], Enum) =
(locate('name, rootErrOf[ErrorLoc](fullName(m, a.getName))).validation |@|
locate('typeParams, typeParams(a)).validation |@|
locate('constructors, rootErrOf[ErrorLoc](enumConstructors(a))).validation) {
(k, tyVars, constructors) =>
(k, tyVars.toSeq, Enum(constructors))
}.disjunction
private[this] def recordOrVariant[Z](
m: ModuleName,
a: DamlLf1.DefDataType,
@ -266,6 +288,9 @@ object InterfaceReader {
ctx: Context): InterfaceReaderError \/ FieldWithType =
type_(a.getType, ctx).flatMap(t => name(a.getField).map(_ -> t))
private def enumConstructors(as: DamlLf1.DefDataType): InterfaceReaderError \/ ImmArraySeq[Name] =
ImmArray(as.getEnum.getConstructorsList.asScala).toSeq.traverseU(name)
/**
* `Fun`, `Forall` and `Tuple` should never appear in Records and Variants
*/

View File

@ -485,7 +485,7 @@ object Ast {
final case class DataRecord(fields: ImmArray[(FieldName, Type)], optTemplate: Option[Template])
extends DataCons
final case class DataVariant(variants: ImmArray[(VariantConName, Type)]) extends DataCons
final case class DataEnum(values: ImmArray[EnumConName]) extends DataCons
final case class DataEnum(constructors: ImmArray[EnumConName]) extends DataCons
case class TemplateKey(
typ: Type,

View File

@ -23,7 +23,6 @@ object ValueVersions
private[this] val minOptional = ValueVersion("2")
private[value] val minContractIdStruct = ValueVersion("3")
private[this] val minMap = ValueVersion("4")
private[this] val minEnum = ValueVersion("dev")
def assignVersion[Cid](v0: Value[Cid]): Either[String, ValueVersion] = {
import com.digitalasset.daml.lf.transaction.VersionTimeline.{maxVersion => maxVV}
@ -52,7 +51,8 @@ object ValueVersions
case ValueMap(map) =>
go(maxVV(minMap, currentVersion), map.values ++: values)
case ValueEnum(_, _) =>
go(maxVV(minEnum, currentVersion), values)
// FixMe (RH) https://github.com/digital-asset/daml/issues/105
throw new NotImplementedError("Enum types not supported")
// tuples are a no-no
case ValueTuple(fields) =>
Left(s"Got tuple when trying to assign version. Fields: $fields")

View File

@ -194,8 +194,7 @@ object Pretty {
PrettyField(label, fieldType._2)
}))
case e: model.DamlLfEnum =>
// FixMe (RH) https://github.com/digital-asset/daml/issues/105
throw new NotImplementedError("Enum types not supported")
PrettyArray(e.constructors.map(PrettyPrimitive).toList)
}
}
@ -223,6 +222,8 @@ object Pretty {
PrettyObject(
PrettyField(constructor, argument(value))
)
case model.ApiEnum(id, constructor) =>
PrettyPrimitive(constructor)
case model.ApiList(elements) =>
PrettyArray(
elements.map(e => argument(e))

View File

@ -29,6 +29,7 @@ object ApiCodecCompressed {
def apiValueToJsValue(value: Model.ApiValue): JsValue = value match {
case v: Model.ApiRecord => apiRecordToJsValue(v)
case v: Model.ApiVariant => apiVariantToJsValue(v)
case v: Model.ApiEnum => apiEnumToJsValue(v)
case v: Model.ApiList => apiListToJsValue(v)
case Model.ApiText(v) => JsString(v)
case Model.ApiInt64(v) => JsString(v.toString)
@ -53,6 +54,9 @@ object ApiCodecCompressed {
def apiVariantToJsValue(value: Model.ApiVariant): JsValue =
JsObject(Map(value.constructor -> apiValueToJsValue(value.value)))
def apiEnumToJsValue(value: Model.ApiEnum): JsValue =
JsString(value.constructor)
def apiRecordToJsValue(value: Model.ApiRecord): JsValue =
JsObject(value.fields.map(f => f.label -> apiValueToJsValue(f.value)).toMap)
@ -135,6 +139,11 @@ object ApiCodecCompressed {
constructor._1,
jsValueToApiType(constructor._2, constructorType, defs)
)
case (JsString(c), Model.DamlLfEnum(cons)) =>
Model.ApiEnum(
Some(id),
c
)
case _ => deserializationError(s"Can't read ${value.prettyPrint} as $dt")
}

View File

@ -40,6 +40,7 @@ object ApiCodecVerbose {
private[this] final val tagMap: String = "map"
private[this] final val tagRecord: String = "record"
private[this] final val tagVariant: String = "variant"
private[this] final val tagEnum: String = "enum"
// ------------------------------------------------------------------------------------------------------------------
// Encoding
@ -47,6 +48,7 @@ object ApiCodecVerbose {
def apiValueToJsValue(value: Model.ApiValue): JsValue = value match {
case v: Model.ApiRecord => apiRecordToJsValue(v)
case v: Model.ApiVariant => apiVariantToJsValue(v)
case v: Model.ApiEnum => apiEnumToJsValue(v)
case v: Model.ApiList => apiListToJsValue(v)
case Model.ApiText(v) => JsObject(propType -> JsString(tagText), propValue -> JsString(v))
case Model.ApiInt64(v) =>
@ -94,6 +96,13 @@ object ApiCodecVerbose {
propValue -> apiValueToJsValue(value.value)
)
def apiEnumToJsValue(value: Model.ApiEnum): JsValue =
JsObject(
propType -> JsString(tagEnum),
propId -> value.enumId.map(_.toJson).getOrElse(JsNull),
propConstructor -> JsString(value.constructor),
)
def apiRecordToJsValue(value: Model.ApiRecord): JsValue =
JsObject(
propType -> JsString(tagRecord),
@ -123,6 +132,7 @@ object ApiCodecVerbose {
strField(value, propType, "ApiValue") match {
case `tagRecord` => jsValueToApiRecord(value)
case `tagVariant` => jsValueToApiVariant(value)
case `tagEnum` => jsValueToApiEnum(value)
case `tagList` =>
Model.ApiList(arrayField(value, propValue, "ApiList").map(jsValueToApiValue))
case `tagText` => Model.ApiText(strField(value, propValue, "ApiText"))
@ -194,6 +204,21 @@ object ApiCodecVerbose {
s"Can't read ${value.prettyPrint} as ApiVariant, type '$t' is not a variant")
}
def jsValueToApiEnum(value: JsValue): Model.ApiEnum =
strField(value, propType, "ApiEnum") match {
case `tagEnum` =>
Model.ApiEnum(
asObject(value, "ApiEnum").fields
.get(propId)
.flatMap(_.convertTo[Option[DamlLfIdentifier]]),
strField(value, propConstructor, "ApiEnum")
)
case t =>
deserializationError(
s"Can't read ${value.prettyPrint} as ApiEnum, type '$t' is not a enum"
)
}
// ------------------------------------------------------------------------------------------------------------------
// Implicits that can be imported for .parseJson and .toJson functions
// ------------------------------------------------------------------------------------------------------------------

View File

@ -4,8 +4,8 @@
package com.digitalasset.navigator.json
import com.digitalasset.daml.lf.data.{Ref => DamlLfRef}
import com.digitalasset.navigator.{model => Model}
import com.digitalasset.navigator.json.Util._
import com.digitalasset.navigator.{model => Model}
import spray.json._
/**
@ -27,6 +27,7 @@ object DamlLfCodec {
private[this] final val propArgs: String = "args"
private[this] final val propVars: String = "vars"
private[this] final val propFields: String = "fields"
private[this] final val propConstructors: String = "constructors"
private[this] final val tagTypeCon: String = "typecon"
private[this] final val tagTypeVar: String = "typevar"
@ -43,6 +44,7 @@ object DamlLfCodec {
private[this] final val tagTypeUnit: String = "unit"
private[this] final val tagTypeRecord: String = "record"
private[this] final val tagTypeVariant: String = "variant"
private[this] final val tagTypeEnum: String = "enum"
private[this] final val tagTypeOptional: String = "optional"
private[this] final val tagTypeMap: String = "map"
@ -115,8 +117,10 @@ object DamlLfCodec {
.toVector)
)
case e: Model.DamlLfEnum =>
// FixMe (RH) https://github.com/digital-asset/daml/issues/105
throw new NotImplementedError("Enum types not supported")
JsObject(
propType -> JsString(tagTypeEnum),
propConstructors -> JsArray(e.constructors.map(JsString(_)).toVector)
)
}
def damlLfDefDataTypeToJsValue(value: Model.DamlLfDefDataType): JsValue = JsObject(
@ -183,6 +187,9 @@ object DamlLfCodec {
nameField(f, propName, "DamlLfVariant"),
jsValueToDamlLfType(anyField(f, propValue, "DamlLfVariant")))): _*)
)
case `tagTypeEnum` =>
val constructors = arrayField(value, propConstructors, "DamlLfEnum")
Model.DamlLfEnum(Model.DamlLfImmArraySeq(constructors: _*).map(asName(_, "DamlLfEnum")))
case t =>
deserializationError(
s"Can't read ${value.prettyPrint} as DamlLfDataType, unknown type '$t'")

View File

@ -36,6 +36,10 @@ final case class ApiVariant(
constructor: String,
value: ApiValue)
extends ApiValue
final case class ApiEnum(
enumId: Option[DamlLfIdentifier],
constructor: String
) extends ApiValue
final case class ApiList(elements: List[ApiValue]) extends ApiValue
final case class ApiOptional(value: Option[ApiValue]) extends ApiValue
final case class ApiMap(value: SortedLookupList[ApiValue]) extends ApiValue

View File

@ -34,9 +34,9 @@ case class PackageRegistry(
)
def withPackages(interfaces: List[DamlLfIface.Interface]): PackageRegistry = {
val newPackages: Map[DamlLfRef.PackageId, DamlLfPackage] = interfaces
val newPackages = interfaces
.filterNot(p => packages.contains(p.packageId))
.map(p => {
.map { p =>
val typeDefs = p.typeDecls.collect {
case (qname, DamlLfIface.reader.InterfaceType.Normal(t)) =>
DamlLfIdentifier(p.packageId, qname) -> t
@ -48,8 +48,7 @@ case class PackageRegistry(
DamlLfIdentifier(p.packageId, qname) -> template(p.packageId, qname, r, t)
}
p.packageId -> DamlLfPackage(p.packageId, typeDefs, templates)
})
.toMap
}
val newTemplates = newPackages
.map(_._2.templates)
@ -159,8 +158,7 @@ case class PackageRegistry(
case DamlLfVariant(fields) =>
fields.foldLeft(deps)((r, field) => foldType(field._2, r, instantiatesRemaining))
case DamlLfEnum(_) =>
// FixMe (RH) https://github.com/digital-asset/daml/issues/105
throw new NotImplementedError("Enum types not supported")
deps
}
}

View File

@ -365,13 +365,37 @@ case object LedgerApiV1 {
}
choice <- dt.fields
.find(f => f._1 == variant.constructor)
.toRight(GenericConversionError(s"Unknown choice ${variant.constructor}"))
.toRight(GenericConversionError(s"Unknown enum constructor ${variant.constructor}"))
argument <- readArgument(value, choice._2, ctx)
} yield {
Model.ApiVariant(Some(typeCon.name.identifier), variant.constructor, argument)
}
}
private def readEnumArgument(
enum: V1.value.Enum,
typ: Model.DamlLfType,
ctx: Context
): Result[Model.ApiEnum] =
for {
typeCon <- typ match {
case t @ Model.DamlLfTypeCon(_, _) => Right(t)
case _ => Left(GenericConversionError(s"Cannot read $enum as $typ"))
}
ddt <- ctx.templates
.damlLfDefDataType(typeCon.name.identifier)
.toRight(GenericConversionError(s"Unknown type ${typeCon.name.identifier}"))
dt <- typeCon.instantiate(ddt) match {
case v @ iface.Enum(_) => Right(v)
case iface.Record(_) | iface.Variant(_) =>
Left(GenericConversionError(s"Enum expected"))
}
_ <- Either.cond(
dt.constructors.contains(enum.constructor),
(),
GenericConversionError(s"Unknown choice ${enum.constructor}"))
} yield Model.ApiEnum(Some(typeCon.name.identifier), enum.constructor)
private def readArgument(
value: V1.value.Value,
typ: Model.DamlLfType,
@ -393,6 +417,7 @@ case object LedgerApiV1 {
case (VS.Map(v), t) => readMapArgument(v, t, ctx)
case (VS.Record(v), t) => readRecordArgument(v, t, ctx)
case (VS.Variant(v), t) => readVariantArgument(v, t, ctx)
case (VS.Enum(v), t) => readEnumArgument(v, t, ctx)
case (VS.Empty, _) => Left(GenericConversionError("Argument value is empty"))
case (_, _) => Left(GenericConversionError(s"Cannot read argument $value as $typ"))
}
@ -422,6 +447,8 @@ case object LedgerApiV1 {
value match {
case arg: Model.ApiRecord => writeRecordArgument(arg).map(a => Value(Value.Sum.Record(a)))
case arg: Model.ApiVariant => writeVariantArgument(arg).map(a => Value(Value.Sum.Variant(a)))
case Model.ApiEnum(id, cons) =>
Right(Value(Value.Sum.Enum(V1.value.Enum(id.map(_.asApi), cons))))
case arg: Model.ApiList => writeListArgument(arg).map(a => Value(Value.Sum.List(a)))
case Model.ApiBool(v) => Right(Value(Value.Sum.Bool(v)))
case Model.ApiInt64(v) => Right(Value(Value.Sum.Int64(v)))

View File

@ -37,29 +37,32 @@ package object filter {
ps: DamlLfTypeLookup): Either[DotNotFailure, Boolean] = {
@annotation.tailrec
def loop(
parameter: DamlLfType,
cursor: PropertyCursor,
ps: DamlLfTypeLookup): Either[DotNotFailure, Boolean] =
def loop(parameter: DamlLfType, cursor: PropertyCursor): Either[DotNotFailure, Boolean] =
parameter match {
case tc: DamlLfTypeCon =>
val next = for {
ddt <- ps(tc.name.identifier)
nextCursor <- cursor.next
//nextField <- tc.instantiate(ddt) match {
nextField <- damlLfInstantiate(tc, ddt) match {
case DamlLfRecord(fields) => fields.find(f => f._1 == nextCursor.current)
case DamlLfVariant(fields) => fields.find(f => f._1 == nextCursor.current)
case DamlLfEnum(_) =>
// FixMe (RH) https://github.com/digital-asset/daml/issues/105
throw new NotImplementedError("Enum types not supported")
val nextOrResult =
(ps(tc.name.identifier).map(damlLfInstantiate(tc, _)), cursor.next) match {
case (Some(DamlLfRecord(fields)), Some(nextCursor)) =>
fields
.collectFirst {
case (nextCursor.current, fType) => fType -> nextCursor
}
.toLeft(false)
case (Some(DamlLfVariant(fields)), Some(nextCursor)) =>
fields
.collectFirst {
case (nextCursor.current, fType) => fType -> nextCursor
}
.toLeft(false)
case (Some(DamlLfEnum(constructors)), _) =>
Right(constructors.exists(checkContained(_, expectedValue)))
case (None, _) | (_, None) =>
Right(false)
}
} yield {
(nextField._2, nextCursor)
}
next match {
case Some((nextType, nextCursor)) => loop(nextType, nextCursor, ps)
case None => Right(false)
nextOrResult match {
case Right(r) => Right(r)
case Left((typ, nextCursor)) => loop(typ, nextCursor)
}
case DamlLfTypeVar(name) => Right(checkContained(name, expectedValue))
@ -84,7 +87,7 @@ package object filter {
Right(checkContained("map", expectedValue))
}
loop(rootParam, cursor.prev.get, ps)
loop(rootParam, cursor.prev.get)
}
def checkArgument(
@ -123,6 +126,15 @@ package object filter {
case _ => Right(false)
}
}
case ApiEnum(_, constructor) =>
cursor.next match {
case None => Right(false)
case Some(nextCursor) =>
nextCursor.current match {
case "__constructor" => Right(checkContained(constructor, expectedValue))
case _ => Right(false)
}
}
case ApiList(elements) =>
cursor.next match {
case None => Right(false)

View File

@ -134,6 +134,15 @@ object project {
case _ => Left(UnknownProperty("variant", nextCursor, expectedValue))
}
}
case ApiEnum(_, constructor) =>
cursor.next match {
case None => Left(MustNotBeLastPart("enum", cursor, expectedValue))
case Some(nextCursor) =>
nextCursor.current match {
case "__constructor" => Right(StringValue(constructor))
case _ => Left(UnknownProperty("enum", nextCursor, expectedValue))
}
}
case ApiList(elements) =>
cursor.next match {
case None => Left(MustNotBeLastPart("list", cursor, expectedValue))

View File

@ -1,26 +1,20 @@
// Copyright (c) 2019 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
import {
ChoicesButton,
ContractColumn,
} from '@da/ui-core';
import { DamlLfDataType, DamlLFFieldWithType } from '@da/ui-core/lib/api/DamlLfType';
import { DamlLfValueRecord } from '@da/ui-core/lib/api/DamlLfValue';
import {ChoicesButton, ContractColumn} from '@da/ui-core';
import {DamlLFFieldWithType, DamlLfRecord} from '@da/ui-core/lib/api/DamlLfType';
import * as DamlLfValueF from '@da/ui-core/lib/api/DamlLfValue';
import * as React from 'react';
import Link from '../../components/Link'
import * as Routes from '../../routes'
import {
Contract,
} from './data';
import {Contract} from './data';
function formatField(field: DamlLFFieldWithType, argument: DamlLfValueRecord): string {
function formatField(field: DamlLFFieldWithType, argument: DamlLfValueF.DamlLfValueRecord): string {
const valueField = argument.fields.filter((f) => f.label === field.name)[0];
return valueField ? JSON.stringify(DamlLfValueF.toJSON(valueField.value)) : '???';
}
function makeColumns(param: DamlLfDataType): ContractColumn<Contract>[] {
function makeColumns(param: DamlLfRecord): ContractColumn<Contract>[] {
return param.fields.map((field) => (
{
key: `argument.${field.name}`,
@ -35,7 +29,7 @@ function makeColumns(param: DamlLfDataType): ContractColumn<Contract>[] {
));
}
export default (param: DamlLfDataType): ContractColumn<Contract>[] => [
export default (param: DamlLfRecord): ContractColumn<Contract>[] => [
{
key: 'id',
title: 'ID',

View File

@ -105,7 +105,8 @@ class Component extends React.Component<Props, {}> {
render() {
const { data } = this.props;
const columns = data && data.node && data.node.__typename === 'Template' ?
const columns =
data && data.node && data.node.__typename === 'Template' && data.node.parameterDef.dataType.type === 'record' ?
makeColumns(data.node.parameterDef.dataType) : [];
return (
<ContractTable

View File

@ -50,6 +50,15 @@ const ArgumentDisplay = (props: Props): JSX.Element => {
</NestedForm>
);
}
case 'enum' : {
return (
<NestedForm level={level}>
<LabeledElement key={'type'} label={`Type (${argument.id.name})`} className={className}>
<span>{argument.constructor}</span>
</LabeledElement>
</NestedForm>
);
}
case 'list': {
return (
<NestedForm level={level}>

View File

@ -73,8 +73,8 @@ const exampleRecord: DamlLfRecord = {
{ name: 'bool parameter', value: DamlLfTypeF.bool() },
],
};
const exampleRecordDef: DamlLfDefDataType = { dataType: exampleRecord, typeVars: []}
const exampleRecordTc: DamlLfTypeCon = { type: 'typecon', name: exampleRecordId, args: [] }
const exampleRecordDef: DamlLfDefDataType = { dataType: exampleRecord, typeVars: []};
const exampleRecordTc: DamlLfTypeCon = { type: 'typecon', name: exampleRecordId, args: [] };
const typeProvider: TypeProvider = {
fetchType(id: DamlLfIdentifier,
@ -85,7 +85,7 @@ const typeProvider: TypeProvider = {
onResult(id, undefined);
}
},
}
};
export interface State {
value: DamlLfValue;

View File

@ -2,9 +2,11 @@
// SPDX-License-Identifier: Apache-2.0
import * as React from 'react';
import * as DamlLfTypeF from '../api/DamlLfType';
import {
DamlLfDataType,
DamlLfDefDataType,
DamlLfEnum,
DamlLfIdentifier,
DamlLfPrimType,
DamlLfRecord,
@ -13,31 +15,35 @@ import {
DamlLfTypePrim,
DamlLfVariant,
} from '../api/DamlLfType';
import * as DamlLfTypeF from '../api/DamlLfType';
import {
DamlLfValue,
DamlLfValueBool,
DamlLfValueDecimal,
DamlLfValueEnum,
DamlLfValueInt64,
DamlLfValueList, DamlLfValueMap,
DamlLfValueList,
DamlLfValueMap,
DamlLfValueOptional,
DamlLfValueParty,
DamlLfValueRecord,
DamlLfValueText,
DamlLfValueUnit,
DamlLfValueVariant, mapEntry,
DamlLfValueVariant,
enumCon,
mapEntry,
} from '../api/DamlLfValue';
import * as DamlLfValueF from '../api/DamlLfValue';
import Button from '../Button';
import { StyledTextInput } from '../Input';
import { LabeledElement } from '../Label';
import {StyledTextInput} from '../Input';
import {LabeledElement} from '../Label';
import NestedForm from '../NestedForm';
import Select from '../Select';
import styled from '../theme';
import TimeInput from '../TimeInput';
import { NonExhaustiveMatch, TypeErrorElement } from '../util';
import {NonExhaustiveMatch, TypeErrorElement} from '../util';
import ContractIdInput from './ContractIdInput';
import * as DamlLfValueF from '@da/ui-core/lib/api/DamlLfValue';
//tslint:disable:no-use-before-declare
@ -61,7 +67,8 @@ export function matchPrimitiveType(value: DamlLfValue, type: DamlLfTypePrim, nam
/** Returns true if both the `value` and the `type` are valid for the given type. */
function matchDataType(value: DamlLfValue, type: DamlLfDataType, name: 'record'): value is DamlLfValueRecord;
function matchDataType(value: DamlLfValue, type: DamlLfDataType, name: 'variant'): value is DamlLfValueVariant;
function matchDataType(value: DamlLfValue, type: DamlLfDataType, name: 'record' | 'variant'): boolean {
function matchDataType(value: DamlLfValue, type: DamlLfDataType, name: 'enum'): value is DamlLfValueEnum;
function matchDataType(value: DamlLfValue, type: DamlLfDataType, name: 'record' | 'variant' | 'enum'): boolean {
return (value.type === name && type.type === name)
}
@ -213,7 +220,7 @@ const UnitInput = (props: InputProps<DamlLfValueUnit>): JSX.Element => {
//-------------------------------------------------------------------------------------------------
interface VariantTypeInputProps {
parameter: DamlLfDataType;
parameter: DamlLfVariant;
disabled: boolean;
onChange(val: string): void;
varType: string | undefined;
@ -227,8 +234,7 @@ const variantTypeNone = '';
const VariantTypeInput = (props: VariantTypeInputProps): JSX.Element => {
const { parameter, disabled, onChange, varType } = props;
const options = parameter.fields
.map((f) => ({value: f.name, label: f.name}))
const options = parameter.fields.map((f) => ({value: f.name, label: f.name}));
return (
<Select
disabled={disabled}
@ -267,7 +273,7 @@ const VariantInput = (props: VariantInputProps): JSX.Element => {
const newConstructor = parameter.fields.filter((f) => f.name === val)[0]
if (newConstructor === undefined) {
// Resetting variant to initial state
onChange(DamlLfValueF.initialDataTypeValue(id, parameter))
onChange(DamlLfValueF.initialVariantValue(id, parameter))
} else if (constructor === undefined) {
// Setting a value for the first time
onChange(DamlLfValueF.variant(id, newConstructor.name, DamlLfValueF.initialValue(newConstructor.value)))
@ -301,7 +307,7 @@ const VariantInput = (props: VariantInputProps): JSX.Element => {
} else {
return (<TypeErrorElement parameter={parameter} argument={argument} />);
}
}
};
//-------------------------------------------------------------------------------------------------
// Record - nested input form
@ -347,8 +353,47 @@ const RecordInput = (props: RecordInputProps): JSX.Element => {
} else {
return (<TypeErrorElement parameter={parameter} argument={argument} />);
}
};
//-------------------------------------------------------------------------------------------------
// Enum - non-nested value
//-------------------------------------------------------------------------------------------------
interface EnumInputProps {
id: DamlLfIdentifier;
parameter: DamlLfEnum;
disabled: boolean;
onChange(val: DamlLfValueEnum): void;
argument: DamlLfValue;
level: number
}
const EnumInput = (props: EnumInputProps): JSX.Element => {
const { id, parameter, level, onChange, argument, disabled } = props;
if (matchDataType(argument, parameter, 'enum')) {
const options = parameter.constructors.map((c) => ({value: c, label: c}));
return (
<NestedForm level={level}>
<LabeledElement label={'Constructor'} key={'constructor'}>
<Select
disabled={disabled}
value={argument.constructor}
onChange={(value) => {
if (value === undefined) {
onChange(DamlLfValueF.initialEnumValue(id, parameter));
} else {
onChange(enumCon(id, value));
}}}
options={options}
/>
</LabeledElement>
</NestedForm>
);
} else {
return (<TypeErrorElement parameter={parameter} argument={argument} />);
}
};
//-------------------------------------------------------------------------------------------------
// Optional - nested value
//-------------------------------------------------------------------------------------------------
@ -530,14 +575,6 @@ interface ListInputProps extends InputProps<DamlLfValueList> {
typeProvider: TypeProvider
}
interface MapInputProps extends InputProps<DamlLfValueMap> {
parameter: DamlLfTypePrim;
name: string;
level: number
contractIdProvider?: ContractIdProvider
typeProvider: TypeProvider
}
const ListInput = (props: ListInputProps): JSX.Element => {
const { argument, parameter, level, name, onChange, disabled, contractIdProvider, typeProvider } = props;
if (matchPrimitiveType(argument, parameter, 'list')) {
@ -591,6 +628,15 @@ const ListInput = (props: ListInputProps): JSX.Element => {
}
};
interface MapInputProps extends InputProps<DamlLfValueMap> {
parameter: DamlLfTypePrim;
name: string;
level: number
contractIdProvider?: ContractIdProvider
typeProvider: TypeProvider
}
const MapInput = (props: MapInputProps): JSX.Element => {
const { argument, parameter, level, onChange, disabled, contractIdProvider, typeProvider } = props;
if (matchPrimitiveType(argument, parameter, 'map')) {
@ -694,9 +740,19 @@ class TypeConInput extends React.Component<TypeConInputProps, TypeConInputState>
const { parameter, onChange } = this.props;
if (ddt) {
const dataType = DamlLfTypeF.instantiate(parameter, ddt);
const initialValue = DamlLfValueF.initialDataTypeValue(parameter.name, dataType);
switch (dataType.type) {
case 'record':
onChange(DamlLfValueF.initialRecordValue(parameter.name, dataType));
break;
case 'variant':
onChange(DamlLfValueF.initialVariantValue(parameter.name, dataType));
break;
case 'enum':
onChange(DamlLfValueF.initialEnumValue(parameter.name, dataType));
break;
default: throw new NonExhaustiveMatch(dataType);
}
this.setState({ dataType });
onChange(initialValue);
} else {
this.setState({ dataType: undefined });
onChange(DamlLfValueF.undef());
@ -753,6 +809,17 @@ class TypeConInput extends React.Component<TypeConInputProps, TypeConInputState>
typeProvider={typeProvider}
/>
);
case 'enum' : return(
<EnumInput
id={parameter.name}
parameter={dataType}
disabled={disabled}
onChange={onChange}
argument={argument}
level={level}
/>
);
default: throw new NonExhaustiveMatch(dataType);
}
}

View File

@ -37,7 +37,8 @@ export type DamlLFFieldWithType = { name: string, value: DamlLfType }
export type DamlLfRecord = { type: 'record', fields: DamlLFFieldWithType[] }
export type DamlLfVariant = { type: 'variant', fields: DamlLFFieldWithType[] }
export type DamlLfDataType = DamlLfRecord | DamlLfVariant
export type DamlLfEnum = { type: 'enum', constructors: string[] }
export type DamlLfDataType = DamlLfRecord | DamlLfVariant | DamlLfEnum
export type DamlLfDefDataType = { dataType: DamlLfDataType, typeVars: string[] }
@ -112,9 +113,10 @@ export function instantiate(tc: DamlLfTypeCon, ddt: DamlLfDefDataType): DamlLfDa
switch (ddt.dataType.type) {
case 'record': return { type: 'record',
fields: ddt.dataType.fields.map((f) => ({name: f.name, value: mapTypeVars(f.value, (n) => typeMap[n.name])})) }
fields: ddt.dataType.fields.map((f) => ({name: f.name, value: mapTypeVars(f.value, (n) => typeMap[n.name])})) };
case 'variant': return { type: 'variant',
fields: ddt.dataType.fields.map((f) => ({name: f.name, value: mapTypeVars(f.value, (n) => typeMap[n.name])})) }
fields: ddt.dataType.fields.map((f) => ({name: f.name, value: mapTypeVars(f.value, (n) => typeMap[n.name])})) };
case 'enum': return {type : 'enum', constructors: ddt.dataType.constructors };
default: throw new NonExhaustiveMatch(ddt.dataType)
}
}

View File

@ -3,7 +3,7 @@
import * as Moment from 'moment';
import { NonExhaustiveMatch } from '../util'
import { DamlLfDataType, DamlLfIdentifier, DamlLfRecord, DamlLfType, DamlLfVariant } from './DamlLfType';
import {DamlLfEnum, DamlLfIdentifier, DamlLfRecord, DamlLfType, DamlLfVariant} from './DamlLfType';
// --------------------------------------------------------------------------------------------------------------------
// Type definitions
@ -27,6 +27,7 @@ export type DamlLfValueOptional = { type: 'optional', value: DamlLfValue | nul
export type DamlLfValueList = { type: 'list', value: DamlLfValue[] }
export type DamlLfValueRecord = { type: 'record', id: DamlLfIdentifier, fields: DamlLfRecordField[] }
export type DamlLfValueVariant = { type: 'variant', id: DamlLfIdentifier, constructor: string, value: DamlLfValue }
export type DamlLfValueEnum = { type: 'enum', id: DamlLfIdentifier, constructor: string }
export type DamlLfValueUndefined = { type: 'undefined' }
export type DamlLfValueMap = { type: 'map', value: DamlLfValueMapEntry[] }
export type DamlLfValueMapEntry = { key: string, value: DamlLfValue }
@ -46,6 +47,7 @@ export type DamlLfValue
| DamlLfValueMap
| DamlLfValueRecord
| DamlLfValueVariant
| DamlLfValueEnum
| DamlLfValueUndefined
// --------------------------------------------------------------------------------------------------------------------
@ -74,6 +76,9 @@ export function record(id: DamlLfIdentifier, fields: DamlLfRecordField[]): DamlL
export function variant(id: DamlLfIdentifier, constructor: string, value: DamlLfValue): DamlLfValueVariant {
return { type: 'variant', id, constructor, value }
}
export function enumCon(id: DamlLfIdentifier, constructor: string): DamlLfValueEnum {
return { type: 'enum', id, constructor }
}
export function undef(): DamlLfValueUndefined { return valueUndef }
// --------------------------------------------------------------------------------------------------------------------
@ -141,6 +146,12 @@ export function evalPath(value: DamlLfValue, path: string[], index: number = 0):
} else {
return notFound;
}
case 'enum':
if (isLast){
return value;
} else {
return notFound;
}
case 'map':
if (isLast) {
return value;
@ -212,6 +223,8 @@ export function toJSON(value: DamlLfValue): JSON {
return r;
case 'variant':
return {[value.constructor]: toJSON(value.value)};
case 'enum' :
return value.constructor;
case 'map':
return value.value.map((e) => ({key: e.key, value: toJSON(e.value)}));
case 'undefined': return '???';
@ -219,19 +232,14 @@ export function toJSON(value: DamlLfValue): JSON {
}
}
export function initialDataTypeValue(id: DamlLfIdentifier, dataType: DamlLfRecord): DamlLfValueRecord;
export function initialDataTypeValue(id: DamlLfIdentifier, dataType: DamlLfVariant): DamlLfValueVariant;
export function initialDataTypeValue(id: DamlLfIdentifier, dataType: DamlLfDataType):
DamlLfValueRecord | DamlLfValueVariant;
export function initialDataTypeValue(id: DamlLfIdentifier, dataType: DamlLfDataType):
DamlLfValueRecord | DamlLfValueVariant {
switch (dataType.type) {
case 'record': return record(id,
dataType.fields.map((f) => ({label: f.name, value: initialValue(f.value)})));
case 'variant': return variant(id,
dataType.fields[0].name, initialValue(dataType.fields[0].value));
default: throw new NonExhaustiveMatch(dataType);
}
export function initialRecordValue(id: DamlLfIdentifier, dataType: DamlLfRecord): DamlLfValueRecord {
return record(id, dataType.fields.map((f) => ({label: f.name, value: initialValue(f.value)})));
}
export function initialVariantValue(id: DamlLfIdentifier, dataType: DamlLfVariant): DamlLfValueVariant {
return variant(id, dataType.fields[0].name, initialValue(dataType.fields[0].value));
}
export function initialEnumValue(id: DamlLfIdentifier, dataType: DamlLfEnum): DamlLfValueEnum {
return enumCon(id, dataType.constructors[0]);
}
const momentDateFormat = 'YYYY-MM-DD';