Skip to content

Commit

Permalink
feat: canister call command
Browse files Browse the repository at this point in the history
  • Loading branch information
hansl authored and Hans Larsen committed Sep 20, 2019
1 parent e9c6eab commit 0be1a56
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 10 deletions.
19 changes: 10 additions & 9 deletions dfx/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions dfx/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ console = "0.7.7"
flate2 = "1.0.11"
futures = "0.1.28"
indicatif = "0.12.0"
rand = "0.7.2"
reqwest = "0.9.20"
serde = "1.0"
serde_bytes = "0.11.2"
Expand Down
57 changes: 57 additions & 0 deletions dfx/src/commands/canister/call.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
use crate::lib::api_client::{call, Blob};
use crate::lib::env::ClientEnv;
use crate::lib::error::DfxResult;
use clap::{App, Arg, ArgMatches, SubCommand};
use tokio::runtime::Runtime;

fn is_number(v: String) -> Result<(), String> {
v.parse::<u64>()
.map_err(|_| String::from("The value must be a number."))
.map(|_| ())
}

pub fn construct() -> App<'static, 'static> {
SubCommand::with_name("call")
.about("Call a canister.")
.arg(
Arg::with_name("canister")
.takes_value(true)
.help("The canister ID (a number) to call.")
.required(true)
.validator(is_number),
)
.arg(
Arg::with_name("method_name")
.help("The method name file to use.")
.required(true),
)
.arg(
Arg::with_name("arguments")
.help("Arguments to pass to the method.")
.takes_value(true)
.multiple(true),
)
}

pub fn exec<T>(env: &T, args: &ArgMatches<'_>) -> DfxResult
where
T: ClientEnv,
{
// Read the config.
let canister_id = args.value_of("canister").unwrap().parse::<u64>()?;
let method_name = args.value_of("method_name").unwrap();
let arguments: Option<Vec<&str>> = args.values_of("arguments").map(|args| args.collect());

let client = env.get_client();
let install = call(
client,
canister_id,
method_name.to_owned(),
arguments.map(|args| Blob(Vec::from(args[0]))),
);

let mut runtime = Runtime::new().expect("Unable to create a runtime");
runtime.block_on(install)?;

Ok(())
}
6 changes: 5 additions & 1 deletion dfx/src/commands/canister/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@ use crate::lib::env::{ClientEnv, ProjectConfigEnv};
use crate::lib::error::{DfxError, DfxResult};
use clap::{App, ArgMatches, SubCommand};

mod call;
mod install;

fn builtins<T>() -> Vec<CliCommand<T>>
where
T: ClientEnv + ProjectConfigEnv,
{
vec![CliCommand::new(install::construct(), install::exec)]
vec![
CliCommand::new(install::construct(), install::exec),
CliCommand::new(call::construct(), call::exec),
]
}

pub fn construct<T>() -> App<'static, 'static>
Expand Down
47 changes: 47 additions & 0 deletions dfx/src/lib/api_client.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::lib::error::*;
use futures::future::{err, ok, result, Future};
use futures::stream::Stream;
use rand::Rng;
use reqwest::r#async::Client as ReqwestClient;
use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr};
Expand Down Expand Up @@ -90,9 +91,22 @@ enum SubmitRequest {
canister_id: CanisterId,
module: Blob,
arg: Blob,
nonce: Option<Blob>,
},
Call {
canister_id: CanisterId,
method_name: String,
arg: Blob,
nonce: Option<Blob>,
},
}

/// Generates a random 32 bytes of blob.
fn random_blob() -> Blob {
let mut rng = rand::thread_rng();
Blob(rng.gen::<[u8; 32]>().iter().cloned().collect())
}

/// A read request. Intended to remain private in favor of exposing specialized
/// functions like `query` instead.
fn read<A>(
Expand Down Expand Up @@ -181,6 +195,7 @@ pub fn install_code(
canister_id,
module,
arg: arg.unwrap_or_else(|| Blob(vec![])),
nonce: Some(random_blob()),
},
)
.and_then(|response| {
Expand All @@ -193,6 +208,38 @@ pub fn install_code(
})
}

/// Canister call
///
/// Canister methods that can change the canister state. This return right away, and cannot wait
/// for the canister to be done.
pub fn call(
client: Client,
canister_id: CanisterId,
method_name: String,
arg: Option<Blob>,
) -> impl Future<Item = Option<Blob>, Error = DfxError> {
submit(
client,
SubmitRequest::Call {
canister_id,
method_name,
arg: arg.unwrap_or_else(|| Blob(vec![])),
nonce: Some(random_blob()),
},
)
.and_then(|response| {
result(
response
.error_for_status()
.map(|response| {
eprintln!("{:?}", response);
None
})
.map_err(DfxError::from),
)
})
}

/// A canister query call request payload
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct CanisterQueryCall {
Expand Down

0 comments on commit 0be1a56

Please sign in to comment.