1use std::collections::HashMap;
4use std::time::Duration;
5
6use serde::Serialize;
7
8use crate::Error;
9use crate::proto::v1::{
10 Condition, MatchLabels, Requirements, Resource, ResourceSelector, Result as FnResult,
11 RunFunctionRequest, RunFunctionResponse, SchemaSelector, Severity, Status, Target,
12 resource_selector,
13};
14use crate::resource::{json_to_pb, json_to_struct};
15
16pub const DEFAULT_TTL: Duration = Duration::from_secs(60);
18
19pub fn to(req: &RunFunctionRequest, ttl: Duration) -> RunFunctionResponse {
26 RunFunctionResponse {
27 meta: Some(crate::proto::v1::ResponseMeta {
28 tag: req.meta.as_ref().map(|m| m.tag.clone()).unwrap_or_default(),
29 ttl: Some(pbjson_types::Duration {
30 seconds: ttl.as_secs() as i64,
31 nanos: ttl.subsec_nanos() as i32,
32 }),
33 }),
34 desired: req.desired.clone(),
35 context: req.context.clone(),
36 ..Default::default()
37 }
38}
39
40pub fn desired_composite(rsp: &mut RunFunctionResponse) -> &mut Resource {
46 rsp.desired
47 .get_or_insert_default()
48 .composite
49 .get_or_insert_default()
50}
51
52pub fn desired_composed(rsp: &mut RunFunctionResponse, name: impl Into<String>) -> &mut Resource {
59 rsp.desired
60 .get_or_insert_default()
61 .resources
62 .entry(name.into())
63 .or_default()
64}
65
66pub fn normal(rsp: &mut RunFunctionResponse, message: impl Into<String>) -> &mut FnResult {
79 add_result(rsp, Severity::Normal, message)
80}
81
82pub fn warning(rsp: &mut RunFunctionResponse, message: impl Into<String>) -> &mut FnResult {
84 add_result(rsp, Severity::Warning, message)
85}
86
87pub fn fatal(rsp: &mut RunFunctionResponse, message: impl Into<String>) -> &mut FnResult {
94 add_result(rsp, Severity::Fatal, message)
95}
96
97fn add_result(
98 rsp: &mut RunFunctionResponse,
99 severity: Severity,
100 message: impl Into<String>,
101) -> &mut FnResult {
102 rsp.results.push(FnResult {
103 severity: severity as i32,
104 message: message.into(),
105 reason: None,
106 target: Some(Target::Composite as i32),
108 });
109 rsp.results.last_mut().expect("a result was just pushed")
110}
111
112pub fn condition_true(
118 rsp: &mut RunFunctionResponse,
119 typ: impl Into<String>,
120 reason: impl Into<String>,
121) -> &mut Condition {
122 add_condition(rsp, Status::ConditionTrue, typ, reason)
123}
124
125pub fn condition_false(
127 rsp: &mut RunFunctionResponse,
128 typ: impl Into<String>,
129 reason: impl Into<String>,
130) -> &mut Condition {
131 add_condition(rsp, Status::ConditionFalse, typ, reason)
132}
133
134pub fn condition_unknown(
136 rsp: &mut RunFunctionResponse,
137 typ: impl Into<String>,
138 reason: impl Into<String>,
139) -> &mut Condition {
140 add_condition(rsp, Status::ConditionUnknown, typ, reason)
141}
142
143fn add_condition(
144 rsp: &mut RunFunctionResponse,
145 status: Status,
146 typ: impl Into<String>,
147 reason: impl Into<String>,
148) -> &mut Condition {
149 rsp.conditions.push(Condition {
150 r#type: typ.into(),
151 status: status as i32,
152 reason: reason.into(),
153 message: None,
154 target: None,
155 });
156 rsp.conditions
157 .last_mut()
158 .expect("a condition was just pushed")
159}
160
161#[derive(Clone, Debug)]
163pub enum ResourceMatch {
164 Name(String),
166 Labels(HashMap<String, String>),
168}
169
170pub fn require_resources(
178 rsp: &mut RunFunctionResponse,
179 name: impl Into<String>,
180 api_version: impl Into<String>,
181 kind: impl Into<String>,
182 r#match: Option<ResourceMatch>,
183 namespace: Option<String>,
184) {
185 let selector = ResourceSelector {
186 api_version: api_version.into(),
187 kind: kind.into(),
188 r#match: r#match.map(|m| match m {
189 ResourceMatch::Name(name) => resource_selector::Match::MatchName(name),
190 ResourceMatch::Labels(labels) => {
191 resource_selector::Match::MatchLabels(MatchLabels { labels })
192 }
193 }),
194 namespace,
195 };
196 requirements(rsp).resources.insert(name.into(), selector);
197}
198
199pub fn require_schema(
205 rsp: &mut RunFunctionResponse,
206 name: impl Into<String>,
207 api_version: impl Into<String>,
208 kind: impl Into<String>,
209) {
210 let selector = SchemaSelector {
211 api_version: api_version.into(),
212 kind: kind.into(),
213 };
214 requirements(rsp).schemas.insert(name.into(), selector);
215}
216
217fn requirements(rsp: &mut RunFunctionResponse) -> &mut Requirements {
218 rsp.requirements.get_or_insert_default()
219}
220
221pub fn set_context_key<T: Serialize + ?Sized>(
228 rsp: &mut RunFunctionResponse,
229 key: impl Into<String>,
230 value: &T,
231) -> Result<(), Error> {
232 let v = serde_json::to_value(value)?;
233 rsp.context
234 .get_or_insert_default()
235 .fields
236 .insert(key.into(), json_to_pb(&v));
237 Ok(())
238}
239
240pub fn set_output<T: Serialize + ?Sized>(
245 rsp: &mut RunFunctionResponse,
246 output: &T,
247) -> Result<(), Error> {
248 let value = serde_json::to_value(output)?;
249 let serde_json::Value::Object(map) = value else {
250 return Err(Error::NotAnObject);
251 };
252 rsp.output = Some(json_to_struct(&map));
253 Ok(())
254}