ic-wasm 0.9.10

A library for performing Wasm transformations specific to canisters running on the Internet Computer
Documentation
use crate::check_endpoints::CanisterEndpoint;
use anyhow::{format_err, Error, Result};
use candid::types::{FuncMode, Function, TypeInner};
use candid_parser::utils::CandidSource;
use std::borrow::Cow;
use std::collections::BTreeSet;
use std::path::Path;
use std::str;
use walrus::{IdsToIndices, Module};

pub struct CandidParser<'a> {
    source: CandidSource<'a>,
}

impl<'a> From<CandidSource<'a>> for CandidParser<'a> {
    fn from(source: CandidSource<'a>) -> Self {
        Self { source }
    }
}

impl<'a> CandidParser<'a> {
    pub fn from_candid_file(path: &'a Path) -> Self {
        Self::from(CandidSource::File(path))
    }

    pub fn try_from_wasm(module: &'a Module) -> Result<Option<Self>> {
        module
            .customs
            .iter()
            .find(|(_, s)| s.name() == "icp:public candid:service")
            .map(|(_, s)| {
                let bytes = match s.data(&IdsToIndices::default()) {
                    Cow::Borrowed(bytes) => bytes,
                    Cow::Owned(_) => unreachable!(),
                };
                let candid = str::from_utf8(bytes).map_err(|e| {
                    format_err!("Cannot interpret WASM custom section as text: {e:?}")
                })?;
                Ok(Self::from(CandidSource::Text(candid)))
            })
            .transpose()
    }
}

impl CandidParser<'_> {
    pub fn parse(&self) -> Result<BTreeSet<CanisterEndpoint>> {
        let (_, top_level) = self.source.load()?;

        let maybe_actor = match top_level {
            Some(actor) => actor,
            None => return Err(Error::msg("Top-level definition not found")),
        };

        let service = match maybe_actor.as_ref() {
            TypeInner::Class(_, class) => class,
            service => service,
        };

        let functions = match service {
            TypeInner::Service(functions) => functions,
            _ => return Err(Error::msg("Top-level service definition not found")),
        };

        let endpoints = functions
            .iter()
            .filter_map(|(name, maybe_function)| {
                if let TypeInner::Func(Function { modes, .. }) = maybe_function.as_ref() {
                    if modes.contains(&FuncMode::Query) || modes.contains(&FuncMode::CompositeQuery)
                    {
                        Some(CanisterEndpoint::Query(name.to_string()))
                    } else {
                        Some(CanisterEndpoint::Update(name.to_string()))
                    }
                } else {
                    None
                }
            })
            .collect();

        Ok(endpoints)
    }
}