Skip to content

Commit

Permalink
feat!: split candid crate (#471)
Browse files Browse the repository at this point in the history
Currently, the `candid` crate mainly consists of two parts:
* Candid data encoding/decoding
* Parser and derived functionality (bindgen)

The second part evolves fast which forces us to bump major version frequently.
Such version bumps propagated to the downstream projects (agent-rs, cdk-rs, sdk, ic monorepo, etc).

By splitting out `candid_parser`, we expect to have a stable `candid` crate.

Also, I move `Principal` into a separate `ic_principal` crate. Therefore, we can provide a lightweight dependency for those who only need `Principal`.

## Notable changes
* Some private or crate public methods are now public since they are called cross-crate
* Add `parse_idl_args()` and `parse_idl_value()` because in `candid_parser` we cannot implement `FromStr` for `IDLArgs` and `IDLValue` which are defined in `candid`.
* `TypeEnv` is defined in `candid`, so we can't implement type method `ast_to_type` in `candid_parser`. Instead I turned it into a function which takes `TypeEnv` as the first argument. Similarly, `size_helper()` and `size()` are changed.
  • Loading branch information
lwshang authored Oct 23, 2023
1 parent 2308128 commit aef7dcd
Show file tree
Hide file tree
Showing 154 changed files with 1,236 additions and 1,163 deletions.
338 changes: 185 additions & 153 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
[workspace]
members = [
"rust/candid",
"rust/candid_parser",
"rust/candid_derive",
"rust/ic_principal",
"tools/didc",
]
resolver = "2"
Expand Down
7 changes: 7 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@

# Changelog

## Rust 0.10.0

* The original `candid` crate is split into three crates:
* `candid`: mainly for Candid data (de-)serialization.
* `candid_parser`: used to be the `parser` and `bindings` module in `candid` crate.
* `ic_principal`: only for `Principal` and `PrincipalError`.

## Rust 0.9.9

* Set different config values for `full_error_message` and `zero_sized_values` for Wasm and non-Wasm target.
Expand Down
59 changes: 5 additions & 54 deletions rust/candid/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "candid"
version = "0.9.11"
version = "0.10.0"
edition = "2021"
authors = ["DFINITY Team"]
description = "Candid is an interface description language (IDL) for interacting with canisters running on the Internet Computer."
Expand All @@ -9,22 +9,16 @@ documentation = "https://docs.rs/candid"
repository = "https://github.com/dfinity/candid"
license = "Apache-2.0"
readme = "README.md"

categories = ["encoding", "parsing", "wasm"]
keywords = ["internet-computer", "idl", "candid", "dfinity", "parser"]
include = ["src", "Cargo.toml", "build.rs", "LICENSE", "README.md"]
build = "build.rs"

[build-dependencies]
lalrpop = { version = "0.20.0", optional = true }
categories = ["encoding", "wasm"]
keywords = ["internet-computer", "idl", "candid", "dfinity"]
include = ["src", "Cargo.toml", "LICENSE", "README.md"]

[dependencies]
byteorder = "1.4.3"
candid_derive = { path = "../candid_derive", version = "=0.6.4" }
codespan-reporting = "0.11"
crc32fast = "1.3.0"
data-encoding = "2.4.0"
hex = "0.4.2"
ic_principal = { path = "../ic_principal", version = "0.1.0" }
leb128 = "0.2.4"
num_enum = "0.6.1"
num-bigint = { version = "0.4.2", features = ["serde"] }
Expand All @@ -33,67 +27,24 @@ paste = "1.0.0"
pretty = "0.12.0"
serde = { version = "1.0.118", features = ["derive"] }
serde_bytes = "0.11"
sha2 = "0.10.1"
thiserror = "1.0.20"
anyhow = "1.0"
binread = { version = "2.1", features = ["debug_template"] }

lalrpop-util = { version = "0.20.0", optional = true }
logos = { version = "0.13", optional = true }
convert_case = { version = "0.6", optional = true }

arbitrary = { version = "1.0", optional = true }
# Don't upgrade serde_dhall. It will introduce dependency with invalid license.
serde_dhall = { version = "0.11", default-features = false, optional = true }
fake = { version = "2.4", optional = true }
rand = { version = "0.8", optional = true }

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
stacker = "0.1"

[dev-dependencies]
goldenfile = "1.1.0"
test-generator = "0.3.0"
rand = "0.8"
criterion = "0.4"
serde_cbor = "0.11.2"
serde_json = "1.0.74"
serde_test = "1.0.137"
impls = "1"
bincode = "1.3.3"

[[bench]]
name = "benchmark"
harness = false
path = "benches/benchmark.rs"

[[test]]
name = "test_suite"
path = "tests/test_suite.rs"
required-features = ["parser"]
[[test]]
name = "value"
path = "tests/value.rs"
required-features = ["parser"]
[[test]]
name = "parse_value"
path = "tests/parse_value.rs"
required-features = ["parser"]
[[test]]
name = "parse_type"
path = "tests/parse_type.rs"
required-features = ["parser"]

[features]
configs = ["serde_dhall"]
random = ["parser", "configs", "arbitrary", "fake", "rand"]
parser = ["lalrpop", "lalrpop-util", "logos", "convert_case"]
all = ["random"]
mute_warnings = []

# docs.rs-specific configuration
# To test locally: RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --features all
[package.metadata.docs.rs]
features = ["all"]
# defines the configuration attribute `docsrs`
rustdoc-args = ["--cfg", "docsrs"]
20 changes: 0 additions & 20 deletions rust/candid/src/bindings/mod.rs

This file was deleted.

125 changes: 4 additions & 121 deletions rust/candid/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,13 @@

use codespan_reporting::diagnostic::Label;
use serde::{de, ser};
use std::io;
use std::{io, num::ParseIntError};
use thiserror::Error;

#[cfg(feature = "parser")]
use crate::parser::token;
#[cfg(feature = "parser")]
use codespan_reporting::{
diagnostic::Diagnostic,
files::{Error as ReportError, SimpleFile},
term::{self, termcolor::StandardStream},
};

pub type Result<T = ()> = std::result::Result<T, Error>;

#[derive(Debug, Error)]
pub enum Error {
#[cfg_attr(docsrs, doc(cfg(feature = "parser")))]
#[cfg(feature = "parser")]
#[error("Candid parser error: {0}")]
Parse(#[from] token::ParserError),

#[error("binary parser error: {}", .0.get(0).map(|f| format!("{} at byte offset {}", f.message, f.range.start/2)).unwrap_or_else(|| "io error".to_string()))]
Binread(Vec<Label<()>>),

Expand All @@ -40,42 +26,6 @@ impl Error {
pub fn subtype<T: ToString>(msg: T) -> Self {
Error::Subtype(msg.to_string())
}
#[cfg_attr(docsrs, doc(cfg(feature = "parser")))]
#[cfg(feature = "parser")]
pub fn report(&self) -> Diagnostic<()> {
match self {
Error::Parse(e) => {
use lalrpop_util::ParseError::*;
let mut diag = Diagnostic::error().with_message("parser error");
let label = match e {
User { error } => {
Label::primary((), error.span.clone()).with_message(&error.err)
}
InvalidToken { location } => {
Label::primary((), *location..location + 1).with_message("Invalid token")
}
UnrecognizedEof { location, expected } => {
diag = diag.with_notes(report_expected(expected));
Label::primary((), *location..location + 1).with_message("Unexpected EOF")
}
UnrecognizedToken { token, expected } => {
diag = diag.with_notes(report_expected(expected));
Label::primary((), token.0..token.2).with_message("Unexpected token")
}
ExtraToken { token } => {
Label::primary((), token.0..token.2).with_message("Extra token")
}
};
diag.with_labels(vec![label])
}
Error::Binread(labels) => {
let diag = Diagnostic::error().with_message("decoding error");
diag.with_labels(labels.to_vec())
}
Error::Subtype(e) => Diagnostic::error().with_message(e),
Error::Custom(e) => Diagnostic::error().with_message(e.to_string()),
}
}
}

fn get_binread_labels(e: &binread::Error) -> Vec<Label<()>> {
Expand Down Expand Up @@ -123,26 +73,6 @@ fn get_binread_labels(e: &binread::Error) -> Vec<Label<()>> {
}
}

#[cfg_attr(docsrs, doc(cfg(feature = "parser")))]
#[cfg(feature = "parser")]
fn report_expected(expected: &[String]) -> Vec<String> {
if expected.is_empty() {
return Vec::new();
}
use pretty::RcDoc;
let doc: RcDoc<()> = RcDoc::intersperse(
expected.iter().map(RcDoc::text),
RcDoc::text(",").append(RcDoc::softline()),
);
let header = if expected.len() == 1 {
"Expects"
} else {
"Expects one of"
};
let doc = RcDoc::text(header).append(RcDoc::softline().append(doc));
vec![doc.pretty(70).to_string()]
}

impl ser::Error for Error {
fn custom<T: std::fmt::Display>(msg: T) -> Self {
Error::msg(format!("Serialize error: {msg}"))
Expand Down Expand Up @@ -174,56 +104,9 @@ impl From<binread::Error> for Error {
Error::Binread(get_binread_labels(&e))
}
}
#[cfg_attr(docsrs, doc(cfg(feature = "parser")))]
#[cfg(feature = "parser")]
impl From<ReportError> for Error {
fn from(e: ReportError) -> Error {
Error::msg(e)
}
}
#[cfg_attr(docsrs, doc(cfg(feature = "random")))]
#[cfg(feature = "random")]
impl From<arbitrary::Error> for Error {
fn from(e: arbitrary::Error) -> Error {
Error::msg(format!("arbitrary error: {e}"))
}
}

#[cfg_attr(docsrs, doc(cfg(feature = "configs")))]
#[cfg(feature = "configs")]
impl From<serde_dhall::Error> for Error {
fn from(e: serde_dhall::Error) -> Error {
Error::msg(format!("dhall error: {e}"))
impl From<ParseIntError> for Error {
fn from(e: ParseIntError) -> Error {
Error::msg(format!("ParseIntError: {e}"))
}
}

#[cfg_attr(docsrs, doc(cfg(feature = "parser")))]
#[cfg(feature = "parser")]
pub fn pretty_parse<T>(name: &str, str: &str) -> Result<T>
where
T: std::str::FromStr<Err = Error>,
{
str.parse::<T>().or_else(|e| {
let writer = StandardStream::stderr(term::termcolor::ColorChoice::Auto);
let config = term::Config::default();
let file = SimpleFile::new(name, str);
term::emit(&mut writer.lock(), &config, &file, &e.report())?;
Err(e)
})
}
#[cfg_attr(docsrs, doc(cfg(feature = "parser")))]
#[cfg(feature = "parser")]
pub fn pretty_read<T>(reader: &mut std::io::Cursor<&[u8]>) -> Result<T>
where
T: binread::BinRead,
{
T::read(reader).or_else(|e| {
let e = Error::from(e);
let writer = StandardStream::stderr(term::termcolor::ColorChoice::Auto);
let config = term::Config::default();
let str = hex::encode(reader.get_ref());
let file = SimpleFile::new("binary", &str);
term::emit(&mut writer.lock(), &config, &file, &e.report())?;
Err(e)
})
}
Loading

0 comments on commit aef7dcd

Please sign in to comment.