Skip to content

Commit b39c92c

Browse files
vsamofalNikaple
authored andcommitted
feature: implemented ability to set default values for file loaders, with dotenv-expand syntax
1 parent 9f83070 commit b39c92c

File tree

4 files changed

+84
-9
lines changed

4 files changed

+84
-9
lines changed

lib/loader/file-loader.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { basename, dirname } from 'path';
33
import { debug } from '../utils/debug.util';
44
import { loadPackage } from '../utils/load-package.util';
55

6+
const DEFAULT_VALUE_SEPARATOR = ':-';
7+
68
let parseToml: any;
79
let cosmiconfig: any;
810

@@ -165,7 +167,9 @@ function transformFileLoaderResult(
165167
const match = obj.match(/\$\{(.+?)\}/g);
166168
if (match) {
167169
for (const placeholder of match) {
168-
const variable = placeholder.slice(2, -1);
170+
const { variable, defaultValue } =
171+
extractVariableNameAndDefaultValue(placeholder);
172+
169173
let resolvedValue = resolveReference(variable, context);
170174

171175
if (obj === resolvedValue) {
@@ -219,6 +223,8 @@ function transformFileLoaderResult(
219223
.replace(placeholder, resolvedValue.toString());
220224
}
221225
}
226+
} else if (defaultValue !== undefined) {
227+
obj = defaultValue;
222228
} else if (disallowUndefinedEnvironmentVariables) {
223229
throw new Error(
224230
`Environment variable is not set for variable name: '${variable}'`,
@@ -263,3 +269,20 @@ function transformFileLoaderResult(
263269
}
264270
return obj;
265271
}
272+
273+
function extractVariableNameAndDefaultValue(placeholder: string): {
274+
variable: string;
275+
defaultValue?: string;
276+
} {
277+
const placeholderWithoutBrackets = placeholder.slice(2, -1);
278+
const splitValues = placeholderWithoutBrackets.split(DEFAULT_VALUE_SEPARATOR);
279+
const defaultValue =
280+
splitValues.length === 1
281+
? undefined
282+
: splitValues.slice(1).join(DEFAULT_VALUE_SEPARATOR);
283+
284+
return {
285+
variable: splitValues[0],
286+
defaultValue,
287+
};
288+
}

tests/e2e/yaml-file-substitutions.spec.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import { AppModule, TestYamlFile } from '../src/app.module';
22
import {
33
ConfigWithAlias,
4+
ConfigWithDefaultValuesForEnvs,
45
DatabaseConfig,
56
DatabaseConfigAlias,
7+
DatabaseConfigAliasCopy,
68
DatabaseConfigWithAliasAndAuthCopy,
79
} from '../src/config.model';
810
import { INestApplication } from '@nestjs/common';
@@ -53,6 +55,25 @@ describe('Environment variable substitutions success cases', () => {
5355
expect(alias).toStrictEqual(instanceToPlain(databaseConfig));
5456
});
5557

58+
it(`should load yaml and fill default values successfully`, async () => {
59+
await init(
60+
{ ignoreEnvironmentVariableSubstitution: false },
61+
['.env-with-default.sub.yaml'],
62+
ConfigWithDefaultValuesForEnvs,
63+
);
64+
const config = app.get(ConfigWithDefaultValuesForEnvs);
65+
const databaseConfig = app.get(DatabaseConfigAliasCopy);
66+
const databaseConfigAlias = app.get(DatabaseConfigAlias);
67+
const alias = instanceToPlain(databaseConfigAlias);
68+
69+
expect(alias).toStrictEqual(instanceToPlain(databaseConfig));
70+
expect(alias.port).toBe(12345);
71+
expect(alias.table.name).toBe(tableName);
72+
expect(alias.host).toBe('my-default-host.com');
73+
expect(config.isAuthEnabled).toBe(false);
74+
expect(config.defaultEmptyString).toBe('');
75+
});
76+
5677
it(`self substitution advanced case`, async () => {
5778
await init({ ignoreEnvironmentVariableSubstitution: false }, [
5879
'.env-advanced-self-referencing-tricky.sub.yaml',

tests/src/.env-with-default.sub.yaml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
isAuthEnabled: ${AUTH_ENABLED:-true}
1+
isAuthEnabled: ${AUTH_ENABLED:-false}
2+
defaultEmptyString: ${EMPTY_STRING:-}
23
database:
3-
host: ${DATABASE_HOST:-127.0.0.1}
4-
port: ${DATABASE_PORT:-3000}
4+
host: ${DATABASE_HOST:-my-default-host.com}
5+
port: ${DATABASE_PORT:-12345}
56
table:
6-
name: ${TABLE_NAME:-users}
7+
name: ${TABLE_NAME:-custom-name}
78

89
databaseAlias:
910
host: ${database.host}

tests/src/config.model.ts

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
IsString,
88
ValidateNested,
99
} from 'class-validator';
10+
import { applyDecorators } from '@nestjs/common';
1011

1112
export class TableConfig {
1213
@IsString()
@@ -26,7 +27,19 @@ export class DatabaseConfig {
2627
public readonly table!: TableConfig;
2728
}
2829

29-
export class DatabaseConfigAlias extends DatabaseConfig {}
30+
export class DatabaseConfigAlias extends DatabaseConfig {
31+
@IsInt()
32+
@Type(() => Number)
33+
public readonly port!: number;
34+
}
35+
36+
export class DatabaseConfigAliasCopy extends DatabaseConfigAlias {}
37+
38+
const ToBoolean = applyDecorators(
39+
Transform(({ value }) =>
40+
typeof value === 'boolean' ? value : value === 'true',
41+
),
42+
);
3043

3144
export class Config {
3245
@Type(() => DatabaseConfig)
@@ -38,6 +51,25 @@ export class Config {
3851
public readonly isAuthEnabled!: boolean;
3952
}
4053

54+
export class ConfigWithDefaultValuesForEnvs {
55+
@IsBoolean()
56+
@ToBoolean
57+
public readonly isAuthEnabled!: boolean;
58+
59+
@IsString()
60+
public readonly defaultEmptyString!: string;
61+
62+
@Type(() => DatabaseConfigAliasCopy)
63+
@ValidateNested()
64+
@IsDefined()
65+
public readonly database!: DatabaseConfigAliasCopy;
66+
67+
@Type(() => DatabaseConfigAlias)
68+
@ValidateNested()
69+
@IsDefined()
70+
public readonly databaseAlias!: DatabaseConfigAlias;
71+
}
72+
4173
export class ConfigWithAlias extends Config {
4274
@Type(() => DatabaseConfigAlias)
4375
@ValidateNested()
@@ -46,9 +78,7 @@ export class ConfigWithAlias extends Config {
4678
}
4779

4880
export class DatabaseConfigWithAliasAndAuthCopy extends ConfigWithAlias {
49-
@Transform(({ value }) =>
50-
typeof value === 'boolean' ? value : value === 'true',
51-
)
81+
@ToBoolean
5282
@IsBoolean()
5383
public readonly isAuthEnabledCopy!: boolean;
5484
}

0 commit comments

Comments
 (0)