Skip to main content

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}