Skip to content

Commit

Permalink
fix labels+primitives bug, fix status-requests, update readme
Browse files Browse the repository at this point in the history
  • Loading branch information
seniorjoinu committed Sep 16, 2020
1 parent d5bb59b commit e7b0be0
Show file tree
Hide file tree
Showing 10 changed files with 470 additions and 115 deletions.
113 changes: 100 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
[![Release](https://jitpack.io/v/seniorjoinu/candid-kt.svg?style=flat-square)](https://jitpack.io/#seniorjoinu/candid-kt)
<img src="https://img.shields.io/badge/dfx-0.6.7-blue?style=flat-square"/>
<img src="https://img.shields.io/badge/kotlin-1.4-blue?style=flat-square"/>
<img src="https://img.shields.io/badge/android-26+-blue?style=flat-square"/>

### Candid-kt
Generates client code for your canisters
Expand All @@ -13,61 +14,147 @@ Use [the gradle plugin](https://github.com/seniorjoinu/candid-kt-gradle-plugin)

For example, this candid code
```
type Phone = nat;
type Name = text;
type Entry =
record {
description: text;
name: Name;
phone: Phone;
};
service : {
"greet": (text) -> (text);
insert: (Name, text, Phone) -> ();
lookup: (Name) -> (opt Entry) query;
}
```
would generate this Kotlin code
```kotlin
typealias MainActorValueSer = ServiceValueSer
typealias Phone = BigInteger

val PhoneValueSer: ValueSer<BigInteger> = NatValueSer

typealias Name = String

val NameValueSer: ValueSer<String> = TextValueSer

data class Entry(
val name: Name,
val description: String,
val phone: Phone
)

object EntryValueSer : ValueSer<Entry> {
val nameValueSer: ValueSer<Name> = NameValueSer

val descriptionValueSer: ValueSer<String> = TextValueSer

val phoneValueSer: ValueSer<Phone> = PhoneValueSer

override fun calcSizeBytes(value: Entry): Int = this.nameValueSer.calcSizeBytes(value.name) +
this.descriptionValueSer.calcSizeBytes(value.description) +
this.phoneValueSer.calcSizeBytes(value.phone)

override fun ser(buf: ByteBuffer, value: Entry) {
this.nameValueSer.ser(buf, value.name)
this.descriptionValueSer.ser(buf, value.description)
this.phoneValueSer.ser(buf, value.phone)
}

override fun deser(buf: ByteBuffer): Entry = Entry(this.nameValueSer.deser(buf),
this.descriptionValueSer.deser(buf), this.phoneValueSer.deser(buf))

override fun poetize(): String = Code.of("%T", EntryValueSer::class)
}

typealias PhonebookServiceValueSer = ServiceValueSer

typealias AnonFunc0ValueSer = FuncValueSer

class AnonFunc0(
funcName: String?,
service: SimpleIDLService?
) : SimpleIDLFunc(funcName, service) {
suspend operator fun invoke(arg0: String): String {
val arg0ValueSer = senior.joinu.candid.serialize.TextValueSer
val valueSizeBytes = 0 + arg0ValueSer.calcSizeBytes(arg0)
suspend operator fun invoke(
arg0: Name,
arg1: String,
arg2: Phone
) {
val arg0ValueSer = NameValueSer
val arg1ValueSer = senior.joinu.candid.serialize.TextValueSer
val arg2ValueSer = PhoneValueSer
val valueSizeBytes = 0 + arg0ValueSer.calcSizeBytes(arg0) + arg1ValueSer.calcSizeBytes(arg1) +
arg2ValueSer.calcSizeBytes(arg2)
val sendBuf = ByteBuffer.allocate(staticPayload.size + valueSizeBytes)
sendBuf.order(ByteOrder.LITTLE_ENDIAN)
sendBuf.put(staticPayload)
arg0ValueSer.ser(sendBuf, arg0)
arg1ValueSer.ser(sendBuf, arg1)
arg2ValueSer.ser(sendBuf, arg2)
val sendBytes = sendBuf.array()

val receiveBytes = this.service!!.call(this.funcName!!, sendBytes)
val receiveBuf = ByteBuffer.wrap(receiveBytes)
receiveBuf.order(ByteOrder.LITTLE_ENDIAN)
receiveBuf.rewind()
val deserContext = TypeDeser.deserUntilM(receiveBuf)
return senior.joinu.candid.serialize.TextValueSer.deser(receiveBuf) as kotlin.String
}

companion object {
val staticPayload: ByteArray = Base64.getDecoder().decode("RElETAADcXF9")
}
}

typealias AnonFunc1ValueSer = FuncValueSer

class AnonFunc1(
funcName: String?,
service: SimpleIDLService?
) : SimpleIDLFunc(funcName, service) {
suspend operator fun invoke(arg0: Name): Entry? {
val arg0ValueSer = NameValueSer
val valueSizeBytes = 0 + arg0ValueSer.calcSizeBytes(arg0)
val sendBuf = ByteBuffer.allocate(staticPayload.size + valueSizeBytes)
sendBuf.order(ByteOrder.LITTLE_ENDIAN)
sendBuf.put(staticPayload)
arg0ValueSer.ser(sendBuf, arg0)
val sendBytes = sendBuf.array()

val receiveBytes = this.service!!.query(this.funcName!!, sendBytes)
val receiveBuf = ByteBuffer.wrap(receiveBytes)
receiveBuf.order(ByteOrder.LITTLE_ENDIAN)
receiveBuf.rewind()
val deserContext = TypeDeser.deserUntilM(receiveBuf)
return senior.joinu.candid.serialize.OptValueSer( EntryValueSer ).deser(receiveBuf) as Entry?
}

companion object {
val staticPayload: ByteArray = Base64.getDecoder().decode("RElETAABcQ==")
}
}

class MainActor(
class PhonebookService(
host: String,
canisterId: SimpleIDLPrincipal?,
keyPair: EdDSAKeyPair?,
apiVersion: String = "v1"
) : SimpleIDLService(host, canisterId, keyPair, apiVersion) {
val greet: AnonFunc0 = AnonFunc0("greet", this)
val insert: AnonFunc0 = AnonFunc0("insert", this)

val lookup: AnonFunc1 = AnonFunc1("lookup", this)
}
```
which we then can use to interact with our deployed canister
```kotlin
val host = "http://localhost:8000"
val canisterId = SimpleIDLPrincipal.fromText("75hes-oqbaa-aaaaa-aaaaa-aaaaa-aaaaa-aaaaa-q")
val keyPair = EdDSAKeyPair.generateInsecure()
val keys = EdDSAKeyPair.generateInsecure()
val canisterId = "75hes-oqbaa-aaaaa-aaaaa-aaaaa-aaaaa-aaaaa-q"

val phonebook = PhonebookService(host, SimpleIDLPrincipal.fromText(canisterId), keys)

val helloWorld = MainActor(host, canisterId, keyPair)
phonebook.insert("test", "test desc", BigInteger("12345"))
val entry = phonebook.lookup("test")

val response = helloWorld.greet("World")
println(response) // Hello, World!
check(entry != null) { "Entry not found" }
```


Expand Down
55 changes: 43 additions & 12 deletions src/main/kotlin/senior/joinu/candid/CandidCodeGenerator.kt
Original file line number Diff line number Diff line change
@@ -1,25 +1,56 @@
package senior.joinu.candid

import com.github.h0tk3y.betterParse.grammar.parseToEnd
import com.squareup.kotlinpoet.FileSpec
import senior.joinu.candid.idl.IDLGrammar
import senior.joinu.candid.transpile.KtTranspiler
import java.nio.charset.Charset
import java.nio.charset.StandardCharsets
import java.nio.file.Path


object CandidCodeGenerator {
sealed class Source {
data class Str(
val data: String,
val generatedFileName: String
) : Source()

data class File(
val path: Path,
val generatedFileName: String? = null,
val encoding: Charset = StandardCharsets.UTF_8
) : Source()
}

fun generateFor(
didPath: Path,
genPath: Path,
genPackage: String = "",
didEncoding: Charset = StandardCharsets.UTF_8
) {
val did = didPath.toFile().readText(didEncoding)

val program = IDLGrammar.parseToEnd(did)
val ktContext = KtTranspiler.transpile(program, genPackage, didPath.fileName.toString())
val spec = ktContext.currentSpec.build()

spec.writeTo(genPath)
input: Source,
genPackage: String = ""
): FileSpec {
val ktContext = when (input) {
is Source.Str -> {
val did = input.data
val program = IDLGrammar.parseToEnd(did)

KtTranspiler.transpile(
program = program,
packageName = genPackage,
fileName = input.generatedFileName
)
}
is Source.File -> {
val did = input.path.toFile().readText(input.encoding)
val program = IDLGrammar.parseToEnd(did)

KtTranspiler.transpile(
program = program,
packageName = genPackage,
fileName = input.generatedFileName ?: input.path.fileName.toString()
)
}
}


return ktContext.currentSpec.build()
}
}
6 changes: 5 additions & 1 deletion src/main/kotlin/senior/joinu/candid/idl/IDLOpcode.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ enum class IDLOpcode(val value: Int) {
VARIANT(-21),
FUNC(-22),
SERVICE(-23),
PRINCIPAL(-24)
PRINCIPAL(-24);

companion object {
fun fromInt(value: Int) = IDLOpcode.values().find { it.value == value }
}
}

val MAGIC_PREFIX = byteArrayOf('D'.toByte(), 'I'.toByte(), 'D'.toByte(), 'L'.toByte())
Loading

0 comments on commit e7b0be0

Please sign in to comment.