function_sdk_rust/
resource.rs1use pbjson_types::{ListValue, Struct, Value, value::Kind};
4use serde::Serialize;
5use sha2::{Digest, Sha256};
6
7use crate::Error;
8use crate::proto::v1::{Ready, Resource};
9
10pub fn struct_to_json(s: &Struct) -> serde_json::Value {
18 serde_json::Value::Object(
19 s.fields
20 .iter()
21 .map(|(k, v)| (k.clone(), pb_to_json(v)))
22 .collect(),
23 )
24}
25
26pub fn get<T: serde::de::DeserializeOwned>(resource: Option<&Resource>) -> Result<T, Error> {
36 let s = resource
37 .and_then(|r| r.resource.as_ref())
38 .ok_or(Error::MissingResource)?;
39 Ok(serde_json::from_value(struct_to_json(s))?)
40}
41
42pub fn json_to_struct(m: &serde_json::Map<String, serde_json::Value>) -> Struct {
44 Struct {
45 fields: m.iter().map(|(k, v)| (k.clone(), json_to_pb(v))).collect(),
46 }
47}
48
49pub fn pb_to_json(v: &Value) -> serde_json::Value {
58 match &v.kind {
59 None | Some(Kind::NullValue(_)) => serde_json::Value::Null,
60 Some(Kind::NumberValue(n)) => number_to_json(*n),
61 Some(Kind::StringValue(s)) => serde_json::Value::String(s.clone()),
62 Some(Kind::BoolValue(b)) => serde_json::Value::Bool(*b),
63 Some(Kind::StructValue(s)) => struct_to_json(s),
64 Some(Kind::ListValue(l)) => {
65 serde_json::Value::Array(l.values.iter().map(pb_to_json).collect())
66 }
67 }
68}
69
70fn number_to_json(n: f64) -> serde_json::Value {
71 const SAFE_INT: f64 = (1i64 << 53) as f64;
72 if n.fract() == 0.0 && n.abs() <= SAFE_INT {
73 return serde_json::Value::Number(serde_json::Number::from(n as i64));
74 }
75 serde_json::Number::from_f64(n)
76 .map(serde_json::Value::Number)
77 .unwrap_or(serde_json::Value::Null)
78}
79
80pub fn json_to_pb(v: &serde_json::Value) -> Value {
82 let kind = match v {
83 serde_json::Value::Null => Kind::NullValue(0),
84 serde_json::Value::Bool(b) => Kind::BoolValue(*b),
85 serde_json::Value::Number(n) => Kind::NumberValue(n.as_f64().unwrap_or(f64::NAN)),
86 serde_json::Value::String(s) => Kind::StringValue(s.clone()),
87 serde_json::Value::Array(a) => Kind::ListValue(ListValue {
88 values: a.iter().map(json_to_pb).collect(),
89 }),
90 serde_json::Value::Object(m) => Kind::StructValue(json_to_struct(m)),
91 };
92 Value { kind: Some(kind) }
93}
94
95pub fn update<T: Serialize + ?Sized>(r: &mut Resource, source: &T) -> Result<(), Error> {
104 let value = serde_json::to_value(source)?;
105 let serde_json::Value::Object(map) = value else {
106 return Err(Error::NotAnObject);
107 };
108 let target = r.resource.get_or_insert_default();
109 for (k, v) in &map {
110 target.fields.insert(k.clone(), json_to_pb(v));
111 }
112 Ok(())
113}
114
115pub fn update_status<T: Serialize + ?Sized>(r: &mut Resource, status: &T) -> Result<(), Error> {
119 update(
120 r,
121 &serde_json::json!({"status": serde_json::to_value(status)?}),
122 )
123}
124
125pub fn set_ready(r: &mut Resource, ready: Ready) {
130 r.ready = ready as i32;
131}
132
133pub fn add_connection_detail(r: &mut Resource, key: impl Into<String>, value: impl Into<Vec<u8>>) {
138 r.connection_details.insert(key.into(), value.into());
139}
140
141#[derive(Clone, Debug, PartialEq, Eq)]
143pub struct Condition {
144 pub typ: String,
146 pub status: String,
148 pub reason: Option<String>,
150 pub message: Option<String>,
152 pub last_transition_time: Option<String>,
154}
155
156impl Condition {
157 fn unknown(typ: &str) -> Self {
158 Self {
159 typ: typ.to_string(),
160 status: "Unknown".to_string(),
161 reason: None,
162 message: None,
163 last_transition_time: None,
164 }
165 }
166}
167
168pub fn get_condition(resource: Option<&Resource>, typ: &str) -> Condition {
175 let Some(s) = resource.and_then(|r| r.resource.as_ref()) else {
176 return Condition::unknown(typ);
177 };
178 let Some(Kind::StructValue(status)) = s.fields.get("status").and_then(|v| v.kind.as_ref())
179 else {
180 return Condition::unknown(typ);
181 };
182 let Some(Kind::ListValue(conditions)) = status
183 .fields
184 .get("conditions")
185 .and_then(|v| v.kind.as_ref())
186 else {
187 return Condition::unknown(typ);
188 };
189
190 for value in &conditions.values {
191 let Some(Kind::StructValue(c)) = value.kind.as_ref() else {
192 continue;
193 };
194 if get_str(c, "type") != Some(typ) {
195 continue;
196 }
197 return Condition {
198 typ: typ.to_string(),
199 status: get_str(c, "status").unwrap_or("Unknown").to_string(),
200 reason: get_str(c, "reason").map(String::from),
201 message: get_str(c, "message").map(String::from),
202 last_transition_time: get_str(c, "lastTransitionTime").map(String::from),
203 };
204 }
205
206 Condition::unknown(typ)
207}
208
209fn get_str<'a>(s: &'a Struct, key: &str) -> Option<&'a str> {
210 match s.fields.get(key)?.kind.as_ref()? {
211 Kind::StringValue(v) => Some(v),
212 _ => None,
213 }
214}
215
216const DNS_LABEL_MAX: usize = 63;
217const HASH_LEN: usize = 5;
218
219pub fn child_name(parts: &[&str], sep: &str) -> String {
226 let full = parts.join(sep);
227 let digest = Sha256::digest(full.as_bytes());
228 let hash: String = digest
229 .iter()
230 .take(HASH_LEN.div_ceil(2))
231 .map(|b| format!("{b:02x}"))
232 .collect();
233 let hash = &hash[..HASH_LEN];
234 let max_prefix = DNS_LABEL_MAX - HASH_LEN - sep.len();
235 let prefix: String = full.chars().take(max_prefix).collect();
236 let prefix = prefix.trim_end_matches(sep);
237 format!("{prefix}{sep}{hash}")
238}