Skip to main content

busbar_sf_rest/
describe.rs

1//! Describe operations and types.
2//!
3//! This module contains types for the Salesforce describe API,
4//! which provides metadata about SObjects and their fields.
5
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8
9// ============================================================================
10// Describe Global Types
11// ============================================================================
12
13/// Result of the describeGlobal operation.
14///
15/// Contains a list of all SObjects accessible to the user.
16#[derive(Debug, Clone, Deserialize, Serialize)]
17pub struct DescribeGlobalResult {
18    /// Character encoding (e.g., "UTF-8").
19    pub encoding: String,
20
21    /// Maximum batch size for composite operations.
22    #[serde(rename = "maxBatchSize")]
23    pub max_batch_size: u32,
24
25    /// List of SObject descriptions.
26    pub sobjects: Vec<SObjectBasicInfo>,
27}
28
29/// Basic information about an SObject from describeGlobal.
30#[derive(Debug, Clone, Deserialize, Serialize)]
31pub struct SObjectBasicInfo {
32    pub name: String,
33    pub label: String,
34    #[serde(rename = "labelPlural")]
35    pub label_plural: String,
36    #[serde(rename = "keyPrefix")]
37    pub key_prefix: Option<String>,
38    pub custom: bool,
39    pub queryable: bool,
40    pub createable: bool,
41    pub updateable: bool,
42    pub deletable: bool,
43    pub searchable: bool,
44    pub retrieveable: bool,
45    #[serde(rename = "customSetting")]
46    pub custom_setting: Option<bool>,
47    #[serde(rename = "deprecatedAndHidden")]
48    pub deprecated_and_hidden: Option<bool>,
49    #[serde(rename = "feedEnabled")]
50    pub feed_enabled: Option<bool>,
51    #[serde(rename = "mruEnabled")]
52    pub mru_enabled: Option<bool>,
53    pub layoutable: Option<bool>,
54    pub triggerable: Option<bool>,
55    pub replicateable: Option<bool>,
56    pub urls: Option<HashMap<String, String>>,
57}
58
59// ============================================================================
60// Describe SObject Types
61// ============================================================================
62
63/// Complete SObject describe result from Salesforce API.
64///
65/// Contains all metadata about an SObject including fields,
66/// relationships, record types, and capabilities.
67#[derive(Debug, Clone, Deserialize, Serialize)]
68pub struct DescribeSObjectResult {
69    // === Identity ===
70    pub name: String,
71    pub label: String,
72    #[serde(rename = "labelPlural")]
73    pub label_plural: Option<String>,
74    #[serde(rename = "keyPrefix")]
75    pub key_prefix: Option<String>,
76    pub custom: bool,
77    #[serde(rename = "customSetting")]
78    pub custom_setting: Option<bool>,
79
80    // === Capabilities (CRUD) ===
81    #[serde(default)]
82    pub createable: bool,
83    #[serde(default)]
84    pub deletable: bool,
85    #[serde(default)]
86    pub queryable: bool,
87    #[serde(default)]
88    pub retrieveable: bool,
89    #[serde(default)]
90    pub searchable: bool,
91    #[serde(default)]
92    pub updateable: bool,
93    pub undeletable: Option<bool>,
94    pub mergeable: Option<bool>,
95    pub replicateable: Option<bool>,
96
97    // === Layout & UI ===
98    pub activateable: Option<bool>,
99    #[serde(rename = "compactLayoutable")]
100    pub compact_layoutable: Option<bool>,
101    #[serde(rename = "deepCloneable")]
102    pub deep_cloneable: Option<bool>,
103    pub layoutable: Option<bool>,
104    pub listviewable: Option<bool>,
105    #[serde(rename = "lookupLayoutable")]
106    pub lookup_layoutable: Option<bool>,
107    #[serde(rename = "searchLayoutable")]
108    pub search_layoutable: Option<bool>,
109    pub triggerable: Option<bool>,
110    #[serde(rename = "mruEnabled")]
111    pub mru_enabled: Option<bool>,
112    #[serde(rename = "feedEnabled")]
113    pub feed_enabled: Option<bool>,
114
115    // === Relationships ===
116    #[serde(rename = "childRelationships", default)]
117    pub child_relationships: Vec<ChildRelationship>,
118    pub fields: Vec<FieldDescribe>,
119
120    // === Record Types ===
121    #[serde(rename = "recordTypeInfos", default)]
122    pub record_type_infos: Vec<RecordTypeInfo>,
123    #[serde(rename = "namedLayoutInfos", default)]
124    pub named_layout_infos: Vec<NamedLayoutInfo>,
125
126    // === Polymorphism & Inheritance ===
127    #[serde(rename = "hasSubtypes")]
128    pub has_subtypes: Option<bool>,
129    #[serde(rename = "isInterface")]
130    pub is_interface: Option<bool>,
131    #[serde(rename = "isSubtype")]
132    pub is_subtype: Option<bool>,
133    #[serde(rename = "defaultImplementation")]
134    pub default_implementation: Option<String>,
135    #[serde(rename = "extendedBy")]
136    pub extended_by: Option<String>,
137    #[serde(rename = "extendsInterfaces")]
138    pub extends_interfaces: Option<String>,
139    #[serde(rename = "implementedBy")]
140    pub implemented_by: Option<String>,
141    #[serde(rename = "implementsInterfaces")]
142    pub implements_interfaces: Option<String>,
143
144    // === API Metadata ===
145    #[serde(rename = "deprecatedAndHidden")]
146    pub deprecated_and_hidden: Option<bool>,
147    #[serde(rename = "sobjectDescribeOption")]
148    pub sobject_describe_option: Option<String>,
149    #[serde(rename = "networkScopeFieldName")]
150    pub network_scope_field_name: Option<String>,
151    #[serde(default)]
152    pub urls: HashMap<String, String>,
153    #[serde(rename = "supportedScopes", default)]
154    pub supported_scopes: Vec<ScopeInfo>,
155    #[serde(rename = "actionOverrides", default)]
156    pub action_overrides: Vec<ActionOverride>,
157}
158
159/// Child relationship metadata for an SObject.
160#[derive(Debug, Clone, Deserialize, Serialize)]
161pub struct ChildRelationship {
162    #[serde(rename = "childSObject")]
163    pub child_sobject: String,
164    pub field: String,
165    #[serde(rename = "relationshipName")]
166    pub relationship_name: Option<String>,
167    #[serde(rename = "deprecatedAndHidden")]
168    pub deprecated_and_hidden: Option<bool>,
169    #[serde(rename = "cascadeDelete")]
170    pub cascade_delete: Option<bool>,
171    #[serde(rename = "restrictedDelete")]
172    pub restricted_delete: Option<bool>,
173    #[serde(rename = "junctionIdListNames", default)]
174    pub junction_id_list_names: Vec<String>,
175    #[serde(rename = "junctionReferenceTo", default)]
176    pub junction_reference_to: Vec<String>,
177}
178
179/// Record type information for an SObject.
180#[derive(Debug, Clone, Deserialize, Serialize)]
181pub struct RecordTypeInfo {
182    pub name: String,
183    #[serde(rename = "recordTypeId")]
184    pub record_type_id: String,
185    #[serde(rename = "developerName")]
186    pub developer_name: Option<String>,
187    pub active: bool,
188    pub available: bool,
189    #[serde(rename = "defaultRecordTypeMapping")]
190    pub default_record_type_mapping: bool,
191    pub master: Option<bool>,
192}
193
194/// Named layout info for an SObject.
195#[derive(Debug, Clone, Deserialize, Serialize)]
196pub struct NamedLayoutInfo {
197    pub name: String,
198}
199
200/// Scope info for SOQL queries.
201#[derive(Debug, Clone, Deserialize, Serialize)]
202pub struct ScopeInfo {
203    pub name: String,
204    pub label: String,
205}
206
207/// Action override for UI customization.
208#[derive(Debug, Clone, Deserialize, Serialize)]
209pub struct ActionOverride {
210    #[serde(rename = "formFactor")]
211    pub form_factor: Option<String>,
212    #[serde(rename = "isAvailableInTouch")]
213    pub is_available_in_touch: Option<bool>,
214    pub name: String,
215    #[serde(rename = "pageId")]
216    pub page_id: Option<String>,
217    pub url: Option<String>,
218}
219
220// ============================================================================
221// Field Describe Types
222// ============================================================================
223
224/// Complete field describe result from Salesforce API.
225///
226/// Contains all metadata about a field including type, size,
227/// capabilities, relationships, and picklist values.
228#[derive(Debug, Clone, Deserialize, Serialize)]
229pub struct FieldDescribe {
230    // === Identity ===
231    pub name: String,
232    pub label: String,
233    #[serde(rename = "type")]
234    pub field_type: String,
235    #[serde(rename = "soapType")]
236    pub soap_type: Option<String>,
237    pub custom: Option<bool>,
238
239    // === Size & Precision ===
240    pub length: Option<i32>,
241    #[serde(rename = "byteLength")]
242    pub byte_length: Option<i32>,
243    pub precision: Option<i32>,
244    pub scale: Option<i32>,
245    pub digits: Option<i32>,
246
247    // === Capabilities ===
248    #[serde(default)]
249    pub createable: bool,
250    #[serde(default)]
251    pub updateable: bool,
252    #[serde(default)]
253    pub nillable: bool,
254    #[serde(default)]
255    pub filterable: bool,
256    #[serde(default)]
257    pub sortable: bool,
258    #[serde(default)]
259    pub groupable: bool,
260    pub aggregatable: Option<bool>,
261    pub searchable: Option<bool>,
262    #[serde(default)]
263    pub unique: bool,
264    pub permissionable: Option<bool>,
265
266    // === Field Characteristics ===
267    #[serde(rename = "externalId", default)]
268    pub external_id: bool,
269    #[serde(rename = "idLookup", default)]
270    pub id_lookup: bool,
271    #[serde(default)]
272    pub calculated: bool,
273    #[serde(rename = "calculatedFormula")]
274    pub calculated_formula: Option<String>,
275    #[serde(rename = "autoNumber", default)]
276    pub auto_number: bool,
277    #[serde(default)]
278    pub encrypted: bool,
279    #[serde(rename = "nameField")]
280    pub name_field: Option<bool>,
281    #[serde(rename = "namePointing")]
282    pub name_pointing: Option<bool>,
283    #[serde(rename = "caseSensitive")]
284    pub case_sensitive: Option<bool>,
285    #[serde(rename = "htmlFormatted")]
286    pub html_formatted: Option<bool>,
287    #[serde(rename = "highScaleNumber")]
288    pub high_scale_number: Option<bool>,
289    #[serde(rename = "displayLocationInDecimal")]
290    pub display_location_in_decimal: Option<bool>,
291    #[serde(rename = "queryByDistance")]
292    pub query_by_distance: Option<bool>,
293
294    // === Defaults ===
295    #[serde(rename = "defaultValue")]
296    pub default_value: Option<serde_json::Value>,
297    #[serde(rename = "defaultValueFormula")]
298    pub default_value_formula: Option<String>,
299    #[serde(rename = "defaultedOnCreate")]
300    pub defaulted_on_create: Option<bool>,
301    #[serde(rename = "formulaTreatNullNumberAsZero")]
302    pub formula_treat_null_number_as_zero: Option<bool>,
303
304    // === Relationships ===
305    #[serde(rename = "referenceTo", default)]
306    pub reference_to: Option<Vec<String>>,
307    #[serde(rename = "relationshipName")]
308    pub relationship_name: Option<String>,
309    #[serde(rename = "relationshipOrder")]
310    pub relationship_order: Option<i32>,
311    #[serde(rename = "referenceTargetField")]
312    pub reference_target_field: Option<String>,
313    #[serde(rename = "polymorphicForeignKey")]
314    pub polymorphic_foreign_key: Option<bool>,
315    #[serde(rename = "cascadeDelete")]
316    pub cascade_delete: Option<bool>,
317    #[serde(rename = "restrictedDelete")]
318    pub restricted_delete: Option<bool>,
319    #[serde(rename = "writeRequiresMasterRead")]
320    pub write_requires_master_read: Option<bool>,
321
322    // === Compound Fields ===
323    #[serde(rename = "compoundFieldName")]
324    pub compound_field_name: Option<String>,
325    #[serde(rename = "extraTypeInfo")]
326    pub extra_type_info: Option<String>,
327
328    // === Picklist ===
329    #[serde(rename = "picklistValues", default)]
330    pub picklist_values: Option<Vec<PicklistValue>>,
331    #[serde(rename = "dependentPicklist")]
332    pub dependent_picklist: Option<bool>,
333    #[serde(rename = "controllerName")]
334    pub controller_name: Option<String>,
335    #[serde(rename = "restrictedPicklist")]
336    pub restricted_picklist: Option<bool>,
337
338    // === Lookup Filters ===
339    #[serde(rename = "filteredLookupInfo")]
340    pub filtered_lookup_info: Option<FilteredLookupInfo>,
341    #[serde(rename = "searchPrefilterable")]
342    pub search_prefilterable: Option<bool>,
343
344    // === Masked Fields ===
345    pub mask: Option<String>,
346    #[serde(rename = "maskType")]
347    pub mask_type: Option<String>,
348
349    // === AI ===
350    #[serde(rename = "aiPredictionField")]
351    pub ai_prediction_field: Option<bool>,
352
353    // === API Metadata ===
354    #[serde(rename = "deprecatedAndHidden")]
355    pub deprecated_and_hidden: Option<bool>,
356    #[serde(rename = "inlineHelpText")]
357    pub inline_help_text: Option<String>,
358}
359
360/// Filtered lookup info for lookup fields.
361#[derive(Debug, Clone, Deserialize, Serialize)]
362pub struct FilteredLookupInfo {
363    #[serde(rename = "controllingFields", default)]
364    pub controlling_fields: Vec<String>,
365    pub dependent: Option<bool>,
366    #[serde(rename = "optionalFilter")]
367    pub optional_filter: Option<bool>,
368}
369
370/// Picklist value for picklist fields.
371#[derive(Debug, Clone, Deserialize, Serialize)]
372pub struct PicklistValue {
373    pub value: String,
374    pub label: String,
375    pub active: bool,
376    #[serde(rename = "defaultValue")]
377    pub default_value: bool,
378    #[serde(rename = "validFor")]
379    pub valid_for: Option<String>,
380}
381
382#[cfg(test)]
383mod tests {
384    use super::*;
385
386    #[test]
387    fn test_describe_global_result_deser() {
388        let json = r#"{
389            "encoding": "UTF-8",
390            "maxBatchSize": 200,
391            "sobjects": [{
392                "name": "Account",
393                "label": "Account",
394                "labelPlural": "Accounts",
395                "keyPrefix": "001",
396                "custom": false,
397                "queryable": true,
398                "createable": true,
399                "updateable": true,
400                "deletable": true,
401                "searchable": true,
402                "retrieveable": true
403            }]
404        }"#;
405
406        let result: DescribeGlobalResult = serde_json::from_str(json).unwrap();
407        assert_eq!(result.encoding, "UTF-8");
408        assert_eq!(result.max_batch_size, 200);
409        assert_eq!(result.sobjects.len(), 1);
410        assert_eq!(result.sobjects[0].name, "Account");
411    }
412
413    #[test]
414    fn test_field_describe_deser() {
415        let json = r#"{
416            "name": "Name",
417            "label": "Account Name",
418            "type": "string",
419            "length": 255,
420            "createable": true,
421            "updateable": true,
422            "nillable": false,
423            "filterable": true,
424            "sortable": true,
425            "groupable": true,
426            "unique": false
427        }"#;
428
429        let field: FieldDescribe = serde_json::from_str(json).unwrap();
430        assert_eq!(field.name, "Name");
431        assert_eq!(field.field_type, "string");
432        assert_eq!(field.length, Some(255));
433        assert!(field.createable);
434        assert!(!field.nillable);
435    }
436
437    #[test]
438    fn test_picklist_value_deser() {
439        let json = r#"{
440            "value": "Hot",
441            "label": "Hot",
442            "active": true,
443            "defaultValue": false
444        }"#;
445
446        let pv: PicklistValue = serde_json::from_str(json).unwrap();
447        assert_eq!(pv.value, "Hot");
448        assert_eq!(pv.label, "Hot");
449        assert!(pv.active);
450        assert!(!pv.default_value);
451    }
452}