ic_wasm/check_endpoints/candid/
mod.rs1use 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}