Skip to main content

ic_canister_kit/candid/
types.rs

1use std::collections::HashMap;
2
3use candid::CandidType;
4use serde::{Deserialize, Serialize};
5
6/// 有的名字作为 key 需要加双引号
7fn wrapped_key_word(name: &str) -> String {
8    if match name {
9        "bool" => true,
10        "nat" => true,
11        "int" => true,
12        "nat8" => true,
13        "nat16" => true,
14        "nat32" => true,
15        "nat64" => true,
16        "int8" => true,
17        "int16" => true,
18        "int32" => true,
19        "int64" => true,
20        "float32" => true,
21        "float64" => true,
22        "null" => true,
23        "text" => true,
24        "principal" => true,
25        "vec" => true,
26        "opt" => true,
27        "record" => true,
28        "variant" => true,
29        // "tuple" => true, // 不是关键字
30        "unknown" => true,
31        "empty" => true,
32        "reserved" => true,
33        "func" => true,
34        "service" => true,
35        "rec" => true, // 可能是关键字
36        _ => false,
37    } || name.contains(' ')
38        || name.contains('-')
39        || name.contains('\\')
40    {
41        format!("\"{}\"", name)
42    } else {
43        name.to_string()
44    }
45}
46
47/// 有可能有名称
48#[derive(Debug, Clone, CandidType, Serialize, Deserialize, Eq, PartialEq, Default)]
49pub struct WrappedCandidTypeName {
50    /// 有时候有名字
51    #[serde(rename = "name", skip_serializing_if = "Option::is_none")]
52    pub name: Option<String>,
53}
54
55impl WrappedCandidTypeName {
56    pub(super) fn from(name: Option<String>) -> Self {
57        Self { name }
58    }
59}
60
61/// 有可能有名称
62#[derive(Debug, Clone, CandidType, Serialize, Deserialize, Eq, PartialEq)]
63pub struct WrappedCandidTypeSubtype {
64    /// 子类型
65    #[serde(rename = "subtype")]
66    pub subtype: Box<WrappedCandidType>,
67
68    /// 有时候有名字
69    #[serde(rename = "name", skip_serializing_if = "Option::is_none")]
70    pub name: Option<String>,
71}
72
73/// 有可能有名称
74#[derive(Debug, Clone, CandidType, Serialize, Deserialize, Eq, PartialEq)]
75pub struct WrappedCandidTypeRecord {
76    /// 子类型
77    #[serde(rename = "subitems")]
78    pub subitems: Vec<(String, WrappedCandidType)>,
79
80    /// 有时候有名字
81    #[serde(rename = "name", skip_serializing_if = "Option::is_none")]
82    pub name: Option<String>,
83}
84
85impl WrappedCandidTypeRecord {
86    /// 文本
87    pub fn to_text(&self) -> String {
88        let Self { subitems, .. } = self;
89
90        if subitems.is_empty() {
91            return "record {}".to_string();
92        }
93
94        format!(
95            "record {{ {} }}",
96            subitems
97                .iter()
98                .map(|(name, subtype)| format!("{} : {}", wrapped_key_word(name), subtype.to_text()))
99                .collect::<Vec<_>>()
100                .join("; ")
101        )
102    }
103}
104
105/// 有可能有名称
106#[derive(Debug, Clone, CandidType, Serialize, Deserialize, Eq, PartialEq)]
107pub struct WrappedCandidTypeVariant {
108    /// 子类型
109    #[serde(rename = "subitems")]
110    pub subitems: Vec<(String, Option<WrappedCandidType>)>,
111
112    /// 有时候有名字
113    #[serde(rename = "name", skip_serializing_if = "Option::is_none")]
114    pub name: Option<String>,
115}
116
117impl WrappedCandidTypeVariant {
118    /// 文本
119    pub fn to_text(&self) -> String {
120        let Self { subitems, .. } = self;
121
122        if subitems.is_empty() {
123            return "variant {}".to_string();
124        }
125
126        format!(
127            "variant {{ {} }}",
128            subitems
129                .iter()
130                .map(|(name, subtype)| {
131                    if let Some(subtype) = subtype {
132                        format!("{} : {}", wrapped_key_word(name), subtype.to_text())
133                    } else {
134                        wrapped_key_word(name)
135                    }
136                })
137                .collect::<Vec<_>>()
138                .join("; ")
139        )
140    }
141}
142
143/// 有可能有名称
144#[derive(Debug, Clone, CandidType, Serialize, Deserialize, Eq, PartialEq)]
145pub struct WrappedCandidTypeTuple {
146    /// 子类型
147    #[serde(rename = "subitems")]
148    pub subitems: Vec<WrappedCandidType>,
149
150    /// 有时候有名字
151    #[serde(rename = "name", skip_serializing_if = "Option::is_none")]
152    pub name: Option<String>,
153}
154
155impl WrappedCandidTypeTuple {
156    /// 文本
157    pub fn to_text(&self) -> String {
158        let Self { subitems, .. } = self;
159
160        if subitems.is_empty() {
161            return "record {}".to_string();
162        }
163
164        format!(
165            "record {{ {} }}",
166            subitems
167                .iter()
168                .map(|subtype| subtype.to_text())
169                .collect::<Vec<_>>()
170                .join("; ")
171        )
172    }
173}
174
175/// 函数的注解 update 无注解
176#[derive(Debug, Copy, Clone, CandidType, Serialize, Deserialize, Eq, PartialEq)]
177pub enum FunctionAnnotation {
178    /// 查询函数, 可以用查询机制简化消耗的 cycles
179    #[serde(rename = "query")]
180    Query,
181    /// 复合查询函数
182    #[serde(rename = "composite_query")]
183    CompositeQuery,
184    /// 用于不关心返回值的函数, 触发即忘场景
185    #[serde(rename = "oneway")]
186    Oneway,
187}
188
189/// 函数结构体
190#[derive(Debug, Clone, CandidType, Serialize, Deserialize, Eq, PartialEq)]
191pub struct WrappedCandidTypeFunction {
192    /// args
193    #[serde(rename = "args", skip_serializing_if = "Vec::is_empty", default = "Vec::new")]
194    pub args: Vec<WrappedCandidType>,
195    /// results
196    #[serde(rename = "rets", skip_serializing_if = "Vec::is_empty", default = "Vec::new")]
197    pub rets: Vec<WrappedCandidType>,
198    /// annotation update query
199    #[serde(rename = "annotation", skip_serializing_if = "Option::is_none")]
200    pub annotation: Option<FunctionAnnotation>,
201
202    /// 有时候有名字
203    #[serde(rename = "name", skip_serializing_if = "Option::is_none")]
204    pub name: Option<String>,
205}
206
207impl WrappedCandidTypeFunction {
208    /// 文本
209    pub fn to_text(&self) -> String {
210        let Self {
211            args, rets, annotation, ..
212        } = self;
213
214        format!(
215            "func ({}) -> ({}){}",
216            args.iter().map(|t| t.to_text()).collect::<Vec<_>>().join(", "),
217            rets.iter().map(|t| t.to_text()).collect::<Vec<_>>().join(", "),
218            match annotation.as_ref() {
219                Some(annotation) => match annotation {
220                    FunctionAnnotation::Query => " query",
221                    FunctionAnnotation::CompositeQuery => " composite_query",
222                    FunctionAnnotation::Oneway => " oneway",
223                },
224                None => "",
225            }
226        )
227    }
228}
229
230/// service 结构体
231#[derive(Debug, Clone, CandidType, Serialize, Deserialize, Eq, PartialEq)]
232pub struct WrappedCandidTypeService {
233    /// args
234    #[serde(rename = "args", skip_serializing_if = "Vec::is_empty", default = "Vec::new")]
235    pub args: Vec<WrappedCandidType>,
236    /// methods
237    #[serde(rename = "methods", skip_serializing_if = "Vec::is_empty", default = "Vec::new")]
238    pub methods: Vec<(String, WrappedCandidTypeFunction)>,
239
240    /// 有时候有名字
241    #[serde(rename = "name", skip_serializing_if = "Option::is_none")]
242    pub name: Option<String>,
243}
244
245impl WrappedCandidTypeService {
246    /// 文本
247    pub fn to_text(&self) -> String {
248        let Self { args, methods, .. } = self;
249
250        format!(
251            "service :{} {{\n{}\n}}",
252            if args.is_empty() {
253                "".to_string()
254            } else {
255                format!(
256                    " ({}) ->",
257                    args.iter().map(|t| t.to_text()).collect::<Vec<_>>().join(", ")
258                )
259            },
260            if methods.is_empty() {
261                "".to_string()
262            } else {
263                format!(
264                    "\n{}\n",
265                    methods
266                        .iter()
267                        .map(|(name, func)| format!(
268                            "    {} : {};",
269                            wrapped_key_word(name),
270                            func.to_text().trim_start_matches("func ")
271                        ))
272                        .collect::<Vec<_>>()
273                        .join("\n")
274                )
275            }
276        )
277    }
278
279    /// 转化为方法
280    pub fn to_methods(&self) -> HashMap<String, String> {
281        self.methods
282            .iter()
283            .map(|(method, candid)| {
284                (method.to_string(), {
285                    let func = candid.to_text();
286                    if let Some(func) = func.strip_prefix("func ") {
287                        func.to_string()
288                    } else {
289                        func
290                    }
291                })
292            })
293            .collect()
294    }
295}
296
297/// 循环 结构体
298#[derive(Debug, Clone, CandidType, Serialize, Deserialize, Eq, PartialEq)]
299pub struct WrappedCandidTypeRecursion {
300    ///  type
301    #[serde(rename = "ty")]
302    pub ty: Box<WrappedCandidType>,
303    /// 分配的序号
304    #[serde(rename = "id")]
305    pub id: u32,
306
307    /// 有时候有名字
308    #[serde(rename = "name", skip_serializing_if = "Option::is_none")]
309    pub name: Option<String>,
310}
311
312impl WrappedCandidTypeRecursion {
313    /// 文本
314    pub fn to_text(&self) -> String {
315        let Self { ty, id, .. } = self;
316
317        format!("μrec_{}.{}", id, ty.to_text())
318    }
319}
320
321/// 循环 结构体
322#[derive(Debug, Clone, CandidType, Serialize, Deserialize, Eq, PartialEq)]
323pub struct WrappedCandidTypeReference {
324    /// 分配的序号
325    #[serde(rename = "id")]
326    pub id: u32,
327
328    /// 有时候有名字
329    #[serde(rename = "name", skip_serializing_if = "Option::is_none")]
330    pub name: Option<String>,
331}
332
333impl WrappedCandidTypeReference {
334    /// 文本
335    pub fn to_text(&self) -> String {
336        let Self { id, .. } = self;
337
338        format!("rec_{}", id,)
339    }
340}
341
342/// 自定义的包装 Candid 类型
343#[derive(Debug, Clone, CandidType, Serialize, Deserialize, Eq, PartialEq)]
344pub enum WrappedCandidType {
345    // 基本类型
346    /// bool
347    /// boolean type: true false Motoko Bool / Rust bool / JavaScript true false
348    /// <https://docs.internetcomputer.org/languages/motoko/reference/language-manual/#type-bool>
349    #[serde(rename = "bool")]
350    Bool(WrappedCandidTypeName),
351    /// nat
352    /// nature number: Motoko Nat / Rust candid:Nat or u128 / JavaScript BigInt(10000) or 10000n
353    /// <https://docs.internetcomputer.org/languages/motoko/reference/language-manual/#type-nat>
354    #[serde(rename = "nat")]
355    Nat(WrappedCandidTypeName),
356    /// int
357    /// integer number: Motoko Int / Rust candid::Int or i128 / JavaScript BigInt(-10000) or -10000n
358    /// <https://docs.internetcomputer.org/languages/motoko/reference/language-manual/#type-int>
359    #[serde(rename = "int")]
360    Int(WrappedCandidTypeName),
361    /// nat8
362    /// integer with limit bits
363    /// <https://docs.internetcomputer.org/languages/motoko/reference/language-manual/#type-natn-and-intn>
364    #[serde(rename = "nat8")]
365    Nat8(WrappedCandidTypeName),
366    /// nat16
367    /// integer with limit bits
368    /// <https://docs.internetcomputer.org/languages/motoko/reference/language-manual/#type-natn-and-intn>
369    #[serde(rename = "nat16")]
370    Nat16(WrappedCandidTypeName),
371    /// nat32
372    /// integer with limit bits
373    /// <https://docs.internetcomputer.org/languages/motoko/reference/language-manual/#type-natn-and-intn>
374    #[serde(rename = "nat32")]
375    Nat32(WrappedCandidTypeName),
376    /// nat64
377    /// integer with limit bits
378    /// <https://docs.internetcomputer.org/languages/motoko/reference/language-manual/#type-natn-and-intn>
379    #[serde(rename = "nat64")]
380    Nat64(WrappedCandidTypeName),
381    /// int8
382    /// integer with limit bits
383    /// <https://docs.internetcomputer.org/languages/motoko/reference/language-manual/#type-natn-and-intn>
384    #[serde(rename = "int8")]
385    Int8(WrappedCandidTypeName),
386    /// int16
387    /// integer with limit bits
388    /// <https://docs.internetcomputer.org/languages/motoko/reference/language-manual/#type-natn-and-intn>
389    #[serde(rename = "int16")]
390    Int16(WrappedCandidTypeName),
391    /// int32
392    /// integer with limit bits
393    /// <https://docs.internetcomputer.org/languages/motoko/reference/language-manual/#type-natn-and-intn>
394    #[serde(rename = "int32")]
395    Int32(WrappedCandidTypeName),
396    /// int64
397    /// integer with limit bits
398    /// <https://docs.internetcomputer.org/languages/motoko/reference/language-manual/#type-natn-and-intn>
399    #[serde(rename = "int64")]
400    Int64(WrappedCandidTypeName),
401    /// float32
402    /// float number: Motoko Float is 64 bits / Rust f32 f64 / JavaScript float
403    /// <https://docs.internetcomputer.org/languages/motoko/reference/language-manual/#type-float32-and-float64>
404    #[serde(rename = "float32")]
405    Float32(WrappedCandidTypeName),
406    /// float64
407    /// float number: Motoko Float is 64 bits / Rust f32 f64 / JavaScript float
408    /// <https://docs.internetcomputer.org/languages/motoko/reference/language-manual/#type-float32-and-float64>
409    #[serde(rename = "float64")]
410    Float64(WrappedCandidTypeName),
411    /// null
412    /// null type: only value is null Motoko Null / Rust None / JavaScript null
413    /// <https://docs.internetcomputer.org/languages/motoko/reference/language-manual/#type-null>
414    #[serde(rename = "null")]
415    Null(WrappedCandidTypeName),
416    /// text
417    /// text type: Motoko Text / Rust String or &str / JavaScript string
418    /// <https://docs.internetcomputer.org/languages/motoko/reference/language-manual/#type-text>
419    #[serde(rename = "text")]
420    Text(WrappedCandidTypeName),
421    /// principal
422    /// principal type: like "zwigo-aiaaa-aaaaa-qaa3a-cai" Motoko Principal / candid::Principal / JavaScript Principal.fromText("aaaaa-aa")
423    /// <https://docs.internetcomputer.org/languages/motoko/reference/language-manual/#type-principal>
424    #[serde(rename = "principal")]
425    Principal(WrappedCandidTypeName),
426    // Blob, // 一律以 vec nat8 替代
427    // 子类型
428    /// vec T
429    /// binary data: `vec nat8` / Motoko `Blob` / Rust `Vec<u8>` or `&[u8]` / JavaScript `[1, 2, 3]`
430    /// <https://docs.internetcomputer.org/languages/motoko/reference/language-manual/#type-blob>
431    /// array of some type: `vec {1,3}` / Motoko `[T]` / Rust `Vec<T>` or `&[T]` / JavaScript `Array`
432    /// <https://docs.internetcomputer.org/languages/motoko/reference/language-manual/#type-vec-t>
433    #[serde(rename = "vec")]
434    Vec(WrappedCandidTypeSubtype),
435    /// opt T
436    /// option type: `null` or `opt t` / Motoko `?T` / Rust `Option<T>` / JavaScript `[]` or `[t]`
437    /// <https://docs.internetcomputer.org/languages/motoko/reference/language-manual/#type-opt-t>
438    #[serde(rename = "opt")]
439    Opt(WrappedCandidTypeSubtype),
440    // 多个子类型
441    /// record { .. } // name=T
442    /// object type: record { name="123"; } Motoko record { name: "123" } / Rust struct / JavaScript object
443    /// <https://docs.internetcomputer.org/languages/motoko/reference/language-manual/#type-record--n--t-->
444    #[serde(rename = "record")]
445    Record(WrappedCandidTypeRecord),
446    /// variant { .. }
447    /// enumerate type: variant { ok : nat; error : text } / Rust enum / JavaScript { dot: null }
448    /// <https://docs.internetcomputer.org/languages/motoko/reference/language-manual/#type-variant--n--t-->
449    #[serde(rename = "variant")]
450    Variant(WrappedCandidTypeVariant),
451    /// tuple record { .. } // T
452    /// tuple type: subitem has no name
453    /// JavaScript array value
454    #[serde(rename = "tuple")]
455    Tuple(WrappedCandidTypeTuple),
456    // 特殊类型
457    /// unknown
458    /// unknown type
459    #[serde(rename = "unknown")]
460    Unknown(WrappedCandidTypeName),
461    /// empty
462    /// empty type
463    /// <https://docs.internetcomputer.org/languages/motoko/reference/language-manual/#type-empty>
464    #[serde(rename = "empty")]
465    Empty(WrappedCandidTypeName), // 没有值的类型, 是其他类型的子类型
466    /// reserved
467    /// reserved type: some function arguments can be ignore
468    /// <https://docs.internetcomputer.org/languages/motoko/reference/language-manual/#type-reserved>
469    #[serde(rename = "reserved")]
470    Reserved(WrappedCandidTypeName), // 占位不使用的类型
471    /// func
472    /// func type
473    /// <https://docs.internetcomputer.org/languages/motoko/reference/language-manual/#type-func--->
474    #[serde(rename = "func")]
475    Func(WrappedCandidTypeFunction),
476    /// service
477    /// service type: canister's api
478    /// <https://docs.internetcomputer.org/languages/motoko/reference/language-manual/#type-service->
479    #[serde(rename = "service")]
480    Service(WrappedCandidTypeService),
481    /// rec
482    /// object type: some subtype or subitem is recursion
483    #[serde(rename = "rec")]
484    Rec(WrappedCandidTypeRecursion), // 循环类型中的主类型
485    /// ref
486    #[serde(rename = "ref")]
487    Reference(WrappedCandidTypeReference), // 循环类型中的引用类型
488}
489
490impl WrappedCandidType {
491    /// 文本
492    pub fn to_text(&self) -> String {
493        match self {
494            Self::Bool(_) => String::from("bool"),
495            Self::Nat(_) => String::from("nat"),
496            Self::Int(_) => String::from("int"),
497            Self::Nat8(_) => String::from("nat8"),
498            Self::Nat16(_) => String::from("nat16"),
499            Self::Nat32(_) => String::from("nat32"),
500            Self::Nat64(_) => String::from("nat64"),
501            Self::Int8(_) => String::from("int8"),
502            Self::Int16(_) => String::from("int16"),
503            Self::Int32(_) => String::from("int32"),
504            Self::Int64(_) => String::from("int64"),
505            Self::Float32(_) => String::from("float32"),
506            Self::Float64(_) => String::from("float64"),
507            Self::Null(_) => String::from("null"),
508            Self::Text(_) => String::from("text"),
509            Self::Principal(_) => String::from("principal"),
510            Self::Vec(WrappedCandidTypeSubtype { subtype, .. }) => {
511                format!("vec {}", subtype.to_text())
512            }
513            Self::Opt(WrappedCandidTypeSubtype { subtype, .. }) => {
514                format!("opt {}", subtype.to_text())
515            }
516            Self::Record(record) => record.to_text(),
517            Self::Variant(variant) => variant.to_text(),
518            Self::Tuple(tuple) => tuple.to_text(),
519            Self::Unknown(_) => String::from("unknown"),
520            Self::Empty(_) => String::from("empty"),
521            Self::Reserved(_) => String::from("reserved"),
522            Self::Func(func) => func.to_text(),
523            Self::Service(service) => service.to_text(),
524            Self::Rec(recursion) => recursion.to_text(),
525            Self::Reference(reference) => reference.to_text(),
526        }
527    }
528}
529
530#[cfg(test)]
531mod serialization_tests {
532    use ciborium::value::Value;
533    use serde::Serialize;
534
535    use super::*;
536
537    #[derive(Serialize)]
538    struct RecordPayload {
539        subitems: Vec<(String, WrappedCandidType)>,
540        name: Option<String>,
541    }
542
543    #[derive(Serialize)]
544    struct VariantPayload {
545        subitems: Vec<(String, Option<WrappedCandidType>)>,
546        name: Option<String>,
547    }
548
549    #[derive(Serialize)]
550    struct TuplePayload {
551        subitems: Vec<WrappedCandidType>,
552        name: Option<String>,
553    }
554
555    #[derive(Serialize)]
556    struct LegacyFunction {
557        args: Vec<WrappedCandidType>,
558        rets: Vec<WrappedCandidType>,
559        annotation: Option<FunctionAnnotation>,
560        name: Option<String>,
561    }
562
563    #[derive(Serialize)]
564    struct LegacyRecursion {
565        ty: Box<WrappedCandidType>,
566        id: u32,
567        name: Option<String>,
568    }
569
570    fn nat() -> WrappedCandidType {
571        WrappedCandidType::Nat(WrappedCandidTypeName::default())
572    }
573
574    fn round_trip_legacy<Legacy: Serialize, Current: serde::de::DeserializeOwned>(legacy: &Legacy) -> Current {
575        let mut cbor = Vec::new();
576        ciborium::ser::into_writer(legacy, &mut cbor).unwrap();
577        ciborium::de::from_reader(cbor.as_slice()).unwrap()
578    }
579
580    fn serialized_keys(value: &impl Serialize) -> Vec<String> {
581        let mut cbor = Vec::new();
582        ciborium::ser::into_writer(value, &mut cbor).unwrap();
583        let value: Value = ciborium::de::from_reader(cbor.as_slice()).unwrap();
584        let Value::Map(entries) = value else {
585            panic!("expected a CBOR map")
586        };
587        entries
588            .into_iter()
589            .filter_map(|(key, _)| match key {
590                Value::Text(key) => Some(key),
591                _ => None,
592            })
593            .collect()
594    }
595
596    #[test]
597    fn deserializes_legacy_candid_type_fields_and_serializes_current_names() {
598        let record: WrappedCandidTypeRecord = round_trip_legacy(&RecordPayload {
599            subitems: vec![("value".to_string(), nat())],
600            name: None,
601        });
602        assert_eq!(record.subitems.len(), 1);
603        assert_eq!(serialized_keys(&record), vec!["subitems"]);
604
605        let variant: WrappedCandidTypeVariant = round_trip_legacy(&VariantPayload {
606            subitems: vec![("ok".to_string(), Some(nat()))],
607            name: None,
608        });
609        assert_eq!(variant.subitems.len(), 1);
610        assert_eq!(serialized_keys(&variant), vec!["subitems"]);
611
612        let tuple: WrappedCandidTypeTuple = round_trip_legacy(&TuplePayload {
613            subitems: vec![nat()],
614            name: None,
615        });
616        assert_eq!(tuple.subitems.len(), 1);
617        assert_eq!(serialized_keys(&tuple), vec!["subitems"]);
618
619        let function: WrappedCandidTypeFunction = round_trip_legacy(&LegacyFunction {
620            args: Vec::new(),
621            rets: vec![nat()],
622            annotation: None,
623            name: None,
624        });
625        assert_eq!(function.rets.len(), 1);
626        assert_eq!(serialized_keys(&function), vec!["rets"]);
627
628        let recursion: WrappedCandidTypeRecursion = round_trip_legacy(&LegacyRecursion {
629            ty: Box::new(nat()),
630            id: 1,
631            name: None,
632        });
633        assert!(matches!(*recursion.ty, WrappedCandidType::Nat(_)));
634        assert_eq!(serialized_keys(&recursion), vec!["ty", "id"]);
635    }
636}