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