Skip to content

Commit

Permalink
support new json cadence type types
Browse files Browse the repository at this point in the history
  • Loading branch information
briandilley committed Jun 28, 2022
1 parent 9f5181d commit b1045b2
Show file tree
Hide file tree
Showing 2 changed files with 217 additions and 5 deletions.
207 changes: 202 additions & 5 deletions src/main/kotlin/com/nftco/flow/sdk/cadence/json-cadence.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,38 @@ import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.annotation.JsonSubTypes
import com.fasterxml.jackson.annotation.JsonSubTypes.Type
import com.fasterxml.jackson.annotation.JsonTypeInfo
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.JsonDeserializer
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
import com.fasterxml.jackson.databind.exc.MismatchedInputException
import com.nftco.flow.sdk.Flow
import com.nftco.flow.sdk.bytesToHex
import java.io.Serializable
import java.math.BigDecimal
import java.math.BigInteger

// https://docs.onflow.org/cadence/json-cadence-spec/#types

// simple types
const val TYPE_ANY = "Any"
const val TYPE_ANYSTRUCT = "AnyStruct"
const val TYPE_ANYRESOURCE = "AnyResource"
const val TYPE_TYPE = "Type"
const val TYPE_VOID = "Void"
const val TYPE_OPTIONAL = "Optional"
const val TYPE_NEVER = "Never"
const val TYPE_BOOLEAN = "Bool"
const val TYPE_STRING = "String"
const val TYPE_CHARACTER = "Character"
const val TYPE_BYTES = "Bytes"
const val TYPE_NUMBER = "Number"
const val TYPE_SIGNEDNUMBER = "SignedNumber"
const val TYPE_INTEGER = "Integer"
const val TYPE_SIGNEDINTEGER = "SignedInteger"
const val TYPE_FIXEDPOINT = "FixedPoint"
const val TYPE_SIGNEDFIXEDPOINT = "SignedFixedPoint"
const val TYPE_INT = "Int"
const val TYPE_UINT = "UInt"
const val TYPE_INT8 = "Int8"
Expand All @@ -37,16 +59,41 @@ const val TYPE_WORD64 = "Word64"
const val TYPE_FIX64 = "Fix64"
const val TYPE_UFIX64 = "UFix64"
const val TYPE_ARRAY = "Array"
const val TYPE_DICTIONARY = "Dictionary"
const val TYPE_ADDRESS = "Address"
const val TYPE_PATH = "Path"
const val TYPE_CAPABILITYPATH = "CapabilityPath"
const val TYPE_STORAGEPATH = "StoragePath"
const val TYPE_PUBLICPATH = "PublicPath"
const val TYPE_PRIVATEPATH = "PrivatePath"
const val TYPE_AUTHACCOUNT = "AuthAccount"
const val TYPE_PUBLICACCOUNT = "PublicAccount"
const val TYPE_AUTHACCOUNT_KEYS = "AuthAccount.Keys"
const val TYPE_PUBLICACCOUNT_KEYS = "PublicAccount.Keys"
const val TYPE_AUTHACCOUNT_CONTRACTS = "AuthAccount.Contracts"
const val TYPE_PUBLICACCOUNT_CONTRACTS = "PublicAccount.Contracts"
const val TYPE_DEPLOYEDCONTRACT = "DeployedContract"
const val TYPE_ACCOUNTKEY = "AccountKey"
const val TYPE_BLOCK = "Block"

// complex type
const val TYPE_OPTIONAL = "Optional"
const val TYPE_DICTIONARY = "Dictionary"
const val TYPE_VARIABLE_SIZED_ARRAY = "VariableSizedArray"
const val TYPE_CONSTANT_SIZED_ARRAY = "ConstantSizedArray"
const val TYPE_CAPABILITY = "Capability"
const val TYPE_ENUM = "Enum"
const val TYPE_FUNCTION = "Function"
const val TYPE_REFERENCE = "Reference"
const val TYPE_RESTRICTION = "Restriction"

// composite types
const val TYPE_STRUCT = "Struct"
const val TYPE_RESOURCE = "Resource"
const val TYPE_EVENT = "Event"
const val TYPE_CONTRACT = "Contract"
const val TYPE_ENUM = "Enum"
const val TYPE_TYPE = "Type"
const val TYPE_STRUCT_INTERFACE = "StructInterface"
const val TYPE_RESOURCE_INTERFACE = "ResourceInterface"
const val TYPE_CONTRACT_INTERFACE = "ContractInterface"

