Skip to main content

ic_wasm/check_endpoints/candid/
mod.rs

1use crate::check_endpoints::CanisterEndpoint;
2use anyhow::{format_err, Error, Result};
3use candid::types::{FuncMode, Function, TypeInner};
4use candid_parser::utils::CandidSource;
5use std::borrow::Cow;
6use std::collections::BTreeSet;
7use std::path::Path;
8use std::str;
9use walrus::{IdsToIndices, Module};
10
11pub struct CandidParser<'a> {
12    source: CandidSource<'a>,
13}
14
15impl<'a> From<CandidSource<'a>> for CandidParser<'a> {
16    fn from(source: CandidSource<'a>) -> Self {
17        Self { source }
18    }
19}
20
21impl<'a> CandidParser<'a> {
22    pub fn from_candid_file(path: &'a Path) -> Self {
23        Self::from(CandidSource::File(path))
24    }
25
26    pub fn try_from_wasm(module: &'a Module) -> Result<Option<Self>> {
27        module
28            .customs
29            .iter()
30            .find(|(_, s)| s.name() == "icp:public candid:service")
31            .map(|(_, s)| {
32                let bytes = match s.data(&IdsToIndices::default()) {
33                    Cow::Borrowed(bytes) => bytes,
34                    Cow::Owned(_) => unreachable!(),
35                };
36                let candid = str::from_utf8(bytes).map_err(|e| {
37                    format_err!("Cannot interpret WASM custom section as text: {e:?}")
38                })?;
39                Ok(Self::from(CandidSource::Text(candid)))
40            })
41            .transpose()
42    }
43}
44
45impl CandidParser<'_> {
46    pub fn parse(&self) -> Result<BTreeSet<CanisterEndpoint>> {
47        let (_, top_level) = self.source.load()?;
48
49        let maybe_actor = match top_level {
50            Some(actor) => actor,
51            None => return Err(Error::msg("Top-level definition not found")),
52        };
53
54        let service = match maybe_actor.as_ref() {
55            TypeInner::Class(_, class) => class,
56            service => service,
57        };
58
59        let functions = match service {
60            TypeInner::Service(functions) => functions,
61            _ => return Err(Error::msg("Top-level service definition not found")),
62        };
63
64        let endpoints = functions
65            .iter()
66            .filter_map(|(name, maybe_function)| {
67                if let TypeInner::Func(Function { modes, .. }) = maybe_function.as_ref() {
68                    if modes.contains(&FuncMode::Query) || modes.contains(&FuncMode::CompositeQuery)
69                    {
70                        Some(CanisterEndpoint::Query(name.to_string()))
71                    } else {
72                        Some(CanisterEndpoint::Update(name.to_string()))
73                    }
74                } else {
75                    None
76                }
77            })
78            .collect();
79
80        Ok(endpoints)
81    }
82}