1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
use busbar_sf_client::security::soql;
use tracing::instrument;
use crate::error::{Error, ErrorKind, Result};
impl super::ToolingClient {
/// Execute a Tooling API composite request with multiple subrequests.
///
/// The Tooling API composite endpoint allows up to 25 subrequests in a single API call.
/// Subrequests can reference results from earlier subrequests using `@{referenceId}`.
///
/// Available since API v40.0.
///
/// # Example
///
/// ```rust,ignore
/// use busbar_sf_tooling::{CompositeRequest, CompositeSubrequest};
///
/// let request = CompositeRequest {
/// all_or_none: false,
/// collate_subrequests: false,
/// subrequests: vec![
/// CompositeSubrequest {
/// method: "GET".to_string(),
/// url: "/services/data/v62.0/tooling/sobjects/ApexClass/01p...".to_string(),
/// reference_id: "refApexClass".to_string(),
/// body: None,
/// },
/// ],
/// };
///
/// let response = client.composite(&request).await?;
/// ```
#[instrument(skip(self, request))]
pub async fn composite(
&self,
request: &busbar_sf_rest::CompositeRequest,
) -> Result<busbar_sf_rest::CompositeResponse> {
let url = self.client.tooling_url("composite");
self.client
.post_json(&url, request)
.await
.map_err(Into::into)
}
/// Execute a Tooling API composite batch request with multiple independent subrequests.
///
/// The composite batch API executes up to 25 subrequests independently.
/// Unlike the standard composite API, subrequests cannot reference each other's results.
///
/// Available since API v40.0.
#[instrument(skip(self, request))]
pub async fn composite_batch(
&self,
request: &busbar_sf_rest::CompositeBatchRequest,
) -> Result<busbar_sf_rest::CompositeBatchResponse> {
let url = self.client.tooling_url("composite/batch");
self.client
.post_json(&url, request)
.await
.map_err(Into::into)
}
/// Execute a Tooling API composite tree request to create record hierarchies.
///
/// Creates parent records with nested child records in a single request.
/// Supports up to 200 records total across all levels of the hierarchy.
///
/// Available since API v42.0.
///
/// # Arguments
/// * `sobject` - The parent SObject type (e.g., "ApexClass", "CustomField")
/// * `request` - The tree request containing parent records and nested children
#[instrument(skip(self, request))]
pub async fn composite_tree(
&self,
sobject: &str,
request: &busbar_sf_rest::CompositeTreeRequest,
) -> Result<busbar_sf_rest::CompositeTreeResponse> {
if !soql::is_safe_sobject_name(sobject) {
return Err(Error::new(ErrorKind::Salesforce {
error_code: "INVALID_SOBJECT".to_string(),
message: "Invalid SObject name".to_string(),
}));
}
let url = self
.client
.tooling_url(&format!("composite/tree/{}", sobject));
self.client
.post_json(&url, request)
.await
.map_err(Into::into)
}
}
#[cfg(test)]
mod tests {
use super::super::ToolingClient;
#[test]
fn test_composite_url_construction() {
let client = ToolingClient::new("https://na1.salesforce.com", "token").unwrap();
let url = client.client.tooling_url("composite");
assert_eq!(
url,
"https://na1.salesforce.com/services/data/v62.0/tooling/composite"
);
}
#[test]
fn test_composite_batch_url_construction() {
let client = ToolingClient::new("https://na1.salesforce.com", "token").unwrap();
let url = client.client.tooling_url("composite/batch");
assert_eq!(
url,
"https://na1.salesforce.com/services/data/v62.0/tooling/composite/batch"
);
}
#[test]
fn test_composite_tree_url_construction() {
let client = ToolingClient::new("https://na1.salesforce.com", "token").unwrap();
let url = client
.client
.tooling_url(&format!("composite/tree/{}", "ApexClass"));
assert_eq!(
url,
"https://na1.salesforce.com/services/data/v62.0/tooling/composite/tree/ApexClass"
);
}
}