Skip to content

Commit

Permalink
fix: Update objectEquals function to ignore extra elements in arrays
Browse files Browse the repository at this point in the history
The objectEquals function in object.ts has been updated to include the option to ignore extra elements in arrays when comparing for equality. This change allows arrays with additional elements to be considered equal, improving the flexibility of the function. The updated function now accepts an options object with the 'ignoreExtraElements' property set to true.
  • Loading branch information
Codeneos committed Aug 12, 2024
1 parent d225281 commit 25fecdb
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 33 deletions.
63 changes: 46 additions & 17 deletions packages/salesforce/src/retrieveDeltaStrategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { RetrieveManifestOptions, SalesforceDeployService } from "./salesforceDe
import { SalesforcePackage, SalesforcePackageComponent } from "./deploymentPackage";
import { MetadataRegistry, MetadataType } from "./metadataRegistry";
import { RetrieveResultComponent } from "./deploy";
import { outputFile } from "fs-extra";

/**
* Interface for a strategy to determine if two objects are equal. Used in the delta strategy to determine if a component has changed.
Expand All @@ -13,22 +14,44 @@ interface CompareStrategy {
(a: Buffer | string, b: Buffer | string): boolean
}

type CompareXmlStrategyType = 'xmlStrict' | 'xmlStrictOrder' | 'xml' | 'metaXml';
type CompareStrategyType = CompareXmlStrategyType | 'binary' | 'default';

/**
* Interface for a strategy to determine which components in the packages have changed and need to be deployed.
* Returns a list of components that have changed which can be used to create a new deployment package.
*/
@injectable({ lifecycle: LifecyclePolicy.transient })
export class RetrieveDeltaStrategy {

private readonly compareStrategies : Record<string, CompareStrategy> = {
'xmlStrictOrder': (a, b) => this.isXmlEqual(a, b, { strictOrder: true }),
private readonly compareStrategies: Record<CompareStrategyType | string, CompareStrategy> = {
'xmlStrict': (a, b) => this.isXmlEqual(a, b, { strictOrder: true }),
'xmlStrictOrder': (a, b) => this.isXmlEqual(a, b, { strictOrder: true, ignoreExtra: true }),
'xml': (a, b) => this.isXmlEqual(a, b, { strictOrder: false, ignoreExtra: true }),
'metaXml': (a, b) => this.isMetaXmlEqual(a, b),
'binary': this.isBinaryEqual.bind(this),
'default': this.isStringEqual.bind(this),
// Custom comparers
'InstalledPackage': (a, b) => !this.isPackageNewer(a, b),
}
'InstalledPackage': (a, b) => !this.isPackageNewer(a, b)
};

/**
* Represents the metadata strategy for different types of metadata.
*/
public static readonly metadataStrategy : Record<string, CompareStrategyType | CompareStrategy> = {
'FlexiPage': 'xmlStrict',
'Layout': 'xmlStrict',
'Flow;': 'xmlStrict',
'StaticResource': 'binary',
'ContentAsset': 'binary',
'Document': 'binary',
};

/**
* Determines the default strategy for strict XML parsing.
* This strategy is used when no specific strategy is defined for a metadata type.
* This strategy is used for XML files that are not metadata types.
*/
public static readonly defaultXmlStrategy: CompareXmlStrategyType = 'xml';

constructor(
private readonly deployService: SalesforceDeployService,
Expand Down Expand Up @@ -104,8 +127,8 @@ export class RetrieveDeltaStrategy {
packagePath: string,
localData: Buffer | string | undefined,
orgData: Buffer | string | undefined,
type: MetadataType): boolean
{
type: MetadataType
): boolean {
if (Buffer.isBuffer(localData) && Buffer.isBuffer(orgData) && localData.compare(orgData) === 0) {
// If both are buffers first do a quick buffer comparison
return false;
Expand All @@ -120,9 +143,9 @@ export class RetrieveDeltaStrategy {
}

try {
return !this.getComparer(packagePath, localData, type)(localData, orgData);
return !this.getComparer(packagePath, localData, type)(orgData, localData);
} catch {
return !this.compareStrategies.default(localData, orgData);
return !this.compareStrategies.default(orgData, localData);
}
}

Expand All @@ -131,11 +154,6 @@ export class RetrieveDeltaStrategy {
return this.compareStrategies[type.name];
}

if (type.name === 'FlexiPage' ||
type.name === 'Layout') {
return this.compareStrategies.xmlStrictOrder;
}

if (/\.([a-z]+)-meta\.xml$/i.test(packagePath)) {
return this.compareStrategies.metaXml;
}
Expand All @@ -146,10 +164,20 @@ export class RetrieveDeltaStrategy {
return this.compareStrategies.binary;
}

if (/\.xml$/i.test(packagePath) || XML.isXml(data)) {
return this.compareStrategies.xml;
const strategyName = RetrieveDeltaStrategy.metadataStrategy[type.name];
if (strategyName) {
if (typeof strategyName === 'string') {
if (!(strategyName in this.compareStrategies)) {
throw new Error(`Specified strategy for metadata type ${type.name} does not exist: ${strategyName}`);
}
return this.compareStrategies[strategyName];
}
return strategyName;
}

if (/\.xml$/i.test(packagePath) || XML.isXml(data)) {
return this.compareStrategies[RetrieveDeltaStrategy.defaultXmlStrategy];
}
return this.compareStrategies.default;
}

Expand All @@ -170,7 +198,8 @@ export class RetrieveDeltaStrategy {
return deepCompare(parsedA, parsedB, {
primitiveCompare: this.primitiveCompare,
ignoreArrayOrder: !options?.strictOrder,
ignoreExtraProperties: !!options?.ignoreExtra
ignoreExtraProperties: !!options?.ignoreExtra,
ignoreExtraElements: !!options?.ignoreExtra
});
}

Expand Down
42 changes: 26 additions & 16 deletions packages/util/src/object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,12 @@ export interface ObjectEqualsOptions {
* the objects are considered equal even though object `b` has an extra property
*/
ignoreExtraProperties?: boolean;
/**
* Ignore extra elements in an array when comparing arrays for equality.
* For example when array `a` looks like `[1, 2, 3]` and array `b` looks like `[1, 2, 3, 4]`
* the arrays are considered equal even though array `b` has an extra element.
*/
ignoreExtraElements?: boolean;
}

/**
Expand Down Expand Up @@ -450,6 +456,26 @@ export function objectEquals(
return true;
}

const objectEqualityFn: typeof objectEquals = options?.objectCompare ?? objectEquals;

if (Array.isArray(a) && Array.isArray(b)) {
if (!options?.ignoreExtraElements && a.length !== b.length) {
return false;
}

if (options?.ignoreArrayOrder) {
const bElements = [...b];
for (const element of a) {
const index = bElements.findIndex(otherElement => objectEqualityFn(element, otherElement, options));
if (index === -1) {
return false;
}
bElements.splice(index, 1);
}
return options?.ignoreExtraElements === true || bElements.length === 0;
}
}

const missingKeys = Object.keys(a).filter(key => !(key in b));
if (missingKeys.length && !options?.ignoreMissingProperties) {
return false;
Expand All @@ -460,22 +486,6 @@ export function objectEquals(
return false;
}

const objectEqualityFn: typeof objectEquals = options?.objectCompare ?? objectEquals;

// If both A and B are arrays and the ignoreArrayOrder option is set, then check if all elements of A are in B
// but ignore the order of the elements in B
if (options?.ignoreArrayOrder && Array.isArray(a) && Array.isArray(b)) {
const validElements = [...b];
for (const element of a) {
const index = validElements.findIndex(otherElement => objectEqualityFn(otherElement, element, options));
if (index === -1) {
return false;
}
validElements.splice(index, 1);
}
return options?.ignoreExtraProperties === true || validElements.length === 0;
}

// Check if all keys of A are equal to the keys in B
for (const key of Object.keys(a).filter(key => key in b)) {
if (!objectEqualityFn(a[key], b[key], options)) {
Expand Down

0 comments on commit 25fecdb

Please sign in to comment.