Skip to content

Commit

Permalink
[7.6] adapt ObjectToConfigAdapter.getFlattenedPaths to consider array…
Browse files Browse the repository at this point in the history
…s as final values (#56105) (#58998)

* adapt ObjectToConfigAdapter.getFlattenedPaths to consider arrays as final values (#56105)

* adapt getFlattenedPaths to consider arrays as final values

* add getUnusedConfigKeys test

* improve tests

* backport getFlattenedObject util
  • Loading branch information
pgayvallet authored Mar 2, 2020
1 parent 5ddabf8 commit c2aafce
Show file tree
Hide file tree
Showing 6 changed files with 173 additions and 15 deletions.
53 changes: 53 additions & 0 deletions src/core/server/config/object_to_config_adapter.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import { ObjectToConfigAdapter } from './object_to_config_adapter';

describe('ObjectToConfigAdapter', () => {
describe('#getFlattenedPaths()', () => {
it('considers arrays as final values', () => {
const data = {
string: 'string',
array: ['an', 'array'],
};
const config = new ObjectToConfigAdapter(data);

expect(config.getFlattenedPaths()).toEqual(['string', 'array']);
});

it('handles nested arrays', () => {
const data = {
string: 'string',
array: ['an', 'array'],
nested: {
number: 12,
array: [{ key: 1 }, { key: 2 }],
},
};
const config = new ObjectToConfigAdapter(data);

expect(config.getFlattenedPaths()).toEqual([
'string',
'array',
'nested.number',
'nested.array',
]);
});
});
});
17 changes: 2 additions & 15 deletions src/core/server/config/object_to_config_adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import { cloneDeep, get, has, set } from 'lodash';

import { getFlattenedObject } from '../../utils';
import { Config, ConfigPath } from './';

/**
Expand All @@ -41,24 +42,10 @@ export class ObjectToConfigAdapter implements Config {
}

public getFlattenedPaths() {
return [...flattenObjectKeys(this.rawConfig)];
return Object.keys(getFlattenedObject(this.rawConfig));
}

public toRaw() {
return cloneDeep(this.rawConfig);
}
}

function* flattenObjectKeys(
obj: { [key: string]: any },
path: string = ''
): IterableIterator<string> {
if (typeof obj !== 'object' || obj === null) {
yield path;
} else {
for (const [key, value] of Object.entries(obj)) {
const newPath = path !== '' ? `${path}.${key}` : key;
yield* flattenObjectKeys(value, newPath);
}
}
}
18 changes: 18 additions & 0 deletions src/core/server/legacy/config/get_unused_config_keys.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,24 @@ describe('getUnusedConfigKeys', () => {
).toEqual(['foo.dolly']);
});

it('handles array values', async () => {
expect(
await getUnusedConfigKeys({
coreHandledConfigPaths: ['core', 'array'],
pluginSpecs: [],
disabledPluginSpecs: [],
settings: {
core: {
prop: 'value',
array: [1, 2, 3],
},
array: ['some', 'values'],
},
legacyConfig: getConfig({}),
})
).toEqual([]);
});

describe('using deprecation', () => {
it('should use the plugin deprecations provider', async () => {
expect(
Expand Down
46 changes: 46 additions & 0 deletions src/core/utils/get_flattened_object.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import { getFlattenedObject } from './get_flattened_object';

describe('getFlattenedObject()', () => {
it('throws when rootValue is not an object or is an array', () => {
expect(() => getFlattenedObject(1 as any)).toThrowError();
expect(() => getFlattenedObject(Infinity as any)).toThrowError();
expect(() => getFlattenedObject(NaN as any)).toThrowError();
expect(() => getFlattenedObject(false as any)).toThrowError();
expect(() => getFlattenedObject(null as any)).toThrowError();
expect(() => getFlattenedObject(undefined as any)).toThrowError();
expect(() => getFlattenedObject([])).toThrowError();
});

it('flattens objects', () => {
expect(getFlattenedObject({ a: 'b' })).toEqual({ a: 'b' });
expect(getFlattenedObject({ a: { b: 'c' } })).toEqual({ 'a.b': 'c' });
expect(getFlattenedObject({ a: { b: 'c' }, d: { e: 'f' } })).toEqual({
'a.b': 'c',
'd.e': 'f',
});
});

it('does not flatten arrays', () => {
expect(getFlattenedObject({ a: ['b'] })).toEqual({ a: ['b'] });
expect(getFlattenedObject({ a: { b: ['c', 'd'] } })).toEqual({ 'a.b': ['c', 'd'] });
});
});
53 changes: 53 additions & 0 deletions src/core/utils/get_flattened_object.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

function shouldReadKeys(value: unknown): value is Record<string, any> {
return typeof value === 'object' && value !== null && !Array.isArray(value);
}

/**
* Flattens a deeply nested object to a map of dot-separated
* paths pointing to all primitive values **and arrays**
* from `rootValue`.
*
* example:
* getFlattenedObject({ a: { b: 1, c: [2,3] } })
* // => { 'a.b': 1, 'a.c': [2,3] }
*
* @param {Object} rootValue
* @returns {Object}
*/
export function getFlattenedObject(rootValue: Record<string, any>) {
if (!shouldReadKeys(rootValue)) {
throw new TypeError(`Root value is not flatten-able, received ${rootValue}`);
}

const result: { [key: string]: any } = {};
(function flatten(prefix, object) {
for (const [key, value] of Object.entries(object)) {
const path = prefix ? `${prefix}.${key}` : key;
if (shouldReadKeys(value)) {
flatten(path, value);
} else {
result[path] = value;
}
}
})('', rootValue);
return result;
}
1 change: 1 addition & 0 deletions src/core/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,4 @@ export * from './pick';
export * from './promise';
export * from './url';
export * from './unset';
export * from './get_flattened_object';

0 comments on commit c2aafce

Please sign in to comment.