function_sdk_rust/request.rs
1//! Helpers for working with RunFunctionRequests.
2
3use crate::Error;
4use crate::proto::v1::{Capability, CredentialData, RunFunctionRequest, credentials};
5use crate::resource::{pb_to_json, struct_to_json};
6
7/// The requirement name a WatchOperation uses to inject the watched resource.
8pub const WATCHED_RESOURCE_KEY: &str = "ops.crossplane.io/watched-resource";
9
10/// Gets the function's input from the request as a typed value.
11///
12/// Input is the function-defined config from the Composition's
13/// `spec.pipeline[].input` block. Crossplane never validates it, so
14/// functions must - deserializing into a typed struct is the first line of
15/// that validation. Returns [`Error::MissingInput`] when the pipeline step
16/// has no input at all.
17pub fn get_input<T: serde::de::DeserializeOwned>(req: &RunFunctionRequest) -> Result<T, Error> {
18 let input = req.input.as_ref().ok_or(Error::MissingInput)?;
19 Ok(serde_json::from_value(struct_to_json(input))?)
20}
21
22/// Gets a function context value by key, if present.
23///
24/// See [`crate::context`] for well-known context keys.
25pub fn get_context_key(req: &RunFunctionRequest, key: &str) -> Option<serde_json::Value> {
26 req.context.as_ref()?.fields.get(key).map(pb_to_json)
27}
28
29/// Gets required resources by requirement name from the request.
30///
31/// Returns None when Crossplane has not (yet) resolved the requirement, and
32/// an empty Vec when it resolved the requirement but found no matches.
33/// Always declare requirements with [`crate::response::require_resources`];
34/// Crossplane considers them satisfied when they stabilize across calls.
35pub fn get_required_resources(
36 req: &RunFunctionRequest,
37 name: &str,
38) -> Option<Vec<serde_json::Value>> {
39 let resources = req.required_resources.get(name)?;
40 Some(
41 resources
42 .items
43 .iter()
44 .filter_map(|r| r.resource.as_ref())
45 .map(struct_to_json)
46 .collect(),
47 )
48}
49
50/// Gets a single required resource by requirement name from the request.
51///
52/// A convenience for requirements that match exactly one resource. Returns
53/// None when the requirement is unresolved or matched nothing.
54pub fn get_required_resource(req: &RunFunctionRequest, name: &str) -> Option<serde_json::Value> {
55 get_required_resources(req, name)?.into_iter().next()
56}
57
58/// Gets the watched resource that triggered this operation, if any.
59///
60/// When a WatchOperation creates an Operation it injects the resource that
61/// changed under the requirement name [`WATCHED_RESOURCE_KEY`].
62pub fn get_watched_resource(req: &RunFunctionRequest) -> Option<serde_json::Value> {
63 get_required_resource(req, WATCHED_RESOURCE_KEY)
64}
65
66/// Gets the supplied credential data from the request, if any.
67pub fn get_credential_data<'a>(
68 req: &'a RunFunctionRequest,
69 name: &str,
70) -> Option<&'a CredentialData> {
71 match req.credentials.get(name)?.source.as_ref()? {
72 credentials::Source::CredentialData(data) => Some(data),
73 }
74}
75
76/// Checks whether Crossplane advertises its capabilities at all.
77///
78/// Crossplane v2.2 and later advertise capabilities in the request metadata.
79/// If this returns false the calling Crossplane predates capability
80/// advertisement, and [`has_capability`] returns false even for features
81/// that older Crossplane does support.
82pub fn advertises_capabilities(req: &RunFunctionRequest) -> bool {
83 has_capability(req, Capability::Capabilities)
84}
85
86/// Checks whether Crossplane advertises a particular capability.
87pub fn has_capability(req: &RunFunctionRequest, cap: Capability) -> bool {
88 req.meta
89 .as_ref()
90 .is_some_and(|m| m.capabilities().any(|c| c == cap))
91}
92
93/// Gets a required OpenAPI v3 schema by requirement name from the request.
94///
95/// Returns None both when the requirement is unresolved and when Crossplane
96/// resolved it but found no schema. To distinguish the two, check
97/// `req.required_schemas.contains_key(name)`.
98pub fn get_required_schema(req: &RunFunctionRequest, name: &str) -> Option<serde_json::Value> {
99 req.required_schemas
100 .get(name)?
101 .openapi_v3
102 .as_ref()
103 .map(struct_to_json)
104}