Git Product home page Git Product logo

candid-kt's Introduction

Release

Candid-kt

Generates client code for your canisters

Usage

Use the gradle plugin to generate Kotlin code out of candid code.

For example, this candid code

type Phone = nat;
type Name = text;
type Entry = 
 record {
   description: text;
   name: Name;
   phone: Phone;
 };
service : {
  insert: (Name, text, Phone) -> ();
  lookup: (Name) -> (opt Entry) query;
}

would generate this Kotlin code

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: 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)
    }

    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 PhonebookService(
    host: String,
    canisterId: SimpleIDLPrincipal?,
    keyPair: EdDSAKeyPair?,
    apiVersion: String = "v1"
) : SimpleIDLService(host, canisterId, keyPair, apiVersion) {
    val insert: AnonFunc0 = AnonFunc0("insert", this)

    val lookup: AnonFunc1 = AnonFunc1("lookup", this)
}

which we then can use to interact with our deployed canister

val host = "http://localhost:8000"
val keys = EdDSAKeyPair.generateInsecure()
val canisterId = "75hes-oqbaa-aaaaa-aaaaa-aaaaa-aaaaa-aaaaa-q"

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

phonebook.insert("test", "test desc", BigInteger("12345"))
val entry = phonebook.lookup("test")

check(entry != null) { "Entry not found" } 

Pros

  • Idiomatic Kotlin
  • Complete type-safety
  • Asynchronous io with coroutines
  • Reflectionless single-allocation (de)serialization

Cons

  • Unstable

Type conversion rules

IDL Kotlin
type T = "existing type" typealias T = "existing type"
int, nat BigInteger
int8, nat8 Byte
int16, nat16 Short
int32, nat32 Int
int64, nat64 Long
float32 Float
float64 Double
bool Boolean
text String
null Null object
reserved Reserved object
empty Empty object
opt T T?
vec T List<T>
type T = record { a: T1, b: T2 } data class T(val a: T1, val b: T2)
type T = variant { A, B: T1 } sealed class T { data class A: T(); data class B(val value: T1): T() }
type T = func (T1) -> T2 class T { suspend operator fun invoke(arg0: T1): T2 }
type T = service { a: SomeFunc } class T { val a: SomeFunc }
principal Principal class
Unnamed IDL types are transpiled into anonymous Kotlin types.

candid-kt's People

Contributors

seniorjoinu avatar tglaeser avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

candid-kt's Issues

Add retrieveAssets function

The retrieveAsset function is a special function that has the next signature

suspend fun __retrieveAsset(path: String): ByteArray

It is needed to enable a client code to fetch some static resources from a canister (e.g. images, fonts...)
It is done via requesting a canister with a built-in method __dfx_asset_path(path) (every canister has this function by default).
You can find example implementation inside js-user-library/http_agent.js.

Make it compatible with the 0.6.x dfx

While the candid spec didn't changed from 0.5.x there are possible changes in other stuff.

Things that probably could be broken:

  1. Canister ids (and Principals in general) - it seems like they are now longer than they was before
  2. CBOR - Dfinity guys use custom cbor library (they put some custom flags in the resulting bytearray), so it could have changed

Add future types

For now future types are unclear, 'cause from candid spec it seems like values of the future types are packed inside a type definition table and not inside the the values part of the payload.
It is also unclear how can we create a canister that sends us a value of future type to check this.

Needs a further investigation.

Add credentials

Some of IC's networks can be secured by the user/password pair (like Tungsten right now).
Add the ability to use such a pair to access these networks.

Fix the issue with variant/record short field types

As described in #20

variant { A; B; C }

and

type A = int;
type B = int;
type C = int;

record { A; B; C }

In the first example A, B and C are name values of variants. After parsing they go into IDLFieldType(variantName, IDLType.Primitive.Null).
In the second example A, B and C are types of positional record fields. After parsing they go into IDLFieldType(null, recordFieldType).

Which is done incorrect right now.

Add comments to the parser

While parser is working nicely it is still a gray area for newcomers. Would be nice to add some docs in form of high-level comments.

Add imports

For now candid-kt ignores import expressions inside IDL files.
The task is to enable it to automatically transpile imported files into kotlin code.

This is an important feature, so before doing anything, a contributor should figure out a correct architecture solution and share it into this thread. Diagrams are welcome (we can then include them into the docs).

Add value serializers tests

Create a test suite to check if all value serializers (including some generated for records and variants) are working fine.

  1. They produce correctly serialized IDL strings.
  2. They are correctly deserialized back.

Enable tests from `IDLGrammarSpec.'positive single service with parameters #methodName'`

Currently IDLGrammarSpecification.'positive single service with parameters #methodName' is disabled as test are failing since merges for #18.

The string representation of the actual and expected program looks the same:

record {
    preimage: vec nat8;
    image: vec nat8;
};
type List = opt record {
    Key;
    List;
};
type Bucket = List;
type List_2 = opt record {
    text;
    List_2;
};
service : {
    "peers": () -> (List_2);
}

However the test expects field List/List_2 to be of type IDLType.Id while it is in fact of IDLType.Primitive.Null.

The root-cause seems to be related to the IDLGrammar#ids workaround, see discussions on #20 and issue #21.

Add type serializers tests

Create a test suite that checks that all type serializers are working fine.

  1. They are correctly serialized into IDL string.
  2. They are correctly deserialized back

Improve code-generation tests

Something simple would work.

Like, you have a bunch of candid code strings, you generate and compile source code from them. If compilation succeeds - all good.

Make the main actor inherit a name of the file

Right now an actor which describes the canister is transpilled into a "MainActor" class. Make it possible to transpile it into "(Filename)Actor" class.

Also, the word "actor" doesn't fit well. I think "service" would be more suitable.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.