use crate::types::SalesforceId;
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Attributes {
#[serde(rename = "type")]
pub type_: String,
pub url: String,
}
impl Attributes {
#[must_use]
pub fn new(type_name: impl Into<String>, id: &SalesforceId, api_version: &str) -> Self {
let type_ = type_name.into();
let url = format!(
"/services/data/{}/sobjects/{}/{}",
api_version,
type_,
id.as_str()
);
Self { type_, url }
}
#[must_use]
pub fn object_type(&self) -> &str {
&self.type_
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct DynamicSObject {
pub attributes: Attributes,
#[serde(flatten)]
pub fields: Map<String, Value>,
}
impl DynamicSObject {
#[must_use]
pub fn new(attributes: Attributes) -> Self {
Self {
attributes,
fields: Map::new(),
}
}
pub fn from_value(value: Value) -> Result<Self, serde_json::Error> {
serde_json::from_value(value)
}
#[must_use]
pub fn get_field(&self, name: &str) -> Option<&Value> {
self.fields.get(name)
}
pub fn get_field_as<T: for<'de> Deserialize<'de>>(
&self,
name: &str,
) -> Result<Option<T>, serde_json::Error> {
match self.fields.get(name) {
Some(value) => T::deserialize(value).map(Some),
None => Ok(None),
}
}
pub fn set_field(&mut self, name: impl Into<String>, value: impl Serialize) {
if let Ok(json_value) = serde_json::to_value(value) {
self.fields.insert(name.into(), json_value);
}
}
pub fn remove_field(&mut self, name: &str) -> Option<Value> {
self.fields.remove(name)
}
#[must_use]
pub fn has_field(&self, name: &str) -> bool {
self.fields.contains_key(name)
}
#[must_use]
pub fn object_type(&self) -> &str {
&self.attributes.type_
}
pub fn field_names(&self) -> impl Iterator<Item = &String> {
self.fields.keys()
}
#[must_use]
pub fn field_count(&self) -> usize {
self.fields.len()
}
pub fn to_value(&self) -> Result<Value, serde_json::Error> {
serde_json::to_value(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_support::Must;
use serde_json::json;
#[test]
fn test_attributes_new() {
let id = SalesforceId::new("001000000000001AAA").must();
let attrs = Attributes::new("Account", &id, "v60.0");
assert_eq!(attrs.type_, "Account");
assert_eq!(
attrs.url,
"/services/data/v60.0/sobjects/Account/001000000000001AAA"
);
}
#[test]
fn test_attributes_object_type() {
let id = SalesforceId::new("001000000000001AAA").must();
let attrs = Attributes::new("Contact", &id, "v60.0");
assert_eq!(attrs.object_type(), "Contact");
}
#[test]
fn test_attributes_serialize() {
let id = SalesforceId::new("001000000000001AAA").must();
let attrs = Attributes::new("Account", &id, "v60.0");
let json = serde_json::to_string(&attrs).must();
assert!(json.contains("\"type\":\"Account\""));
assert!(json.contains("\"url\":"));
}
#[test]
fn test_attributes_deserialize() {
let json = r#"{
"type": "Account",
"url": "/services/data/v60.0/sobjects/Account/001000000000001AAA"
}"#;
let attrs: Attributes = serde_json::from_str(json).must();
assert_eq!(attrs.type_, "Account");
}
#[test]
fn test_dynamic_sobject_new() {
let id = SalesforceId::new("001000000000001AAA").must();
let attrs = Attributes::new("Account", &id, "v60.0");
let sobject = DynamicSObject::new(attrs);
assert_eq!(sobject.object_type(), "Account");
assert_eq!(sobject.field_count(), 0);
}
#[test]
fn test_dynamic_sobject_set_and_get_field() {
let id = SalesforceId::new("001000000000001AAA").must();
let attrs = Attributes::new("Account", &id, "v60.0");
let mut sobject = DynamicSObject::new(attrs);
sobject.set_field("Name", "Acme Corp");
sobject.set_field("Industry", "Technology");
assert_eq!(
sobject.get_field("Name").and_then(|v| v.as_str()),
Some("Acme Corp")
);
assert_eq!(
sobject.get_field("Industry").and_then(|v| v.as_str()),
Some("Technology")
);
}
#[test]
fn test_dynamic_sobject_get_field_as() {
let id = SalesforceId::new("001000000000001AAA").must();
let attrs = Attributes::new("Account", &id, "v60.0");
let mut sobject = DynamicSObject::new(attrs);
sobject.set_field("AnnualRevenue", 1_000_000);
let revenue: Option<i64> = sobject.get_field_as("AnnualRevenue").must();
assert_eq!(revenue, Some(1_000_000));
}
#[test]
fn test_dynamic_sobject_get_field_as_type_mismatch() {
let id = SalesforceId::new("001000000000001AAA").must();
let attrs = Attributes::new("Account", &id, "v60.0");
let mut sobject = DynamicSObject::new(attrs);
sobject.set_field("Name", "Acme Corp");
let result: Result<Option<i64>, _> = sobject.get_field_as("Name");
let Err(err) = result else {
panic!("Expected an error");
};
assert!(err.to_string().contains(""));
}
#[test]
fn test_dynamic_sobject_has_field() {
let id = SalesforceId::new("001000000000001AAA").must();
let attrs = Attributes::new("Account", &id, "v60.0");
let mut sobject = DynamicSObject::new(attrs);
sobject.set_field("Name", "Acme Corp");
assert!(sobject.has_field("Name"));
assert!(!sobject.has_field("Industry"));
}
#[test]
fn test_dynamic_sobject_remove_field() {
let id = SalesforceId::new("001000000000001AAA").must();
let attrs = Attributes::new("Account", &id, "v60.0");
let mut sobject = DynamicSObject::new(attrs);
sobject.set_field("Name", "Acme Corp");
assert!(sobject.has_field("Name"));
let removed = sobject.remove_field("Name");
assert!(removed.is_some());
assert!(!sobject.has_field("Name"));
}
#[test]
fn test_dynamic_sobject_field_names() {
let id = SalesforceId::new("001000000000001AAA").must();
let attrs = Attributes::new("Account", &id, "v60.0");
let mut sobject = DynamicSObject::new(attrs);
sobject.set_field("Name", "Acme Corp");
sobject.set_field("Industry", "Technology");
let names: Vec<&String> = sobject.field_names().collect();
assert_eq!(names.len(), 2);
assert!(names.contains(&&"Name".to_string()));
assert!(names.contains(&&"Industry".to_string()));
}
#[test]
fn test_dynamic_sobject_field_count() {
let id = SalesforceId::new("001000000000001AAA").must();
let attrs = Attributes::new("Account", &id, "v60.0");
let mut sobject = DynamicSObject::new(attrs);
assert_eq!(sobject.field_count(), 0);
sobject.set_field("Name", "Acme Corp");
assert_eq!(sobject.field_count(), 1);
sobject.set_field("Industry", "Technology");
assert_eq!(sobject.field_count(), 2);
}
#[test]
fn test_dynamic_sobject_serialize() {
let id = SalesforceId::new("001000000000001AAA").must();
let attrs = Attributes::new("Account", &id, "v60.0");
let mut sobject = DynamicSObject::new(attrs);
sobject.set_field("Name", "Acme Corp");
let json = serde_json::to_string(&sobject).must();
assert!(json.contains("\"attributes\""));
assert!(json.contains("\"Name\":\"Acme Corp\""));
}
#[test]
fn test_dynamic_sobject_deserialize() {
let json = json!({
"attributes": {
"type": "Account",
"url": "/services/data/v60.0/sobjects/Account/001000000000001AAA"
},
"Name": "Acme Corp",
"Industry": "Technology"
});
let sobject: DynamicSObject = serde_json::from_value(json).must();
assert_eq!(sobject.object_type(), "Account");
assert_eq!(
sobject.get_field("Name").and_then(|v| v.as_str()),
Some("Acme Corp")
);
}
#[test]
fn test_dynamic_sobject_from_value() {
let json = json!({
"attributes": {
"type": "Contact",
"url": "/services/data/v60.0/sobjects/Contact/003000000000001AAA"
},
"FirstName": "John",
"LastName": "Doe"
});
let sobject = DynamicSObject::from_value(json).must();
assert_eq!(sobject.object_type(), "Contact");
assert_eq!(sobject.field_count(), 2);
}
#[test]
fn test_dynamic_sobject_to_value() {
let id = SalesforceId::new("001000000000001AAA").must();
let attrs = Attributes::new("Account", &id, "v60.0");
let mut sobject = DynamicSObject::new(attrs);
sobject.set_field("Name", "Acme Corp");
let value = sobject.to_value().must();
assert!(value.is_object());
assert!(value.get("attributes").is_some());
assert!(value.get("Name").is_some());
}
#[test]
fn test_manual_construction_basic() {
let id = SalesforceId::new("001000000000001AAA").must();
let attrs = Attributes::new("Account", &id, "v60.0");
let mut account = DynamicSObject::new(attrs);
account.set_field("Name", "Acme Corp");
assert_eq!(account.object_type(), "Account");
assert_eq!(
account.get_field("Name").and_then(|v| v.as_str()),
Some("Acme Corp")
);
}
#[test]
fn test_manual_construction_multiple_fields() {
let id = SalesforceId::new("001000000000001AAA").must();
let attrs = Attributes::new("Account", &id, "v60.0");
let mut account = DynamicSObject::new(attrs);
account.set_field("Name", "Acme Corp");
account.set_field("Industry", "Technology");
account.set_field("AnnualRevenue", 1_000_000);
assert_eq!(account.field_count(), 3);
}
#[test]
fn test_manual_construction_empty() {
let id = SalesforceId::new("001000000000001AAA").must();
let attrs = Attributes::new("Account", &id, "v60.0");
let account = DynamicSObject::new(attrs);
assert_eq!(account.field_count(), 0);
assert_eq!(account.object_type(), "Account");
}
#[test]
fn test_dynamic_sobject_from_value_missing_attributes() {
let json = json!({
"Name": "Acme Corp",
"Industry": "Technology"
});
let result = DynamicSObject::from_value(json);
assert!(result.is_err(), "Expected error for missing attributes");
}
#[test]
fn test_dynamic_sobject_remove_field_nonexistent() {
let id = SalesforceId::new("001000000000001AAA").must();
let attrs = Attributes::new("Account", &id, "v60.0");
let mut sobject = DynamicSObject::new(attrs);
let removed = sobject.remove_field("NonexistentField");
assert!(removed.is_none());
}
#[test]
fn test_dynamic_sobject_get_field_nonexistent() {
let id = SalesforceId::new("001000000000001AAA").must();
let attrs = Attributes::new("Account", &id, "v60.0");
let sobject = DynamicSObject::new(attrs);
assert!(sobject.get_field("NonexistentField").is_none());
let result: Result<Option<String>, _> = sobject.get_field_as("NonexistentField");
assert_eq!(result.must(), None);
}
#[test]
fn test_roundtrip_serialization() {
let id = SalesforceId::new("001000000000001AAA").must();
let attrs = Attributes::new("Account", &id, "v60.0");
let mut original = DynamicSObject::new(attrs);
original.set_field("Name", "Acme Corp");
original.set_field("Industry", "Technology");
let json = serde_json::to_string(&original).must();
let deserialized: DynamicSObject = serde_json::from_str(&json).must();
assert_eq!(original, deserialized);
}
}