Skip to content

Commit

Permalink
feat(core): add mechanism for left shallow merge (#3849)
Browse files Browse the repository at this point in the history
Refs #3845
  • Loading branch information
char0n authored Feb 22, 2024
1 parent 375419a commit 3334c6b
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 11 deletions.
51 changes: 49 additions & 2 deletions packages/apidom-core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ transcluder.transclude(search, replace); // => ArrayElement<[1, 4, 3]>

## Shallow merging

`mergeRight` functions merged members of two or more ObjectElements shallowly
`mergeRight` and `mergeLeft` functions merge members of two or more ObjectElements shallowly
and handles shallow merging of ArrayElements as well.

### API
Expand Down Expand Up @@ -155,6 +155,53 @@ const output = mergeRight.all([ foobar, foobaz, bar ]);
// => ObjectElement({ foo: { baz: 4 }, bar: 'yay!' })
```

#### mergeLeft(source, target, [options])

Merges two ApiDOM elements source and target shallowly, returning a new merged ApiDOM element with the elements
from both target and source. If an element at the same key is present for both target and source,
the value from source will appear in the result. Merging creates a new ApiDOM element,
so that neither target nor source is modified (operation is immutable).

```js
import { mergeLeft, ObjectElement } from '@swagger-api/apidom-core';

const x = new ObjectElement({
foo: { bar: 3 },
});

const y = new ObjectElement({
foo: { baz: 4 },
quux: 5,
});

const output = mergeLeft(x, y);
// =>
// ObjectElement({
// foo: ObjectElement({
// bar: 3,
// }),
// quux: 5,
// })
```

#### mergeLeft.all([element1, element2, ...], [options])

Merges shallowly any number of ApiDOM elements into a single ApiDOM element.

```js
import { mergeLeft, ObjectElement } from '@swagger-api/apidom-core';

const foobar = new ObjectElement({ foo: { bar: 3 } });
const foobaz = new ObjectElement({ foo: { baz: 4 } });
const bar = new ObjectElement({ bar: 'yay!' });

const output = mergeLeft.all([ foobar, foobaz, bar ]);
// => ObjectElement({ foo: { baz: 3 }, bar: 'yay!' })
```

### Shallow merge Options

`mergeRight` and `mergeLeft` take the same options as [deepmerge](#deepmerge-options), except for `customMerge` and `clone`.

## Deep merging

Expand Down Expand Up @@ -238,7 +285,7 @@ const output = deepmerge.all([ foobar, foobaz, bar ]);
// => ObjectElement({ foo: { bar: 3, baz: 4 }, bar: 'yay!' })
```

### Options
### Deepmerge Options

#### arrayElementMerge

Expand Down
3 changes: 2 additions & 1 deletion packages/apidom-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ export { default as sexprs } from './transformers/sexprs';

export { default as deepmerge } from './deepmerge';
export type { DeepMergeUserOptions, ObjectOrArrayElement } from './deepmerge';

export { default as mergeRight } from './merge/merge-right';
export type { MergeRightOptions } from './merge/merge-right';
export { default as mergeLeft } from './merge/merge-left';
export type { MergeRightOptions as MergeLeftOptions } from './merge/merge-right';
11 changes: 11 additions & 0 deletions packages/apidom-core/src/merge/merge-left.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import mergeRight from './merge-right';

const mergeLeft = (...[sourceElement, targetElement, options]: Parameters<typeof mergeRight>) => {
return mergeRight(targetElement, sourceElement, options);
};

mergeLeft.all = (...[list, options]: Parameters<typeof mergeRight.all>) => {
return mergeRight.all([...list].reverse(), options);
};

export default mergeLeft;
4 changes: 2 additions & 2 deletions packages/apidom-core/test/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import 'mocha';
import * as fs from 'node:fs';
import * as path from 'node:path';
import { assert } from 'chai';
import openapi3_1 from '@swagger-api/apidom-ns-openapi-3-1';
import ApiDOMParser from '@swagger-api/apidom-parser';
import * as openapi3_1Adapter from '@swagger-api/apidom-parser-adapter-openapi-json-3-1';
Expand All @@ -15,6 +15,6 @@ describe('apidom', function () {
it('test', async function () {
const parseResult = await parser.parse(spec);

console.dir(apiDOM.dehydrate(parseResult, namespace));
assert.isDefined(apiDOM.dehydrate(parseResult, namespace));
});
});
45 changes: 45 additions & 0 deletions packages/apidom-core/test/merge/merge-left.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { assert } from 'chai';

import { toValue, ObjectElement, mergeLeft } from '../../src';

/**
* mergeLeft is just a specialization of mergeRight. We don't need tests here
* as they would become redundant with mergeRight tests.
*/
describe('mergeLeft', function () {
it('should merge existing simple keys in target at the roots', function () {
const source = new ObjectElement({ key1: 'changed', key2: 'value2' });
const target = new ObjectElement({ key1: 'value1', key3: 'value3' });
const merged = mergeLeft(target, source);
const expected = {
key1: 'value1',
key2: 'value2',
key3: 'value3',
};

assert.deepEqual(
toValue(target),
{ key1: 'value1', key3: 'value3' },
'merge should be immutable',
);
assert.deepEqual(toValue(merged), expected);
});

it('mergeLeft.all', function () {
const source = new ObjectElement({ key1: 'changed', key2: 'value2' });
const target = new ObjectElement({ key1: 'value1', key3: 'value3' });
const merged = mergeLeft.all([target, source]);
const expected = {
key1: 'value1',
key2: 'value2',
key3: 'value3',
};

assert.deepEqual(
toValue(target),
{ key1: 'value1', key3: 'value3' },
'merge should be immutable',
);
assert.deepEqual(toValue(merged), expected);
});
});
11 changes: 5 additions & 6 deletions packages/apidom-core/test/merge/merge-right.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { assert } from 'chai';

import {
deepmerge,
toValue,
Element,
ObjectElement,
Expand All @@ -24,7 +23,7 @@ describe('mergeRight', function () {
it('should merge existing simple keys in target at the roots', function () {
const source = new ObjectElement({ key1: 'changed', key2: 'value2' });
const target = new ObjectElement({ key1: 'value1', key3: 'value3' });
const merged = deepmerge(target, source);
const merged = mergeRight(target, source);
const expected = {
key1: 'changed',
key2: 'value2',
Expand Down Expand Up @@ -117,7 +116,7 @@ describe('mergeRight', function () {
assert.deepEqual(toValue(merged), expected);
});

it('should clone source and target', function () {
it('should not clone source and target', function () {
const source = new ObjectElement({
b: {
c: 'foo',
Expand All @@ -128,7 +127,7 @@ describe('mergeRight', function () {
d: 'bar',
},
});
const merged = deepmerge(target, source) as ObjectElement;
const merged = mergeRight(target, source) as ObjectElement;
const expected = {
a: {
d: 'bar',
Expand All @@ -139,8 +138,8 @@ describe('mergeRight', function () {
};

assert.deepEqual(toValue(merged), expected);
assert.notStrictEqual(merged.get('a'), target.get('a'));
assert.notStrictEqual(merged.get('b'), source.get('b'));
assert.strictEqual(merged.get('a'), target.get('a'));
assert.strictEqual(merged.get('b'), source.get('b'));
});

it('should replace object with simple key in target', function () {
Expand Down

0 comments on commit 3334c6b

Please sign in to comment.