use crate::types::describe::SObjectDescribe;
use super::schema_diff::compare_schemas;
#[must_use]
pub fn generate_changelog(old_schema: &SObjectDescribe, new_schema: &SObjectDescribe) -> String {
let mut md = String::with_capacity(1024);
write_changelog(&mut md, old_schema, new_schema);
md
}
pub fn write_changelog(
md: &mut String,
old_schema: &SObjectDescribe,
new_schema: &SObjectDescribe,
) {
use std::fmt::Write;
let diff = compare_schemas(old_schema, new_schema);
let _ = writeln!(md, "# Schema Changelog: {}", new_schema.label);
let _ = writeln!(md, "**API Name:** `{}`\n", new_schema.name);
if diff.is_empty() {
md.push_str("No changes detected.\n");
return;
}
if !diff.added.is_empty() {
md.push_str("## Added Fields\n\n");
md.push_str("| Label | API Name | Type |\n");
md.push_str("|---|---|---|\n");
for field in &diff.added {
let _ = writeln!(
md,
"| {} | `{}` | {:?} |",
field.label, field.name, field.type_
);
}
md.push('\n');
}
if !diff.removed.is_empty() {
md.push_str("## Removed Fields\n\n");
md.push_str("| Label | API Name | Type |\n");
md.push_str("|---|---|---|\n");
for field in &diff.removed {
let _ = writeln!(
md,
"| {} | `{}` | {:?} |",
field.label, field.name, field.type_
);
}
md.push('\n');
}
if !diff.changed.is_empty() {
md.push_str("## Changed Fields\n\n");
md.push_str("| API Name | Old Type | New Type |\n");
md.push_str("|---|---|---|\n");
for change in &diff.changed {
let _ = writeln!(
md,
"| `{}` | {:?} | {:?} |",
change.name, change.old_type, change.new_type
);
}
md.push('\n');
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_support::Must;
use serde_json::json;
fn create_mock_describe(fields_json: &serde_json::Value) -> SObjectDescribe {
let describe_json = json!({
"name": "Account",
"label": "Account",
"custom": false,
"queryable": true,
"activateable": false, "createable": true, "customSetting": false, "deletable": true,
"deprecatedAndHidden": false, "feedEnabled": true, "hasSubtypes": false,
"isSubtype": false, "keyPrefix": "001", "labelPlural": "Accounts", "layoutable": true,
"mergeable": true, "mruEnabled": true, "replicateable": true, "retrieveable": true,
"searchable": true, "triggerable": true, "undeletable": true, "updateable": true,
"urls": {}, "childRelationships": [], "recordTypeInfos": [],
"fields": fields_json.clone()
});
serde_json::from_value(describe_json).must()
}
fn mock_field(name: &str, field_type: &str) -> serde_json::Value {
json!({
"name": name,
"type": field_type,
"label": format!("{} Label", name),
"referenceTo": [],
"aggregatable": true, "autoNumber": false, "byteLength": 18, "calculated": false,
"cascadeDelete": false, "caseSensitive": false, "createable": false, "custom": false,
"defaultedOnCreate": true, "dependentPicklist": false, "deprecatedAndHidden": false,
"digits": 0, "displayLocationInDecimal": false, "encrypted": false, "externalId": false,
"filterable": true, "groupable": true, "highScaleNumber": false, "htmlFormatted": false,
"idLookup": true, "length": 18, "nameField": false, "namePointing": false, "nillable": false,
"permissionable": false, "polymorphicForeignKey": false, "precision": 0, "queryByDistance": false,
"restrictedDelete": false, "restrictedPicklist": false, "scale": 0, "soapType": "tns:ID",
"sortable": true, "unique": false, "updateable": false, "writeRequiresMasterRead": false
})
}
#[test]
fn test_schema_changelog_generator_no_changes() {
let schema_v1 = create_mock_describe(&json!([
mock_field("Id", "id"),
mock_field("Name", "string")
]));
let md = generate_changelog(&schema_v1, &schema_v1);
assert!(md.contains("# Schema Changelog: Account"));
assert!(md.contains("No changes detected."));
}
#[test]
fn test_schema_changelog_generator_with_changes() {
let schema_v1 = create_mock_describe(&json!([
mock_field("Id", "id"),
mock_field("Name", "string"),
mock_field("OldField", "string")
]));
let schema_v2 = create_mock_describe(&json!([
mock_field("Id", "id"),
mock_field("Name", "textarea"), mock_field("NewField", "int") ]));
let md = generate_changelog(&schema_v1, &schema_v2);
assert!(md.contains("# Schema Changelog: Account"));
assert!(md.contains("**API Name:** `Account`"));
assert!(md.contains("## Added Fields"));
assert!(md.contains("| NewField Label | `NewField` | Int |"));
assert!(md.contains("## Removed Fields"));
assert!(md.contains("| OldField Label | `OldField` | String |"));
assert!(md.contains("## Changed Fields"));
assert!(md.contains("| `Name` | String | Textarea |"));
}
}