Skip to main content

busbar_sf_tooling/client/
dependencies.rs

1//! MetadataComponentDependency operations (Beta).
2
3use crate::error::Result;
4use tracing::instrument;
5
6impl super::ToolingClient {
7    /// Get metadata component dependencies.
8    ///
9    /// Returns dependency relationships between metadata components in your org.
10    /// Note: Limited to 2000 records per query in Tooling API.
11    ///
12    /// Available since API v43.0 for Tooling API.
13    ///
14    /// # Security
15    ///
16    /// **IMPORTANT**: If you are including user-provided values in the WHERE clause,
17    /// you MUST escape them to prevent SOQL injection attacks:
18    ///
19    /// ```rust,ignore
20    /// use busbar_sf_client::security::soql;
21    ///
22    /// // CORRECT - properly escaped:
23    /// let safe_type = soql::escape_string(user_input);
24    /// let filter = format!("MetadataComponentType = '{}'", safe_type);
25    /// let deps = client.get_metadata_component_dependencies(Some(&filter)).await?;
26    /// ```
27    ///
28    /// # Example
29    ///
30    /// ```rust,ignore
31    /// // Get all dependencies (limited to 2000)
32    /// let deps = client.get_metadata_component_dependencies(None).await?;
33    ///
34    /// // Filter by component type (hardcoded - safe)
35    /// let apex_deps = client.get_metadata_component_dependencies(
36    ///     Some("MetadataComponentType = 'ApexClass'")
37    /// ).await?;
38    /// ```
39    #[cfg(feature = "dependencies")]
40    #[instrument(skip(self))]
41    pub async fn get_metadata_component_dependencies(
42        &self,
43        where_clause: Option<&str>,
44    ) -> Result<Vec<crate::types::MetadataComponentDependency>> {
45        let base_query = "SELECT MetadataComponentId, MetadataComponentName, MetadataComponentNamespace, MetadataComponentType, RefMetadataComponentId, RefMetadataComponentName, RefMetadataComponentNamespace, RefMetadataComponentType FROM MetadataComponentDependency";
46
47        let query = if let Some(filter) = where_clause {
48            format!("{} WHERE {}", base_query, filter)
49        } else {
50            base_query.to_string()
51        };
52
53        self.query_all(&query).await
54    }
55}
56
57#[cfg(test)]
58#[cfg(feature = "dependencies")]
59mod tests {
60    #[test]
61    fn test_metadata_component_dependency_query_construction_no_filter() {
62        let base_query = "SELECT MetadataComponentId, MetadataComponentName, MetadataComponentNamespace, MetadataComponentType, RefMetadataComponentId, RefMetadataComponentName, RefMetadataComponentNamespace, RefMetadataComponentType FROM MetadataComponentDependency";
63
64        let query = base_query.to_string();
65
66        assert_eq!(query, base_query);
67        assert!(query.contains("MetadataComponentId"));
68        assert!(query.contains("RefMetadataComponentId"));
69        assert!(!query.contains("WHERE"));
70    }
71
72    #[test]
73    fn test_metadata_component_dependency_query_construction_with_filter() {
74        let base_query = "SELECT MetadataComponentId, MetadataComponentName, MetadataComponentNamespace, MetadataComponentType, RefMetadataComponentId, RefMetadataComponentName, RefMetadataComponentNamespace, RefMetadataComponentType FROM MetadataComponentDependency";
75        let filter = "MetadataComponentType = 'ApexClass'";
76
77        let query = format!("{} WHERE {}", base_query, filter);
78
79        assert!(query.contains("WHERE MetadataComponentType = 'ApexClass'"));
80        assert!(query.contains("MetadataComponentId"));
81        assert!(query.contains("RefMetadataComponentId"));
82    }
83}