@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
Expand Down Expand Up @@ -201,5 +248,155 @@ open class ResourceField(value: CompositeValue) : CompositeField(TYPE_RESOURCE,
open class EventField(value: CompositeValue) : CompositeField(TYPE_EVENT, value)
open class ContractField(value: CompositeValue) : CompositeField(TYPE_CONTRACT, value)
open class EnumField(value: CompositeValue) : CompositeField(TYPE_ENUM, value)
open class TypeValue(val staticType: String) : Serializable
open class TypeValue(val staticType: CadenceType) : Serializable
open class TypeField(value: TypeValue) : Field<TypeValue>(TYPE_TYPE, value)

open class InitializerType(
val label: String,
val id: String,
val type: CadenceType
) : Serializable
open class FieldType(
val id: String,
val type: CadenceType
) : Serializable
open class ParameterType(
val label: String,
val id: String,
val type: CadenceType
) : Serializable

// TODO: this JsonDeserializer.None::class is lame, but
// it's the only way I could figure out how to deserialize
// a polymorphic object using multiple of it's fields.
// this is necessary for the partial types (for repeated types)
// https://docs.onflow.org/cadence/json-cadence-spec/#repeated-types

@JsonDeserialize(using = CadenceTypeDeserializer::class)
abstract class CadenceType(val kind: String) : Serializable

@JsonDeserialize(using = JsonDeserializer.None::class)
open class PartialCadenceType(kind: String, val type: String) : CadenceType(kind)

@JsonDeserialize(using = JsonDeserializer.None::class)
open class SimpleType(kind: String) : CadenceType(kind)

@JsonDeserialize(using = JsonDeserializer.None::class)
open class OptionalType(val type: CadenceType) : CadenceType(TYPE_OPTIONAL)

@JsonDeserialize(using = JsonDeserializer.None::class)
open class VariableSizedArrayType(val type: CadenceType) : CadenceType(TYPE_VARIABLE_SIZED_ARRAY)

@JsonDeserialize(using = JsonDeserializer.None::class)
open class ConstantSizedArrayType(val type: CadenceType) : CadenceType(TYPE_CONSTANT_SIZED_ARRAY)

@JsonDeserialize(using = JsonDeserializer.None::class)
open class DictionaryType(val key: CadenceType, val value: CadenceType) : CadenceType(TYPE_DICTIONARY)

@JsonDeserialize(using = JsonDeserializer.None::class)
open class CompositeType(
kind: String,
val type: String,
val typeID: String,
val initializers: Array<InitializerType>,
val fields: Array<FieldType>
) : CadenceType(kind)

@JsonDeserialize(using = JsonDeserializer.None::class)
open class FunctionType(
val typeID: String,
val parameters: Array<ParameterType>,
val `return`: CadenceType
) : CadenceType(TYPE_FUNCTION)

@JsonDeserialize(using = JsonDeserializer.None::class)
open class ReferenceType(
val typeID: String,
val authorized: Boolean,
val type: CadenceType
) : CadenceType(TYPE_REFERENCE)

@JsonDeserialize(using = JsonDeserializer.None::class)
open class RestrictionType(
val typeID: String,
val type: CadenceType,
val restrictions: Array<CadenceType>
) : CadenceType(TYPE_RESTRICTION)

@JsonDeserialize(using = JsonDeserializer.None::class)
open class CapabilityType(
val type: CadenceType
) : CadenceType(TYPE_CAPABILITY)

@JsonDeserialize(using = JsonDeserializer.None::class)
open class EnumType(
val type: CadenceType,
val typeID: String,
val initializers: Array<InitializerType>,
val fields: Array<FieldType>
) : CadenceType(TYPE_ENUM)

class CadenceTypeDeserializer(vc: Class<*>?) : StdDeserializer<CadenceType>(vc) {
constructor() : this(null)

override fun deserialize(p: JsonParser, ctxt: DeserializationContext): CadenceType {
val node = p.codec.readTree<JsonNode>(p)
if (!node.isObject) {
throw MismatchedInputException.from(p, "Expected object for CadenceType, found ${node.nodeType} instead")
}

val kind = node.get("kind")?.asText()
?: throw MismatchedInputException.from(p, "kind not specified for CadenceType")
val type = node.get("type")

if (type != null && type.isTextual && !type.textValue().isNullOrBlank()) {
return PartialCadenceType(
kind = kind,
type = type.asText()
)
}

return when (kind) {
TYPE_ANY, TYPE_ANYSTRUCT, TYPE_ANYRESOURCE, TYPE_TYPE, TYPE_VOID, TYPE_NEVER, TYPE_BOOLEAN,
TYPE_STRING, TYPE_CHARACTER, TYPE_BYTES, TYPE_NUMBER, TYPE_SIGNEDNUMBER, TYPE_INTEGER, TYPE_SIGNEDINTEGER,
TYPE_FIXEDPOINT, TYPE_SIGNEDFIXEDPOINT, TYPE_INT, TYPE_UINT, TYPE_INT8, TYPE_UINT8, TYPE_INT16,
TYPE_UINT16, TYPE_INT32, TYPE_UINT32, TYPE_INT64, TYPE_UINT64, TYPE_INT128, TYPE_UINT128, TYPE_INT256,
TYPE_UINT256, TYPE_WORD8, TYPE_WORD16, TYPE_WORD32, TYPE_WORD64, TYPE_FIX64, TYPE_UFIX64, TYPE_ARRAY,
TYPE_ADDRESS, TYPE_PATH, TYPE_CAPABILITYPATH, TYPE_STORAGEPATH, TYPE_PUBLICPATH,
TYPE_PRIVATEPATH, TYPE_AUTHACCOUNT, TYPE_PUBLICACCOUNT, TYPE_AUTHACCOUNT_KEYS, TYPE_PUBLICACCOUNT_KEYS,
TYPE_AUTHACCOUNT_CONTRACTS, TYPE_PUBLICACCOUNT_CONTRACTS, TYPE_DEPLOYEDCONTRACT, TYPE_ACCOUNTKEY,
TYPE_BLOCK -> {
p.codec.treeToValue(node, SimpleType::class.java)
}
TYPE_STRUCT, TYPE_RESOURCE, TYPE_EVENT, TYPE_CONTRACT, TYPE_STRUCT_INTERFACE, TYPE_RESOURCE_INTERFACE,
TYPE_CONTRACT_INTERFACE -> {
p.codec.treeToValue(node, CompositeType::class.java)
}
TYPE_OPTIONAL -> {
p.codec.treeToValue(node, OptionalType::class.java)
}
TYPE_VARIABLE_SIZED_ARRAY -> {
p.codec.treeToValue(node, VariableSizedArrayType::class.java)
}
TYPE_CONSTANT_SIZED_ARRAY -> {
p.codec.treeToValue(node, ConstantSizedArrayType::class.java)
}
TYPE_DICTIONARY -> {
p.codec.treeToValue(node, DictionaryType::class.java)
}
TYPE_FUNCTION -> {
p.codec.treeToValue(node, FunctionType::class.java)
}
TYPE_REFERENCE -> {
p.codec.treeToValue(node, ReferenceType::class.java)
}
TYPE_CAPABILITY -> {
p.codec.treeToValue(node, CapabilityType::class.java)
}
TYPE_ENUM -> {
p.codec.treeToValue(node, EnumType::class.java)
}
else -> throw MismatchedInputException.from(p, "Unknown CadenceType kind: $kind")
}
}
}
15 changes: 15 additions & 0 deletions src/test/kotlin/com/nftco/flow/sdk/JsonCadenceTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.nftco.flow.sdk

import org.assertj.core.api.Assertions
import org.junit.jupiter.api.Test

class JsonCadenceTest {

@Test
fun `Can parse new JSON Cadence`() {
val flow = TestUtils.newMainnetAccessApi()
val tx = flow.getTransactionResultById(FlowId("663869d910278d7b6caf793396f6f2c5b91aace7180c2c70cfb3b0b6efd7a049"))!!
val events = tx.events.map { it.payload.jsonCadence }
Assertions.assertThat(events).hasSize(4)
}
}

0 comments on commit b1045b2

Please sign in to comment.