Skip to content

Commit

Permalink
Implement exercise by key
Browse files Browse the repository at this point in the history
ExerciseCommand got a new required element: `reference` of polymorphic type.
  • Loading branch information
leo-da committed Jan 15, 2020
1 parent 3381681 commit 93966b0
Show file tree
Hide file tree
Showing 13 changed files with 219 additions and 81 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -189,8 +189,10 @@ class Ledger {
*/
async exercise<T extends object, C, R>(choice: Choice<T, C, R>, contractId: ContractId<T>, argument: C): Promise<[R , Event<object>[]]> {
const payload = {
templateId: choice.template().templateId,
contractId,
reference: {
templateId: choice.template().templateId,
contractId
},
choice: choice.choiceName,
argument,
};
Expand Down
3 changes: 3 additions & 0 deletions ledger-service/http-json/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ hj_scalacopts = [
da_scala_library(
name = "http-json",
srcs = glob(["src/main/scala/**/*.scala"]),
plugins = [
"@maven//:org_spire_math_kind_projector_2_12",
],
scalacopts = hj_scalacopts,
tags = ["maven_coordinates=com.digitalasset.ledger-service:http-json:__VERSION__"],
visibility = ["//visibility:public"],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,16 @@ class CommandService(
private def liftET[A](fa: Future[A]): EitherT[Future, Error, A] = EitherT.rightT(fa)

@SuppressWarnings(Array("org.wartremover.warts.Any"))
def exercise(jwt: Jwt, jwtPayload: JwtPayload, input: ExerciseCommand[lav1.value.Value])
def exercise(
jwt: Jwt,
jwtPayload: JwtPayload,
input: ExerciseCommand[lav1.value.Value, (domain.TemplateId.RequiredPkg, domain.ContractId)])
: Future[Error \/ ExerciseResponse[lav1.value.Value]] = {

val command = exerciseCommand(input)
val request = submitAndWaitRequest(jwtPayload, input.meta, command)

val et: EitherT[Future, Error, ExerciseResponse[lav1.value.Value]] = for {
command <- EitherT.either(exerciseCommand(input))
request = submitAndWaitRequest(jwtPayload, input.meta, command)
response <- liftET(logResult('exercise, submitAndWaitForTransactionTree(jwt, request)))
exerciseResult <- EitherT.either(exerciseResult(response))
contracts <- EitherT.either(contracts(response))
Expand All @@ -76,11 +80,13 @@ class CommandService(
def exerciseWithResult(
jwt: Jwt,
jwtPayload: JwtPayload,
input: ExerciseCommand[lav1.value.Value]): Future[Error \/ lav1.value.Value] = {
input: ExerciseCommand[lav1.value.Value, (domain.TemplateId.RequiredPkg, domain.ContractId)])
: Future[Error \/ lav1.value.Value] = {

val command = exerciseCommand(input)
val request = submitAndWaitRequest(jwtPayload, input.meta, command)

val et: EitherT[Future, Error, lav1.value.Value] = for {
command <- EitherT.either(exerciseCommand(input))
request = submitAndWaitRequest(jwtPayload, input.meta, command)
response <- liftET(
logResult('exerciseWithResult, submitAndWaitForTransactionTree(jwt, request)))
result <- EitherT.either(exerciseResult(response))
Expand All @@ -106,12 +112,14 @@ class CommandService(
}

private def exerciseCommand(
input: ExerciseCommand[lav1.value.Value]): Error \/ lav1.commands.Command.Command.Exercise =
resolveTemplateId(input.templateId)
.toRightDisjunction(
Error('exerciseCommand, ErrorMessages.cannotResolveTemplateId(input.templateId)))
.map(tpId =>
Commands.exercise(refApiIdentifier(tpId), input.contractId, input.choice, input.argument))
input: ExerciseCommand[lav1.value.Value, (domain.TemplateId.RequiredPkg, domain.ContractId)])
: lav1.commands.Command.Command.Exercise = {
Commands.exercise(
templateId = refApiIdentifier(input.reference._1),
contractId = input.reference._2,
choice = input.choice,
argument = input.argument)
}

private def submitAndWaitRequest(
jwtPayload: JwtPayload,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,20 @@ class ContractsService(
)
}

def lookup(jwt: Jwt, jwtPayload: JwtPayload, contractLocator: domain.ContractLocator[ApiValue])
def resolve(jwt: Jwt, jwtPayload: JwtPayload, contractLocator: domain.ContractLocator[LfValue])
: Future[Option[(domain.TemplateId.RequiredPkg, domain.ContractId)]] =
contractLocator match {
case domain.EnrichedContractKey(templateId, key) =>
findByContractKey(jwt, jwtPayload.party, templateId, key).map(_.map(a =>
(a.templateId, a.contractId)))
case domain.EnrichedContractId(Some(templateId), contractId) =>
Future.successful(resolveTemplateId(templateId).map(a => (a, contractId)))
case domain.EnrichedContractId(None, contractId) =>
findByContractId(jwt, jwtPayload.party, None, contractId).map(_.map(a =>
(a.templateId, a.contractId)))
}

def lookup(jwt: Jwt, jwtPayload: JwtPayload, contractLocator: domain.ContractLocator[LfValue])
: Future[Option[domain.ActiveContract[LfValue]]] =
contractLocator match {
case domain.EnrichedContractKey(templateId, contractKey) =>
Expand All @@ -67,14 +80,12 @@ class ContractsService(
jwt: Jwt,
party: lar.Party,
templateId: TemplateId.OptionalPkg,
contractKey: api.value.Value): Future[Option[domain.ActiveContract[LfValue]]] =
contractKey: LfValue): Future[Option[domain.ActiveContract[LfValue]]] =
for {

lfKey <- toFuture(apiValueToLfValue(contractKey)): Future[LfValue]

resolvedTemplateId <- toFuture(resolveTemplateId(templateId)): Future[TemplateId.RequiredPkg]

predicate = isContractKey(lfKey) _
predicate = isContractKey(contractKey) _

errorOrAc <- searchInMemoryOneTpId(jwt, party, resolvedTemplateId, Map.empty)
.collect {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import com.digitalasset.http.ContractsService.SearchResult
import com.digitalasset.http.EndpointsCompanion._
import com.digitalasset.http.Statement.discard
import com.digitalasset.http.domain.JwtPayload
import com.digitalasset.http.json.{DomainJsonDecoder, DomainJsonEncoder, ResponseFormats, SprayJson}
import com.digitalasset.http.util.FutureUtil.{either, eitherT}
import com.digitalasset.http.json._
import com.digitalasset.http.util.FutureUtil.{either, eitherT, rightT}
import com.digitalasset.http.util.{ApiValueToLfValueConverter, FutureUtil}
import com.digitalasset.jwt.domain.Jwt
import com.digitalasset.ledger.api.refinements.{ApiTypes => lar}
Expand Down Expand Up @@ -84,12 +84,20 @@ class Endpoints(

cmd <- either(
decoder
.decodeV[domain.ExerciseCommand](reqBody)
.decodeExerciseCommand(reqBody)
.leftMap(e => InvalidUserInput(e.shows))
): ET[domain.ExerciseCommand[ApiValue]]
): ET[domain.ExerciseCommand[LfValue, domain.ContractLocator[LfValue]]]

resolvedRef <- eitherT(
resolveReference(jwt, jwtPayload, cmd.reference)
): ET[(domain.TemplateId.RequiredPkg, domain.ContractId)]

apiArg <- either(lfValueToApiValue(cmd.argument)): ET[ApiValue]

resolvedCmd = cmd.copy(argument = apiArg, reference = resolvedRef)

apiResp <- eitherT(
handleFutureFailure(commandService.exercise(jwt, jwtPayload, cmd))
handleFutureFailure(commandService.exercise(jwt, jwtPayload, resolvedCmd))
): ET[domain.ExerciseResponse[ApiValue]]

lfResp <- either(apiResp.traverse(apiValueToLfValue)): ET[domain.ExerciseResponse[LfValue]]
Expand Down Expand Up @@ -138,12 +146,11 @@ class Endpoints(

_ = logger.debug(s"/contracts/lookup reqBody: $reqBody")

// TODO(Leo): decode to domain.ContractLocator[LfValue], findByContractKey converts it to LfValue
cl <- either(
decoder
.decodeContractLocator(reqBody)
.leftMap(e => InvalidUserInput(e.shows))
): ET[domain.ContractLocator[ApiValue]]
): ET[domain.ContractLocator[LfValue]]

_ = logger.debug(s"/contracts/lookup cl: $cl")

Expand Down Expand Up @@ -205,7 +212,7 @@ class Endpoints(
case req @ HttpRequest(GET, Uri.Path("/parties"), _, _, _) =>
val et: ET[JsValue] = for {
_ <- FutureUtil.eitherT(input(req)): ET[(Jwt, JwtPayload, String)]
ps <- FutureUtil.rightT(partiesService.allParties()): ET[List[domain.PartyDetails]]
ps <- rightT(partiesService.allParties()): ET[List[domain.PartyDetails]]
jsVal <- either(SprayJson.encode(ps)).leftMap(e => ServerError(e.shows)): ET[JsValue]
} yield jsVal

Expand All @@ -219,6 +226,9 @@ class Endpoints(
\/.fromTryCatchNonFatal(LfValueCodec.apiValueToJsValue(a)).leftMap(e =>
ServerError(e.description))

private def lfValueToApiValue(a: LfValue): Error \/ ApiValue =
JsValueToApiValueConverter.lfValueToApiValue(a).leftMap(e => ServerError(e.shows))

private def collectActiveContracts(
predicates: Map[domain.TemplateId.RequiredPkg, LfValue => Boolean]): PartialFunction[
Error \/ domain.ActiveContract[LfValue],
Expand Down Expand Up @@ -300,6 +310,14 @@ class Endpoints(
}
.toRightDisjunction(Unauthorized("missing Authorization header with OAuth 2.0 Bearer Token"))

private def resolveReference(
jwt: Jwt,
jwtPayload: JwtPayload,
reference: domain.ContractLocator[LfValue])
: Future[Error \/ (domain.TemplateId.RequiredPkg, domain.ContractId)] =
contractsService
.resolve(jwt, jwtPayload, reference)
.map(_.toRightDisjunction(InvalidUserInput(ErrorMessages.cannotResolveTemplateId(reference))))
}

object Endpoints {
Expand All @@ -309,7 +327,4 @@ object Endpoints {
private type ApiValue = lav1.value.Value

private type LfValue = lf.value.Value[lf.value.Value.AbsoluteContractId]

private type ActiveContractStream[A] = Source[A, NotUsed]

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
package com.digitalasset.http

object ErrorMessages {
def cannotResolveTemplateId[A](t: domain.TemplateId[A]): String =
def cannotResolveTemplateId(t: domain.TemplateId[_]): String =
s"Cannot resolve template ID, given: ${t.toString}"

def cannotResolveTemplateId(a: domain.ContractLocator[_]): String =
s"Cannot resolve templateId, given: $a"
}
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ object HttpService extends StrictLogging {
packageService.resolveKeyType,
jsValueToApiValueConverter.jsObjectToApiRecord,
jsValueToApiValueConverter.jsValueToApiValue,
jsValueToApiValueConverter.jsValueToLfValue,
)

(encoder, decoder)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package com.digitalasset.http
import java.time.Instant

import akka.http.scaladsl.model.{StatusCode, StatusCodes}
import com.digitalasset.daml.lf
import com.digitalasset.daml.lf.data.Ref
import com.digitalasset.daml.lf.iface
import com.digitalasset.http.util.ClientUtil.boxedRecord
Expand Down Expand Up @@ -35,6 +36,8 @@ object domain {
}
}

type LfValue = lf.value.Value[lf.value.Value.AbsoluteContractId]

case class JwtPayload(ledgerId: lar.LedgerId, applicationId: lar.ApplicationId, party: Party)

case class TemplateId[+PkgId](packageId: PkgId, moduleName: String, entityName: String)
Expand Down Expand Up @@ -83,9 +86,8 @@ object domain {
argument: LfV,
meta: Option[CommandMeta])

final case class ExerciseCommand[+LfV](
templateId: TemplateId.OptionalPkg,
contractId: lar.ContractId,
final case class ExerciseCommand[+LfV, +Ref](
reference: Ref,
choice: lar.Choice,
argument: LfV,
meta: Option[CommandMeta])
Expand Down Expand Up @@ -387,17 +389,33 @@ object domain {
}

object ExerciseCommand {
implicit val traverseInstance: Traverse[ExerciseCommand] = new Traverse[ExerciseCommand] {
override def traverseImpl[G[_]: Applicative, A, B](fa: ExerciseCommand[A])(
f: A => G[B]): G[ExerciseCommand[B]] = f(fa.argument).map(a => fa.copy(argument = a))
implicit val bitraverseInstance: Bitraverse[ExerciseCommand] = new Bitraverse[ExerciseCommand] {
override def bitraverseImpl[G[_]: Applicative, A, B, C, D](
fab: ExerciseCommand[A, B])(f: A => G[C], g: B => G[D]): G[ExerciseCommand[C, D]] = {
import scalaz.syntax.applicative._
^(f(fab.argument), g(fab.reference))((c, d) => fab.copy(argument = c, reference = d))
}
}

implicit val hasTemplateId: HasTemplateId[ExerciseCommand] =
new HasTemplateId[ExerciseCommand] {
override def templateId(fa: ExerciseCommand[_]): TemplateId.OptionalPkg = fa.templateId
implicit val leftTraverseInstance: Traverse[ExerciseCommand[+?, Nothing]] =
bitraverseInstance.leftTraverse

implicit val hasTemplateId =
new HasTemplateId[ExerciseCommand[+?, domain.ContractLocator[_]]] {

override def templateId(
fab: ExerciseCommand[_, domain.ContractLocator[_]]): TemplateId.OptionalPkg = {
fab.reference match {
case EnrichedContractKey(templateId, _) => templateId
case EnrichedContractId(Some(templateId), _) => templateId
case EnrichedContractId(None, _) =>
throw new IllegalArgumentException(
"Please specify templateId, optional templateId is not supported yet!")
}
}

override def lfType(
fa: ExerciseCommand[_],
fa: ExerciseCommand[_, domain.ContractLocator[_]],
templateId: TemplateId.RequiredPkg,
f: PackageService.ResolveTemplateRecordType,
g: PackageService.ResolveChoiceRecordType,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ class DomainJsonDecoder(
resolveRecordType: PackageService.ResolveChoiceRecordType,
resolveKey: PackageService.ResolveKeyType,
jsObjectToApiRecord: (domain.LfType, JsObject) => JsonError \/ lav1.value.Record,
jsValueToApiValue: (domain.LfType, JsValue) => JsonError \/ lav1.value.Value) {
jsValueToApiValue: (domain.LfType, JsValue) => JsonError \/ lav1.value.Value,
jsValueToLfValue: (domain.LfType, JsValue) => JsonError \/ domain.LfValue) {

def decodeR[F[_]](a: String)(
implicit ev1: JsonReader[F[JsObject]],
Expand Down Expand Up @@ -78,6 +79,15 @@ class DomainJsonDecoder(
} yield apiValue
}

@SuppressWarnings(Array("org.wartremover.warts.Any"))
def decodeUnderlyingValuesToLf[F[_]: Traverse: domain.HasTemplateId](
fa: F[JsValue]): JsonError \/ F[domain.LfValue] = {
for {
lfType <- lookupLfType(fa)
lfValue <- fa.traverse(jsValue => jsValueToLfValue(lfType, jsValue))
} yield lfValue
}

private def lookupLfType[F[_]: domain.HasTemplateId](fa: F[_]): JsonError \/ domain.LfType = {
val H: HasTemplateId[F] = implicitly
val templateId: domain.TemplateId.OptionalPkg = H.templateId(fa)
Expand All @@ -91,7 +101,7 @@ class DomainJsonDecoder(
}

def decodeContractLocator(a: String)(implicit ev: JsonReader[domain.ContractLocator[JsValue]])
: JsonError \/ domain.ContractLocator[lav1.value.Value] =
: JsonError \/ domain.ContractLocator[domain.LfValue] =
for {
b <- SprayJson
.parse(a)
Expand All @@ -100,14 +110,47 @@ class DomainJsonDecoder(
} yield c

def decodeContractLocator(a: JsValue)(implicit ev: JsonReader[domain.ContractLocator[JsValue]])
: JsonError \/ domain.ContractLocator[lav1.value.Value] =
: JsonError \/ domain.ContractLocator[domain.LfValue] =
SprayJson
.decode[domain.ContractLocator[JsValue]](a)
.leftMap(e => JsonError("DomainJsonDecoder_decodeContractLocator " + e.shows))
.flatMap {
case k: domain.EnrichedContractKey[JsValue] =>
decodeUnderlyingValues[domain.EnrichedContractKey](k)
decodeUnderlyingValuesToLf[domain.EnrichedContractKey](k)
case c: domain.EnrichedContractId =>
\/-(c)
}

def decodeExerciseCommand(a: String)(
implicit ev1: JsonReader[domain.ExerciseCommand[JsValue, JsValue]],
ev2: JsonReader[domain.ContractLocator[JsValue]])
: JsonError \/ domain.ExerciseCommand[domain.LfValue, domain.ContractLocator[domain.LfValue]] =
for {
b <- SprayJson
.parse(a)
.leftMap(e => JsonError("DomainJsonDecoder_decodeExerciseCommand " + e.shows))
c <- decodeExerciseCommand(b)
} yield c

def decodeExerciseCommand(a: JsValue)(
implicit ev1: JsonReader[domain.ExerciseCommand[JsValue, JsValue]],
ev2: JsonReader[domain.ContractLocator[JsValue]])
: JsonError \/ domain.ExerciseCommand[domain.LfValue, domain.ContractLocator[domain.LfValue]] =
for {
cmd0 <- SprayJson
.decode[domain.ExerciseCommand[JsValue, JsValue]](a)
.leftMap(e => JsonError("DomainJsonDecoder_decodeExerciseCommand " + e.shows))

ref <- decodeContractLocator(cmd0.reference)

cmd1 = cmd0.copy(reference = ref): domain.ExerciseCommand[
JsValue,
domain.ContractLocator[domain.LfValue]]

lfType <- lookupLfType[domain.ExerciseCommand[+?, domain.ContractLocator[_]]](cmd1)(
domain.ExerciseCommand.hasTemplateId)

arg <- jsValueToLfValue(lfType, cmd1.argument)

} yield cmd1.copy(argument = arg)
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import scala.language.higherKinds

class DomainJsonEncoder(
apiRecordToJsObject: lav1.value.Record => JsonError \/ JsObject,
apiValueToJsValue: lav1.value.Value => JsonError \/ JsValue) {
val apiValueToJsValue: lav1.value.Value => JsonError \/ JsValue) {

def encodeR[F[_]](fa: F[lav1.value.Record])(
implicit ev1: Traverse[F],
Expand Down
Loading

0 comments on commit 93966b0

Please sign in to comment.