ex3_ic_agent/agent/
status.rs

1//! Types for interacting with the status endpoint of a replica. See [`Status`] for details.
2
3use std::{collections::BTreeMap, fmt::Debug};
4
5/// Value returned by the status endpoint of a replica. This is a loose mapping to CBOR values.
6/// Because the agent should not return [`serde_cbor::Value`] directly across API boundaries,
7/// we reimplement it as [`Value`] here.
8#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Clone, Hash)]
9pub enum Value {
10    /// See [`Null`](serde_cbor::Value::Null).
11    Null,
12    /// See [`String`](serde_cbor::Value::Text).
13    String(String),
14    /// See [`Integer`](serde_cbor::Value::Integer).
15    Integer(i64),
16    /// See [`Bool`](serde_cbor::Value::Bool).
17    Bool(bool),
18    /// See [`Bytes`](serde_cbor::Value::Bytes).
19    Bytes(Vec<u8>),
20    /// See [`Vec`](serde_cbor::Value::Array).
21    Vec(Vec<Value>),
22    /// See [`Map`](serde_cbor::Value::Map).
23    Map(BTreeMap<String, Box<Value>>),
24}
25
26impl std::fmt::Display for Value {
27    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28        match self {
29            Value::Null => f.write_str("null"),
30            Value::String(s) => f.write_fmt(format_args!(r#""{}""#, s.escape_debug())),
31            Value::Integer(i) => f.write_str(&i.to_string()),
32            Value::Bool(true) => f.write_str("true"),
33            Value::Bool(false) => f.write_str("false"),
34            Value::Bytes(b) => f.debug_list().entries(b).finish(),
35            Value::Vec(v) => f.debug_list().entries(v).finish(),
36            Value::Map(m) => f.debug_map().entries(m).finish(),
37        }
38    }
39}
40
41/// The structure returned by [`super::Agent::status`], containing the information returned
42/// by the status endpoint of a replica.
43#[derive(Debug, Ord, PartialOrd, PartialEq, Eq)]
44pub struct Status {
45    /// Identifies the interface version supported, i.e. the version of the present document that
46    /// the internet computer aims to support, e.g. 0.8.1. The implementation may also return
47    /// unversioned to indicate that it does not comply to a particular version, e.g. in between
48    /// releases.
49    pub ic_api_version: String,
50
51    /// Optional. Identifies the implementation of the Internet Computer, by convention with the
52    /// canonical location of the source code.
53    pub impl_source: Option<String>,
54
55    /// Optional. If the user is talking to a released version of an Internet Computer
56    /// implementation, this is the version number. For non-released versions, output of
57    /// `git describe` like 0.1.13-13-g2414721 would also be very suitable.
58    pub impl_version: Option<String>,
59
60    /// Optional. The precise git revision of the Internet Computer implementation.
61    pub impl_revision: Option<String>,
62
63    /// Optional.  The health status of the replica.  One hopes it's "healthy".
64    pub replica_health_status: Option<String>,
65
66    /// Optional.  The root (public) key used to verify certificates.
67    pub root_key: Option<Vec<u8>>,
68
69    /// Contains any additional values that the replica gave as status.
70    pub values: BTreeMap<String, Box<Value>>,
71}
72
73impl std::fmt::Display for Status {
74    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75        f.write_str("{\n")?;
76        for (key, value) in &self.values {
77            f.write_fmt(format_args!(r#"  "{}": "#, key.escape_debug()))?;
78            std::fmt::Display::fmt(&value, f)?;
79        }
80        f.write_str("\n}")
81    }
82}
83
84fn cbor_value_to_value(value: &serde_cbor::Value) -> Result<Value, ()> {
85    match value {
86        serde_cbor::Value::Null => Ok(Value::Null),
87        serde_cbor::Value::Bool(b) => Ok(Value::Bool(*b)),
88        serde_cbor::Value::Integer(i) => Ok(Value::Integer(*i as i64)),
89        serde_cbor::Value::Bytes(b) => Ok(Value::Bytes(b.to_owned())),
90        serde_cbor::Value::Text(s) => Ok(Value::String(s.to_owned())),
91        serde_cbor::Value::Array(a) => Ok(Value::Vec(
92            a.iter()
93                .map(cbor_value_to_value)
94                .collect::<Result<Vec<Value>, ()>>()
95                .map_err(|_| ())?,
96        )),
97        serde_cbor::Value::Map(m) => {
98            let mut map = BTreeMap::new();
99            for (key, value) in m.iter() {
100                let k = match key {
101                    serde_cbor::Value::Text(t) => t.to_owned(),
102                    serde_cbor::Value::Integer(i) => i.to_string(),
103                    _ => return Err(()),
104                };
105                let v = Box::new(cbor_value_to_value(value)?);
106
107                map.insert(k, v);
108            }
109            Ok(Value::Map(map))
110        }
111        serde_cbor::Value::Tag(_, v) => cbor_value_to_value(v.as_ref()),
112        _ => Err(()),
113    }
114}
115
116impl std::convert::TryFrom<&serde_cbor::Value> for Status {
117    type Error = ();
118
119    fn try_from(value: &serde_cbor::Value) -> Result<Self, ()> {
120        let v = cbor_value_to_value(value)?;
121
122        match v {
123            Value::Map(map) => {
124                // This field is not optional.
125                let ic_api_version = map.get("ic_api_version").ok_or(()).and_then(|v| {
126                    if let Value::String(s) = v.as_ref() {
127                        Ok(s.to_owned())
128                    } else {
129                        Err(())
130                    }
131                })?;
132                let impl_source = map.get("impl_source").and_then(|v| {
133                    if let Value::String(s) = v.as_ref() {
134                        Some(s.to_owned())
135                    } else {
136                        None
137                    }
138                });
139                let impl_version: Option<String> = map.get("impl_version").and_then(|v| {
140                    if let Value::String(s) = v.as_ref() {
141                        Some(s.to_owned())
142                    } else {
143                        None
144                    }
145                });
146                let impl_revision: Option<String> = map.get("impl_revision").and_then(|v| {
147                    if let Value::String(s) = v.as_ref() {
148                        Some(s.to_owned())
149                    } else {
150                        None
151                    }
152                });
153                let replica_health_status: Option<String> =
154                    map.get("replica_health_status").and_then(|v| {
155                        if let Value::String(s) = v.as_ref() {
156                            Some(s.to_owned())
157                        } else {
158                            None
159                        }
160                    });
161                let root_key: Option<Vec<u8>> = map.get("root_key").and_then(|v| {
162                    if let Value::Bytes(bytes) = v.as_ref() {
163                        Some(bytes.to_owned())
164                    } else {
165                        None
166                    }
167                });
168
169                Ok(Status {
170                    ic_api_version,
171                    impl_source,
172                    impl_version,
173                    impl_revision,
174                    replica_health_status,
175                    root_key,
176                    values: map,
177                })
178            }
179            _ => Err(()),
180        }
181    }
182}