Skip to content

Commit

Permalink
array of non-primitive types support
Browse files Browse the repository at this point in the history
  • Loading branch information
Enity committed Sep 7, 2022
1 parent 225f854 commit 1b89bc9
Show file tree
Hide file tree
Showing 7 changed files with 167 additions and 43 deletions.
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ class AppConfig {
- [Basic usage](#basic-usage)
- [Advanced usage](#advanced-usage)
- [Nested configs](#nested-configs)
- [Array of non-primitive types](#array-of-non-primitive-types)
- [Compile options reference](#compile-options-reference)
- [Error handling](#error-handling)
- [Documentation generator](#documentation-generator)
Expand Down Expand Up @@ -181,6 +182,33 @@ const main = async () => {
};
```

## Array of non-primitive types

```typescript
import { compileConfig, ConfigField } from '@monkee/turbo-config';

class Repository {
@ConfigField()
url!: string;

@ConfigField()
token!: string;
}

class AppConfig {
@ConfigField({ arrayOf: Repository })
repositories!: Repository[];
}

const main = async () => {
process.env.REPOSITORIES = 'url=first;token=someToken,url=second;token=secret';

const { config } = await compileConfig(AppConfig);

console.log(config.repositories);
};
```

## Compile options reference

```typescript
Expand Down
2 changes: 2 additions & 0 deletions src/compiler/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export type CompileResult<T> = {
const getValueBySourcePriority = (
values: ValuesBySource,
priority: CONFIG_SOURCE[],
mergeArrays: boolean,
): unknown => {
const latestDefined = priority
.slice()
Expand Down Expand Up @@ -139,6 +140,7 @@ const buildRawConfig = <T extends object>(
[CONFIG_SOURCE.CLI]: cliVal,
},
opts.sourcesPriority!,
true,
);

rawConfig[propertyName] = prioritizedValue;
Expand Down
27 changes: 25 additions & 2 deletions src/decorators/config-field-decorator.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import type { ClassConstructor } from 'class-transformer';
import { Type } from 'class-transformer';
import { IsBoolean, IsNumber, IsString, ValidateNested } from 'class-validator';
import {
IsArray,
IsBoolean,
IsNumber,
IsString,
ValidateNested,
} from 'class-validator';
import { TurboConfigCompileError } from '../errors';
import {
ArrayOfClassesTransformer,
ArrayOfFloatsTransformer,
ArrayOfIntsTransformer,
ArrayOfStringsTransformer,
Expand All @@ -11,7 +19,11 @@ import {
import { CliKey, EnvKey, GenericKey, NestedKey, YamlKey } from './decorators';
import { getPropertyType } from './metadata';

export type ArrayOfOptions = 'strings' | 'ints' | 'floats';
export type ArrayOfOptions =
| 'strings'
| 'ints'
| 'floats'
| ClassConstructor<unknown>;

export type ConfigFieldOptions = {
arrayOf?: ArrayOfOptions;
Expand Down Expand Up @@ -64,6 +76,17 @@ const getArrayDecorators = (type: ArrayOfOptions, separator: string) => {
}),
IsNumber(undefined, { each: true }),
];
default:
return [
IsArray(),
ValidateNested({ each: true }),
Type(() => type),
ArrayOfClassesTransformer({
separator,
throwOnInvalidValue: true,
type,
}),
];
}
};

Expand Down
38 changes: 0 additions & 38 deletions src/dev-index.ts

This file was deleted.

32 changes: 31 additions & 1 deletion src/transform-helpers/common-transformers.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Transform } from 'class-transformer';
import type { ClassConstructor } from 'class-transformer';
import { plainToInstance, Transform } from 'class-transformer';
import { arrayParse, booleanParse, floatParse, intParse } from './parsers';

export type CommonTransformerArgs = {
Expand Down Expand Up @@ -96,3 +97,32 @@ export const ArrayOfFloatsTransformer = (
return parsedArray.map((val) => floatParse(val, args.throwOnInvalidValue));
});
};

export const ArrayOfClassesTransformer = (
args: ArrayTransformerArgs & { type?: ClassConstructor<unknown> } = {
separator: ',',
throwOnInvalidValue: true,
},
) => {
return Transform(({ value }) => {
const parsedArray = arrayParse(value, args.separator ?? ',');

return parsedArray.map((val) => {
if (typeof val === 'string' && args.type !== undefined) {
const parsedVal = val
.split(';')
.reduce<Record<string, string>>((accum, entry) => {
const [key, value] = entry.split('=');
if (key !== undefined && value !== undefined) {
accum[key] = value;
}

return accum;
}, {});

return plainToInstance(args.type, parsedVal);
}
return val;
});
});
};
80 changes: 78 additions & 2 deletions tests/complex-config.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,17 @@ import {

describe('Complex config positive scenario (e2e)', () => {
it('Complex config', async () => {
class Repository {
@ConfigField()
url!: string;

@ConfigField()
token!: string;

@ConfigField()
someFlag: boolean = false;
}

class Nested {
@ConfigField()
host!: string;
Expand All @@ -23,6 +34,15 @@ describe('Complex config positive scenario (e2e)', () => {
@ConfigField({ nested: true, nestedKey: 'db.mysql' })
dbMysql!: Nested;

@ConfigField({ arrayOf: Repository })
repositories!: Repository[];

@ConfigField({ arrayOf: Repository })
repositoriesEnvs!: Repository[];

@ConfigField({ arrayOf: Repository })
repositoriesCli!: Repository[];

@ConfigField({ genericKey: 'app.port_mistake_use_default' })
appPort: number = 8989;

Expand All @@ -39,8 +59,18 @@ describe('Complex config positive scenario (e2e)', () => {
arrFromArgs!: number[];
}

setEnvs(['TASKS', 'one:two:three'], ['APP_PORT', '8989']);
setArgs('--some.arrayOfInts=1,6,10');
setEnvs(
['TASKS', 'one:two:three'],
['APP_PORT', '8989'],
[
'REPOSITORIES_ENVS',
'url=https://github.com/1;token=someToken;someFlag=true',
],
);
setArgs(
'--some.arrayOfInts=1,6,10',
'--repositoriesCli=url=https://gitpop.com/555;token=gitpopToken;',
);

const { config, configSchema } = await compileConfig(ComplexConfig, {
sourcesPriority: [
Expand All @@ -60,6 +90,25 @@ describe('Complex config positive scenario (e2e)', () => {
expected.dbMysql = new Nested();
expected.dbMysql.autoReconnect = true;
expected.dbMysql.host = 'notLocalhost';

const repository1 = new Repository();
repository1.url = 'https://gitlab.com/123';
repository1.token = 'secretToken';
repository1.someFlag = false;
expected.repositories = [repository1];

const repository2 = new Repository();
repository2.url = 'https://github.com/1';
repository2.token = 'someToken';
repository2.someFlag = true;
expected.repositoriesEnvs = [repository2];

const repository3 = new Repository();
repository3.url = 'https://gitpop.com/555';
repository3.token = 'gitpopToken';
repository3.someFlag = false;
expected.repositoriesCli = [repository3];

expected.appHost = 'localhost';
expected.appPort = 8989;
expected.tasks = ['one', 'two', 'three'];
Expand Down Expand Up @@ -115,6 +164,33 @@ describe('Complex config positive scenario (e2e)', () => {
defaultValue: undefined,
keys: { env: 'INTS_ARR', yaml: 'intsArray', cli: 'intsArr' },
},
repositories: {
type: Array,
defaultValue: undefined,
keys: {
env: 'REPOSITORIES',
yaml: 'repositories',
cli: 'repositories',
},
},
repositoriesEnvs: {
type: Array,
defaultValue: undefined,
keys: {
env: 'REPOSITORIES_ENVS',
yaml: 'repositoriesEnvs',
cli: 'repositoriesEnvs',
},
},
repositoriesCli: {
type: Array,
defaultValue: undefined,
keys: {
env: 'REPOSITORIES_CLI',
yaml: 'repositoriesCli',
cli: 'repositoriesCli',
},
},
arrFromArgs: {
type: Array,
defaultValue: undefined,
Expand Down
3 changes: 3 additions & 0 deletions tests/files/configs/complex-config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,6 @@ intsArray:
- 1
- 2
- 3
repositories:
- url: https://gitlab.com/123
token: secretToken

0 comments on commit 1b89bc9

Please sign in to comment.