Skip to content

Commit

Permalink
Adding ability to override formatter when constructing openlaw VM (#257)
Browse files Browse the repository at this point in the history
* Adding ability to override formatter when constructing openlaw VM

* Add support for bulk processing external signatures.

* Add name field to signatory object.

* Make action ID unique.

* Fix formatting.

Co-authored-by: David Roon <david@roon.me>
  • Loading branch information
Happy0 and adridadou authored Jun 23, 2020
1 parent de18f6d commit 665ed45
Show file tree
Hide file tree
Showing 9 changed files with 161 additions and 60 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import io.circe._
import io.circe.generic.auto._
import io.circe.generic.semiauto.{deriveDecoder, deriveEncoder}
import io.circe.syntax._
import org.adridadou.openlaw.OpenlawNativeValue
import org.adridadou.openlaw.{OpenlawNativeValue, OpenlawValue}
import org.adridadou.openlaw.parser.template.variableTypes._
import org.parboiled2.Rule1
import org.adridadou.openlaw.parser.template.expressions._
import org.adridadou.openlaw.result.Result
import org.adridadou.openlaw.result.{Failure, Result, Success}

import scala.reflect.ClassTag

Expand All @@ -29,7 +29,7 @@ trait ExpressionRules extends JsonRules {
}

def ExpressionRule: Rule1[Expression] = rule {
BooleanTerm | Term | SubTerm | Factor
variableType2 | BooleanTerm | Term | SubTerm | Factor
}

def BooleanTerm: Rule1[Expression] = rule {
Expand Down Expand Up @@ -242,6 +242,15 @@ trait ExpressionRules extends JsonRules {
)
}

def variableType2: Rule1[VariableTypeDefinition] = rule {
capture(oneOrMore(keyChar)) ~ "<" ~ variableType ~ ">" ~> (
(
s: String,
typeParameter: VariableTypeDefinition
) => VariableTypeDefinition(s.trim, Some(typeParameter))
)
}

def variableType: Rule1[VariableTypeDefinition] = rule {
capture(oneOrMore(keyChar)) ~ optional("<" ~ variableType ~ ">") ~> (
(
Expand Down Expand Up @@ -450,6 +459,7 @@ sealed trait Parameter extends OpenlawNativeValue {
executionResult: TemplateExecutionResult
): Result[List[VariableName]]
}

final case class OneValueParameter(expr: Expression) extends Parameter {
override def variables(
executionResult: TemplateExecutionResult
Expand Down Expand Up @@ -507,6 +517,32 @@ object VariableTypeDefinition {
final case class VariableTypeDefinition(
name: String,
typeParameter: Option[VariableTypeDefinition] = None
)
) extends Expression {
override def missingInput(
executionResult: TemplateExecutionResult
): Result[List[
VariableName
]] =
typeParameter.map(_.missingInput(executionResult)).getOrElse(Success(Nil))

override def validate(
executionResult: TemplateExecutionResult
): Result[Unit] =
typeParameter.map(_.validate(executionResult)).getOrElse(Success.unit)

override def expressionType(
executionResult: TemplateExecutionResult
): Result[VariableType] =
Success(VariableTypeType)

override def evaluate(
executionResult: TemplateExecutionResult
): Result[Option[OpenlawValue]] =
Failure("you cannot evaluate a type definition")

override def variables(
executionResult: TemplateExecutionResult
): Result[List[VariableName]] = Success(Nil)
}

final case class PartialOperation(op: String, expr: Expression)
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import io.circe.generic.semiauto.{deriveDecoder, deriveEncoder}
import org.adridadou.openlaw.OpenlawValue
import org.adridadou.openlaw.parser.template.variableTypes.VariableType
import org.adridadou.openlaw.parser.template.expressions.Expression
import org.adridadou.openlaw.result.Result
import org.adridadou.openlaw.result.{Result, Success}

object VariableAliasing {
implicit val variableAliasingEnc: Encoder[VariableAliasing] = deriveEncoder
Expand All @@ -16,7 +16,11 @@ final case class VariableAliasing(name: VariableName, expr: Expression)
extends Expression
with TemplatePart {
def validate(executionResult: TemplateExecutionResult): Result[Unit] =
expr.validate(executionResult)
if (name.isAnonymous) {
Success.unit
} else {
expr.validate(executionResult)
}

override def expressionType(
executionResult: TemplateExecutionResult
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package org.adridadou.openlaw.parser.template.variableTypes
import cats.implicits._
import io.circe.{Decoder, Encoder, HCursor, Json}
import cats.kernel.Eq
import io.circe.Json.JString
import org.adridadou.openlaw.parser.template._
import io.circe.parser._
import io.circe.syntax._
Expand Down Expand Up @@ -39,7 +38,7 @@ case object AbstractStructureType
val someTest = values
.foldLeft(Success(Map[VariableName, VariableDefinition]()))({
case (Success(m), (key, value)) =>
getField(key, value, executionResult).map(variableDefinition =>
getField(key, value).map(variableDefinition =>
m + (VariableName(key) -> variableDefinition)
)
case (other, (_, _)) =>
Expand Down Expand Up @@ -88,8 +87,7 @@ case object AbstractStructureType

private def getField(
name: String,
value: Parameter,
executionResult: TemplateExecutionResult
value: Parameter
): Result[VariableDefinition] = value match {
case OneValueParameter(VariableName(typeName)) =>
Success(
Expand All @@ -99,7 +97,19 @@ case object AbstractStructureType
)
)
case OneValueParameter(definition: VariableDefinition) =>
Success(definition.copy(name = VariableName(name)))
Success(
definition.copy(
name = VariableName(name),
variableTypeDefinition = definition.variableTypeDefinition
)
)
case OneValueParameter(varType: VariableTypeDefinition) =>
Success(
VariableDefinition(
name = VariableName(name),
variableTypeDefinition = Some(varType)
)
)
case _ =>
Failure("error in the constructor for Structured Type")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,16 @@ import org.adridadou.openlaw.result.{Failure, Result, Success}
import org.adridadou.openlaw.result.Implicits._
import org.adridadou.openlaw.values.ContractId
import org.adridadou.openlaw.vm.OpenlawExecutionEngine
import scala.collection.JavaConverters._

object IntegratedServiceDefinition {
val parser = new OpenlawTemplateLanguageParserService()
val engine = new OpenlawExecutionEngine()
private val signatureDefinitionStr =
"[[Input:Structure(signerEmail: Text; contractContentBase64: Text; contractTitle: Text; signaturePlaceholderText: Text; accessToken: Text; tokenExpiry: Number; refreshToken: Text)]] " +
"[[Output:Structure(signerEmail: Text; signature: Text; recordLink: Text; pdfContentsBase64: Text)]]"
"""
|[[Input:Structure(contractContentBase64: Text; contractTitle: Text; accessToken: Text; tokenExpiry: Number; refreshToken: Text; signerEmailsJson: Text)]]
|[[Output:Structure(signerEmailsJson: Text; signaturesJson: Text; recordLink: Text; pdfContentsBase64: Text)]]
""".stripMargin

private val storageDefinitionStr =
"[[Input:Structure(accessToken: Text; operation: Text; filePath: Text; fileBase64Content: Text)]] " +
Expand Down Expand Up @@ -158,12 +161,23 @@ object StorageOutput {
implicit val storageOutputDec: Decoder[StorageOutput] = deriveDecoder
implicit val storageOutputEq: Eq[StorageOutput] = Eq.fromUniversalEquals
}

final case class Signatory(
signaturePlaceholderText: String,
email: String,
name: String
)

object Signatory {
implicit val enc: Encoder[Signatory] = deriveEncoder
implicit val dev: Decoder[Signatory] = deriveDecoder
}

final case class SignatureInput(
signerEmail: Email,
// Json representation of List[Signatory]
signerEmailsJson: String,
contractContentBase64: String,
contractTitle: String,
// The text that the signature service should match to determine where on the document the user should sign.
signaturePlaceholderText: String,
accessToken: String,
// When the token will expire as a unix timestamp
tokenExpiry: Long,
Expand All @@ -178,35 +192,17 @@ object SignatureInput {
}

final case class SignatureOutput(
signerEmail: Email,
signature: EthereumSignature,
// Json representation of List[Email]
signerEmailsJson: String,
// Json representation of List[EthereumSignature]
signaturesJson: String,
recordLink: String,
pdfContentsBase64: String
)
object SignatureOutput {
implicit val signatureOutputEnc: Encoder[SignatureOutput] =
Encoder.instance[SignatureOutput] { output =>
Json.obj(
"signerEmail" -> Json.fromString(output.signerEmail.email),
"signature" -> Json.fromString(output.signature.toString),
"recordLink" -> Json.fromString(output.recordLink),
"pdfContentsBase64" -> Json.fromString(output.pdfContentsBase64)
)
}
implicit val signatureOutputDec: Decoder[SignatureOutput] =
Decoder.instance[SignatureOutput] { c: HCursor =>
for {
signerEmail <- c.downField("signerEmail").as[Email]
signature <- c.downField("signature").as[String]
recordLink <- c.downField("recordLink").as[String]
pdfContentsBase64 <- c.downField("pdfContentsBase64").as[String]
} yield SignatureOutput(
signerEmail,
EthereumSignature(signature).getOrThrow(),
recordLink,
pdfContentsBase64
)
}
implicit val signatureOutputEnc: Encoder[SignatureOutput] = deriveEncoder
implicit val signatureOutputDec: Decoder[SignatureOutput] = deriveDecoder

implicit val signatureOutputEq: Eq[SignatureOutput] = Eq.fromUniversalEquals

def prepareDataToSign(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,8 @@ object Email {

object SignatureAction {
implicit val signatureActionEq: Eq[SignatureAction] = Eq.fromUniversalEquals

val bulkRequestIdentifier = "bulkRequestIdentifier"
}

final case class SignatureAction(
Expand All @@ -183,12 +185,30 @@ final case class SignatureAction(
override def nextActionSchedule(
executionResult: TemplateExecutionResult,
pastExecutions: List[OpenlawExecution]
): Result[Option[Instant]] =
if (executionResult.hasSigned(email)) {
Success(None)
} else {
Success(Some(executionResult.info.now))
): Result[Option[Instant]] = {

services match {
case List(ServiceName.openlawServiceName) =>
if (executionResult.hasSigned(email)) {
Success(None)
} else {
Success(Some(executionResult.info.now))
}
case _ =>
pastExecutions.find({
case externalCall: PendingExternalCallExecution =>
externalCall.requestIdentifier.identifier
.startsWith(SignatureAction.bulkRequestIdentifier)
case _ => false
}) match {
case None if executionResult.hasSigned(email) => Success(None)
case None => {
Success(Some(executionResult.info.now))
}
case Some(_) => Success(None)
}
}
}

override def identifier(
executionResult: TemplateExecutionResult
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -273,8 +273,7 @@ trait ParameterTypeProvider {
): VariableType with ParameterType
}

abstract class VariableType(val name: String) {

abstract class VariableType(val name: String) extends OpenlawNativeValue {
def serialize: Json = Json.obj("name" -> io.circe.Json.fromString(name))

def validateOperation(
Expand Down Expand Up @@ -516,6 +515,18 @@ abstract class VariableType(val name: String) {
}
}

object VariableTypeType extends VariableType("VariableType") {
override def getTypeClass: Class[_ <: OpenlawValue] = classOf[VariableType]
override def cast(
value: String,
executionResult: TemplateExecutionResult
): Result[OpenlawValue] = Failure("you cannot cast a variable type!")
override def internalFormat(value: OpenlawValue): Result[String] =
VariableType.convert[VariableType](value).map(varType => varType.name)

override def thisType: VariableType = VariableTypeType
}

object VariableType {

val allTypes: List[VariableType] =
Expand Down
24 changes: 19 additions & 5 deletions shared/src/main/scala/org/adridadou/openlaw/vm/OpenlawVm.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import scala.reflect.ClassTag
import io.circe.generic.semiauto._
import LocalDateTimeHelper._
import org.adridadou.openlaw._
import org.adridadou.openlaw.parser.template.formatters.Formatter

final case class Signature(userId: UserId, signature: OpenlawSignatureEvent)

Expand Down Expand Up @@ -56,7 +57,11 @@ final case class OpenlawVmState(
executionState: ContractExecutionState,
crypto: CryptoService,
externalCallStructures: Map[ServiceName, IntegratedServiceDefinition] =
Map.empty
Map.empty,
overriddenFormatter: (
Option[FormatterDefinition],
TemplateExecutionResult
) => Option[Formatter] = (_, _) => None
) extends LazyLogging {

def updateTemplate(
Expand Down Expand Up @@ -196,7 +201,7 @@ final case class OpenlawVmState(
Some(definition.id(crypto)),
Some(definition.creationDate),
profileAddress,
(_, _) => None
overriddenFormatter
)
) match {
case None =>
Expand All @@ -219,7 +224,11 @@ final case class OpenlawVm(
identityOracle: OpenlawSignatureOracle,
oracles: List[OpenlawOracle[_]],
externalCallStructures: Map[ServiceName, IntegratedServiceDefinition] =
Map()
Map(),
overriddenFormatter: (
Option[FormatterDefinition],
TemplateExecutionResult
) => Option[Formatter] = (_, _) => None
) extends LazyLogging {
private val templateOracle = TemplateLoadOracle(crypto)
val contractId: ContractId = contractDefinition.id(crypto)
Expand All @@ -234,7 +243,8 @@ final case class OpenlawVm(
optExecutionResult = None,
executionState = ContractCreated,
crypto = crypto,
externalCallStructures = externalCallStructures
externalCallStructures = externalCallStructures,
overriddenFormatter = overriddenFormatter
)

def isSignatureValid(
Expand Down Expand Up @@ -558,14 +568,18 @@ final case class OpenlawVm(
): Result[OpenlawVm] = {
vm.allNextActions.flatMap { nextActions =>
vm.executionState match {
case ContractCreated if nextActions.isEmpty =>
case ContractCreated if nextActions.isEmpty && everyoneHasSigned(vm) =>
vm(UpdateExecutionStateCommand(ContractRunning, event))
case _ =>
Success(vm)
}
}
}

private def everyoneHasSigned(vm: OpenlawVm) = {
vm.allSignatures.size == vm.allIdentities.map(_.size).getOrElse(0)
}

private def executeEvent(event: OpenlawVmEvent): Result[OpenlawVm] =
oracles.find(_.shouldExecute(event)) match {
case Some(oracle) =>
Expand Down
Loading

0 comments on commit 665ed45

Please sign in to comment.