Skip to content

Commit

Permalink
feat!: drop support for lodash-style deep get/set
Browse files Browse the repository at this point in the history
  • Loading branch information
mshanemc committed Oct 18, 2023
1 parent 1b8145d commit b0790f3
Show file tree
Hide file tree
Showing 3 changed files with 15 additions and 88 deletions.
36 changes: 10 additions & 26 deletions src/config/configStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/

import { AsyncOptionalCreatable, cloneJson, set } from '@salesforce/kit';
import { AsyncOptionalCreatable, cloneJson } from '@salesforce/kit';
import { entriesOf, isPlainObject } from '@salesforce/ts-types';
import { definiteEntriesOf, definiteValuesOf, get, isJsonMap, isString, JsonMap, Optional } from '@salesforce/ts-types';
import { definiteEntriesOf, definiteValuesOf, isJsonMap, isString, JsonMap, Optional } from '@salesforce/ts-types';
import { Crypto } from '../crypto/crypto';
import { SfError } from '../sfError';
import { LWWMap, stateFromContents } from './lwwMap';
Expand Down Expand Up @@ -85,24 +85,23 @@ export abstract class BaseConfigStore<
/**
* Returns the value associated to the key, or undefined if there is none.
*
* @param key The key. Supports query key like `a.b[0]`.
* @param key The key (object property)
* @param decrypt If it is an encrypted key, decrypt the value.
* If the value is an object, a clone will be returned.
*/
public get<K extends Key<P>>(key: K, decrypt?: boolean): P[K];
public get<V = ConfigValue>(key: string, decrypt?: boolean): V;
public get<K extends Key<P>>(key: K | string, decrypt = false): P[K] | ConfigValue {
const k = key as string;
let value = this.getMethod(this.contents.value ?? {}, k);
const rawValue = this.contents.get(key as K);

if (this.hasEncryption() && decrypt) {
if (isJsonMap(value)) {
value = this.recursiveDecrypt(cloneJson(value), k);
} else if (this.isCryptoKey(k)) {
value = this.decrypt(value);
if (isJsonMap(rawValue)) {
return this.recursiveDecrypt(cloneJson(rawValue), key);
} else if (this.isCryptoKey(key)) {
return this.decrypt(rawValue) as P[K] | ConfigValue;
}
}
return value as P[K];
return rawValue as P[K] | ConfigValue;
}

/**
Expand Down Expand Up @@ -302,21 +301,6 @@ export abstract class BaseConfigStore<
return this.getEncryptedKeys().length > 0;
}

// Allows extended classes the ability to override the set method. i.e. maybe they want
// nested object set from kit.
// eslint-disable-next-line class-methods-use-this
protected setMethod(contents: ConfigContents, key: string, value?: ConfigValue): void {
set(contents, key, value);
}

// Allows extended classes the ability to override the get method. i.e. maybe they want
// nested object get from ts-types.
// NOTE: Key must stay string to be reliably overwritten.
// eslint-disable-next-line class-methods-use-this
protected getMethod(contents: ConfigContents, key: string): Optional<ConfigValue> {
return get(contents, key) as ConfigValue;
}

// eslint-disable-next-line class-methods-use-this
protected initialContents(): P {
return {} as P;
Expand Down Expand Up @@ -389,7 +373,7 @@ export abstract class BaseConfigStore<
return this.crypto.isEncrypted(value) ? value : this.crypto.encrypt(value);
}

protected decrypt(value: unknown): Optional<string> {
protected decrypt(value: unknown): string | undefined {
if (!value) return;
if (!this.crypto) throw new SfError('crypto is not initialized', 'CryptoNotInitializedError');
if (!isString(value))
Expand Down
1 change: 0 additions & 1 deletion src/config/lwwMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,6 @@ export class LWWMap<P extends ConfigContents> {
else this.#data.set(key, new LWWRegister(this.id, { peer: this.id, timestamp: process.hrtime.bigint(), value }));
}

// TODO: how to handle the deep `get` that is currently allowed ex: get('foo.bar.baz')
public get<K extends Key<P>>(key: K): P[K] | undefined {
// map loses the typing
const value = this.#data.get(key)?.value;
Expand Down
66 changes: 5 additions & 61 deletions test/unit/config/configStoreTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ describe('ConfigStore', () => {
config.get('1').a = 'b';

expect(config.get('1').a).to.equal('b');
expect(config.get('1.a')).to.equal('b');
});

it('updates the object reference', async () => {
Expand Down Expand Up @@ -146,60 +145,6 @@ describe('ConfigStore', () => {
expect(config.get('owner', true).superPassword).to.equal(expected);
});

describe.skip('TODO: set with deep (dots/accessors) keys', () => {
it('encrypts nested query key using dot notation', async () => {
const expected = 'a29djf0kq3dj90d3q';
const config = await CarConfig.create();
config.set('owner.creditCardNumber', expected);
// encrypted
expect(config.get('owner.creditCardNumber')).to.not.equal(expected);
// decrypted
expect(config.get('owner.creditCardNumber', true)).to.equal(expected);
});

it('encrypts nested query key using accessor with single quotes', async () => {
const expected = 'a29djf0kq3dj90d3q';
const config = await CarConfig.create();
config.set('owner["creditCardNumber"]', expected);
// encrypted
expect(config.get("owner['creditCardNumber']")).to.not.equal(expected);
// decrypted
expect(config.get("owner['creditCardNumber']", true)).to.equal(expected);
});

it('encrypts nested query key using accessor with double quotes', async () => {
const expected = 'a29djf0kq3dj90d3q';
const config = await CarConfig.create();
config.set('owner["creditCardNumber"]', expected);
// encrypted
expect(config.get('owner["creditCardNumber"]')).to.not.equal(expected);
// decrypted
expect(config.get('owner["creditCardNumber"]', true)).to.equal(expected);
});

it('encrypts nested query special key using accessor with single quotes', async () => {
const expected = 'a29djf0kq3dj90d3q';
const config = await CarConfig.create();
const query = `owner['${specialKey}']`;
config.set(query, expected);
// encrypted
expect(config.get(query)).to.not.equal(expected);
// decrypted
expect(config.get(query, true)).to.equal(expected);
});

it('encrypts nested query special key using accessor with double quotes', async () => {
const expected = 'a29djf0kq3dj90d3q';
const config = await CarConfig.create();
const query = `owner["${specialKey}"]`;
config.set(query, expected);
// encrypted
expect(config.get(query)).to.not.equal(expected);
// decrypted
expect(config.get(query, true)).to.equal(expected);
});
});

it('decrypt returns copies', async () => {
const expected = 'a29djf0kq3dj90d3q';
const config = await CarConfig.create();
Expand All @@ -213,7 +158,6 @@ describe('ConfigStore', () => {
decryptedOwner.creditCardNumber = 'invalid';
expect(config.get('owner').creditCardNumber).to.not.equal('invalid');
expect(config.get('owner', true).creditCardNumber).to.equal(expected);
expect(config.get('owner.creditCardNumber', true)).to.equal(expected);
});

// Ensures accessToken and refreshToken are both decrypted upon config.get()
Expand All @@ -236,12 +180,12 @@ describe('ConfigStore', () => {
const config = await CarConfig.create();
const owner = { name: 'Bob', creditCardNumber: expected };
config.set('owner', owner);
const encryptedCreditCardNumber = config.get('owner.creditCardNumber');
const encryptedCreditCardNumber = config.get('owner').creditCardNumber;
const contents = config.getContents();
contents.owner.name = 'Tim';
config.setContents(contents);
expect(config.get('owner.name')).to.equal(contents.owner.name);
expect(config.get('owner.creditCardNumber')).to.equal(encryptedCreditCardNumber);
expect(config.get('owner').name).to.equal(contents.owner.name);
expect(config.get('owner').creditCardNumber).to.equal(encryptedCreditCardNumber);
});

it('updates encrypted object', async () => {
Expand All @@ -252,8 +196,8 @@ describe('ConfigStore', () => {

config.update('owner', { creditCardNumber: expected });

expect(config.get('owner.name')).to.equal(owner.name);
expect(config.get('owner.creditCardNumber', true)).to.equal(expected);
expect(config.get('owner').name).to.equal(owner.name);
expect(config.get('owner', true).creditCardNumber).to.equal(expected);
});
});
});

3 comments on commit b0790f3

@svc-cli-bot
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logger Benchmarks - ubuntu-latest

Benchmark suite Current: b0790f3 Previous: 60bfafd Ratio
Child logger creation 479910 ops/sec (±1.29%) 439957 ops/sec (±4.05%) 0.92
Logging a string on root logger 514693 ops/sec (±10.56%) 451881 ops/sec (±15.17%) 0.88
Logging an object on root logger 374561 ops/sec (±12.47%) 254038 ops/sec (±11.54%) 0.68
Logging an object with a message on root logger 251654 ops/sec (±13.03%) 196574 ops/sec (±10.05%) 0.78
Logging an object with a redacted prop on root logger 9029 ops/sec (±189.69%) 237540 ops/sec (±13.68%) 26.31
Logging a nested 3-level object on root logger 238726 ops/sec (±14.31%) 7037 ops/sec (±187.21%) 0.029477308713755517

This comment was automatically generated by workflow using github-action-benchmark.

@svc-cli-bot
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Performance Alert ⚠️

Possible performance regression was detected for benchmark 'Logger Benchmarks - ubuntu-latest'.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 2.

Benchmark suite Current: b0790f3 Previous: 60bfafd Ratio
Logging an object with a redacted prop on root logger 9029 ops/sec (±189.69%) 237540 ops/sec (±13.68%) 26.31

This comment was automatically generated by workflow using github-action-benchmark.

@svc-cli-bot
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logger Benchmarks - windows-latest

Benchmark suite Current: b0790f3 Previous: 60bfafd Ratio
Child logger creation 409466 ops/sec (±2.46%) 360011 ops/sec (±9.42%) 0.88
Logging a string on root logger 512110 ops/sec (±7.97%) 438491 ops/sec (±9.03%) 0.86
Logging an object on root logger 288918 ops/sec (±20.60%) 331694 ops/sec (±12.37%) 1.15
Logging an object with a message on root logger 165984 ops/sec (±24.61%) 218893 ops/sec (±15.88%) 1.32
Logging an object with a redacted prop on root logger 190959 ops/sec (±20.43%) 238278 ops/sec (±17.12%) 1.25
Logging a nested 3-level object on root logger 121341 ops/sec (±25.17%) 173777 ops/sec (±17.96%) 1.43

This comment was automatically generated by workflow using github-action-benchmark.

Please sign in to comment.