#![no_std]
extern crate alloc;
pub mod codec;
mod hash;
mod interface_id;
use alloc::{
boxed::Box,
format,
string::{String, ToString as _},
vec,
vec::Vec,
};
use core::{
fmt::{Display, Write},
str::FromStr,
};
pub use interface_id::InterfaceId;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
pub type Annotation = (String, Option<String>);
#[derive(Debug, Default, Clone, PartialEq)]
#[cfg_attr(
feature = "templates",
derive(askama::Template),
template(path = "idl.askama", escape = "none")
)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct IdlDoc {
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Vec::is_empty")
)]
pub globals: Vec<Annotation>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub program: Option<ProgramUnit>,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Vec::is_empty")
)]
pub services: Vec<ServiceUnit>,
}
impl IdlDoc {
#[cfg(feature = "serde")]
pub fn to_json_string(&self) -> Result<String, serde_json::Error> {
serde_json::to_string(self)
}
#[cfg(feature = "serde")]
pub fn to_json_string_pretty(&self) -> Result<String, serde_json::Error> {
serde_json::to_string_pretty(self)
}
}
#[derive(Debug, Default, Clone, PartialEq)]
#[cfg_attr(
feature = "templates",
derive(askama::Template),
template(path = "program.askama", escape = "none")
)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ProgramUnit {
pub name: String,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Vec::is_empty")
)]
pub ctors: Vec<CtorFunc>,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Vec::is_empty")
)]
pub services: Vec<ServiceExpo>,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Vec::is_empty")
)]
pub types: Vec<Type>,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Vec::is_empty")
)]
pub docs: Vec<String>,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Vec::is_empty")
)]
pub annotations: Vec<Annotation>,
}
impl ProgramUnit {
pub fn normalize(&mut self) {
for (idx, ctor) in self.ctors.iter_mut().enumerate() {
ctor.entry_id = entry_id_from_annotations(&ctor.annotations, idx as u16);
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ServiceIdent {
pub name: String,
#[cfg_attr(
feature = "serde",
serde(
default,
skip_serializing_if = "Option::is_none",
with = "serde_opt_str"
)
)]
pub interface_id: Option<InterfaceId>,
}
impl Display for ServiceIdent {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(&self.name)?;
if let Some(id) = self.interface_id {
f.write_str("@")?;
id.fmt(f)?;
}
Ok(())
}
}
impl FromStr for ServiceIdent {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (name, interface_id) = match s.split_once('@') {
None => (s.trim().to_string(), None),
Some((name, id_str)) => {
if name.is_empty() {
return Err("name is empty".to_string());
}
if id_str.is_empty() {
return Err("interface_id is empty".to_string());
}
let id = id_str.trim().parse::<InterfaceId>()?;
(name.trim().to_string(), Some(id))
}
};
Ok(ServiceIdent { name, interface_id })
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ServiceExpo {
#[cfg_attr(feature = "serde", serde(flatten))]
pub name: ServiceIdent,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub route: Option<String>,
pub route_idx: u8,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Vec::is_empty")
)]
pub docs: Vec<String>,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Vec::is_empty")
)]
pub annotations: Vec<Annotation>,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct CtorFunc {
pub name: String,
#[cfg_attr(feature = "serde", serde(default))]
pub params: Vec<FuncParam>,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Option::is_none")
)]
pub throws: Option<TypeDecl>,
#[cfg_attr(feature = "serde", serde(default))]
pub entry_id: u16,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Vec::is_empty")
)]
pub docs: Vec<String>,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Vec::is_empty")
)]
pub annotations: Vec<Annotation>,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(
feature = "templates",
derive(askama::Template),
template(path = "service.askama", escape = "none")
)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ServiceUnit {
#[cfg_attr(feature = "serde", serde(flatten))]
pub name: ServiceIdent,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Vec::is_empty")
)]
pub extends: Vec<ServiceIdent>,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Vec::is_empty")
)]
pub funcs: Vec<ServiceFunc>,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Vec::is_empty")
)]
pub events: Vec<ServiceEvent>,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Vec::is_empty")
)]
pub types: Vec<Type>,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Vec::is_empty")
)]
pub docs: Vec<String>,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Vec::is_empty")
)]
pub annotations: Vec<Annotation>,
}
impl ServiceUnit {
pub fn normalize(&mut self) {
self.events.sort_by_key(|e| e.name.to_lowercase());
self.funcs.sort_by_key(|f| f.name.to_lowercase());
self.extends.sort_by_key(|e| e.name.to_lowercase());
for (idx, func) in self.funcs.iter_mut().enumerate() {
func.entry_id = entry_id_from_annotations(&func.annotations, idx as u16);
}
for (idx, event) in self.events.iter_mut().enumerate() {
event.entry_id = entry_id_from_annotations(&event.annotations, idx as u16);
}
}
pub fn is_partial(&self) -> bool {
self.annotations.iter().any(|(k, _)| k == "partial")
}
}
fn entry_id_from_annotations(annotations: &[(String, Option<String>)], fallback: u16) -> u16 {
annotations
.iter()
.find(|(k, _)| k == "entry_id")
.and_then(|(_, v)| v.as_ref()?.parse::<u16>().ok())
.unwrap_or(fallback)
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ServiceFunc {
pub name: String,
#[cfg_attr(feature = "serde", serde(default))]
pub params: Vec<FuncParam>,
pub output: TypeDecl,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub throws: Option<TypeDecl>,
pub kind: FunctionKind,
#[cfg_attr(feature = "serde", serde(default))]
pub entry_id: u16,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Vec::is_empty")
)]
pub docs: Vec<String>,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Vec::is_empty")
)]
pub annotations: Vec<Annotation>,
}
#[derive(Debug, Default, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(rename_all = "lowercase")
)]
pub enum FunctionKind {
#[default]
Command,
Query,
}
impl ServiceFunc {
pub fn returns_void(&self) -> bool {
use PrimitiveType::*;
use TypeDecl::*;
self.output == Primitive(Void)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct FuncParam {
pub name: String,
#[cfg_attr(feature = "serde", serde(rename = "type"))]
pub type_decl: TypeDecl,
}
impl Display for FuncParam {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let FuncParam { name, type_decl } = self;
write!(f, "{name}: {type_decl}")
}
}
pub type ServiceEvent = EnumVariant;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(rename_all = "lowercase", tag = "kind")
)]
pub enum TypeDecl {
Slice { item: Box<TypeDecl> },
Array { item: Box<TypeDecl>, len: u32 },
Tuple { types: Vec<TypeDecl> },
Generic { name: String },
Named {
name: String,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Vec::is_empty")
)]
generics: Vec<TypeDecl>,
},
#[cfg_attr(feature = "serde", serde(untagged))]
Primitive(#[cfg_attr(feature = "serde", serde(with = "serde_str"))] PrimitiveType),
}
impl TypeDecl {
pub fn named(name: impl Into<String>) -> TypeDecl {
Self::named_with_generics(name, vec![])
}
pub fn named_with_generics(name: impl Into<String>, generics: Vec<TypeDecl>) -> TypeDecl {
TypeDecl::Named {
name: name.into(),
generics,
}
}
pub fn generic(name: impl Into<String>) -> TypeDecl {
TypeDecl::Generic { name: name.into() }
}
pub fn tuple(types: Vec<TypeDecl>) -> TypeDecl {
TypeDecl::Tuple { types }
}
pub fn option(item: TypeDecl) -> TypeDecl {
TypeDecl::Named {
name: "Option".to_string(),
generics: vec![item],
}
}
pub fn result(ok: TypeDecl, err: TypeDecl) -> TypeDecl {
TypeDecl::Named {
name: "Result".to_string(),
generics: vec![ok, err],
}
}
pub fn option_type_decl(ty: &TypeDecl) -> Option<TypeDecl> {
match ty {
TypeDecl::Named { name, generics } if name == "Option" => {
if let [item] = generics.as_slice() {
Some(item.clone())
} else {
None
}
}
_ => None,
}
}
pub fn result_type_decl(ty: &TypeDecl) -> Option<(TypeDecl, TypeDecl)> {
match ty {
TypeDecl::Named { name, generics } if name == "Result" => {
if let [ok, err] = generics.as_slice() {
Some((ok.clone(), err.clone()))
} else {
None
}
}
_ => None,
}
}
#[cfg(feature = "serde")]
pub fn to_json_string(&self) -> Result<String, serde_json::Error> {
serde_json::to_string(self)
}
#[cfg(feature = "serde")]
pub fn to_json_string_pretty(&self) -> Result<String, serde_json::Error> {
serde_json::to_string_pretty(self)
}
}
impl Display for TypeDecl {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
use TypeDecl::*;
match self {
Slice { item } => write!(f, "[{item}]"),
Array { item, len } => write!(f, "[{item}; {len}]"),
Tuple { types } => {
f.write_char('(')?;
for (i, ty) in types.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{ty}")?;
}
f.write_char(')')?;
Ok(())
}
Generic { name } => f.write_str(name),
Named { name, generics } => {
write!(f, "{name}")?;
if !generics.is_empty() {
f.write_char('<')?;
for (i, g) in generics.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{g}")?;
}
f.write_char('>')?;
}
Ok(())
}
Primitive(primitive_type) => write!(f, "{primitive_type}"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum PrimitiveType {
Void,
Bool,
Char,
String,
U8,
U16,
U32,
U64,
U128,
I8,
I16,
I32,
I64,
I128,
ActorId,
CodeId,
MessageId,
H160,
H256,
U256,
}
impl PrimitiveType {
pub const fn as_str(&self) -> &'static str {
use PrimitiveType::*;
match self {
Void => "()",
Bool => "bool",
Char => "char",
String => "String",
U8 => "u8",
U16 => "u16",
U32 => "u32",
U64 => "u64",
U128 => "u128",
I8 => "i8",
I16 => "i16",
I32 => "i32",
I64 => "i64",
I128 => "i128",
ActorId => "ActorId",
CodeId => "CodeId",
MessageId => "MessageId",
H160 => "H160",
H256 => "H256",
U256 => "U256",
}
}
}
impl Display for PrimitiveType {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(self.as_str())
}
}
impl core::str::FromStr for PrimitiveType {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
use PrimitiveType::*;
match s {
"()" => Ok(Void),
"bool" => Ok(Bool),
"char" => Ok(Char),
"String" | "string" => Ok(String),
"u8" => Ok(U8),
"u16" => Ok(U16),
"u32" => Ok(U32),
"u64" => Ok(U64),
"u128" => Ok(U128),
"i8" => Ok(I8),
"i16" => Ok(I16),
"i32" => Ok(I32),
"i64" => Ok(I64),
"i128" => Ok(I128),
"ActorId" | "actor" | "actor_id" => Ok(ActorId),
"CodeId" | "code" | "code_id" => Ok(CodeId),
"MessageId" | "messageid" | "message_id" => Ok(MessageId),
"H256" | "h256" => Ok(H256),
"U256" | "u256" => Ok(U256),
"H160" | "h160" => Ok(H160),
other => Err(format!("Unknown primitive type: {other}")),
}
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(
feature = "templates",
derive(askama::Template),
template(path = "type.askama", escape = "none")
)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Type {
pub name: String,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Vec::is_empty")
)]
pub type_params: Vec<TypeParameter>,
#[cfg_attr(feature = "serde", serde(flatten))]
pub def: TypeDef,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Vec::is_empty")
)]
pub docs: Vec<String>,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Vec::is_empty")
)]
pub annotations: Vec<Annotation>,
}
impl Type {
#[cfg(feature = "serde")]
pub fn to_json_string(&self) -> Result<String, serde_json::Error> {
serde_json::to_string(self)
}
#[cfg(feature = "serde")]
pub fn to_json_string_pretty(&self) -> Result<String, serde_json::Error> {
serde_json::to_string_pretty(self)
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct TypeParameter {
pub name: String,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub ty: Option<TypeDecl>,
}
impl Display for TypeParameter {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let TypeParameter { name, ty: _ } = self;
write!(f, "{name}")
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(rename_all = "lowercase", tag = "kind")
)]
pub enum TypeDef {
Struct(StructDef),
Enum(EnumDef),
Alias(AliasDef),
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(
feature = "templates",
derive(askama::Template),
template(path = "struct_def.askama", escape = "none")
)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct StructDef {
#[cfg_attr(feature = "serde", serde(default))]
pub fields: Vec<StructField>,
}
impl StructDef {
pub fn is_unit(&self) -> bool {
self.fields.is_empty()
}
pub fn is_inline(&self) -> bool {
self.fields
.iter()
.all(|f| f.name.is_none() && f.docs.is_empty() && f.annotations.is_empty())
}
pub fn is_tuple(&self) -> bool {
self.fields.iter().all(|f| f.name.is_none())
}
#[cfg(feature = "serde")]
pub fn to_json_string(&self) -> Result<String, serde_json::Error> {
serde_json::to_string(self)
}
#[cfg(feature = "serde")]
pub fn to_json_string_pretty(&self) -> Result<String, serde_json::Error> {
serde_json::to_string_pretty(self)
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(
feature = "templates",
derive(askama::Template),
template(path = "field.askama", escape = "none")
)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct StructField {
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub name: Option<String>,
#[cfg_attr(feature = "serde", serde(rename = "type"))]
pub type_decl: TypeDecl,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Vec::is_empty")
)]
pub docs: Vec<String>,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Vec::is_empty")
)]
pub annotations: Vec<Annotation>,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct EnumDef {
#[cfg_attr(feature = "serde", serde(default))]
pub variants: Vec<EnumVariant>,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(
feature = "templates",
derive(askama::Template),
template(path = "variant.askama", escape = "none")
)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct EnumVariant {
pub name: String,
#[cfg_attr(feature = "serde", serde(flatten))]
pub def: StructDef,
#[cfg_attr(feature = "serde", serde(default))]
pub entry_id: u16,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Vec::is_empty")
)]
pub docs: Vec<String>,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Vec::is_empty")
)]
pub annotations: Vec<Annotation>,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct AliasDef {
pub target: TypeDecl,
}
#[cfg(feature = "serde")]
mod serde_str {
use super::*;
use core::str::FromStr;
use serde::{Deserializer, Serializer};
pub(super) fn serialize<T, S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
where
T: Display,
S: Serializer,
{
serializer.collect_str(value)
}
pub(super) fn deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error>
where
T: FromStr,
<T as FromStr>::Err: Display,
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
T::from_str(s.as_str()).map_err(serde::de::Error::custom)
}
}
#[cfg(feature = "serde")]
mod serde_opt_str {
use super::*;
use core::str::FromStr;
use serde::{Deserializer, Serializer};
pub(super) fn serialize<T, S>(value: &Option<T>, serializer: S) -> Result<S::Ok, S::Error>
where
T: Display,
S: Serializer,
{
match value {
Some(value) => serializer.collect_str(value),
None => serializer.serialize_none(),
}
}
pub(super) fn deserialize<'de, T, D>(deserializer: D) -> Result<Option<T>, D::Error>
where
T: FromStr,
<T as FromStr>::Err: Display,
D: Deserializer<'de>,
{
let opt = Option::<String>::deserialize(deserializer)?;
opt.map(|s| T::from_str(s.as_str()).map_err(serde::de::Error::custom))
.transpose()
}
}