Skip to content

Commit

Permalink
fix: optional properties get type from format (#207)
Browse files Browse the repository at this point in the history
Co-authored-by: Matthew Fernando Garcia <[email protected]>
  • Loading branch information
wtgtybhertgeghgtwtg and Matthew Fernando Garcia authored Jan 23, 2021
1 parent b36c62b commit 4a705e4
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 78 deletions.
50 changes: 45 additions & 5 deletions __tests__/load-config-sync.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -376,11 +376,13 @@ describe('loadConfig', () => {
const variableName = 'VARIABLE_NAME';
const variableValue = 'The value of this environment variable.';

it('returns the formatted value.', () => {
beforeEach(() => {
process.env[variableName] = variableValue;
// @ts-ignore
fs._setFiles({[filePath]: Buffer.from(fileContent, 'utf8')});
});

it('returns the formatted value.', () => {
// eslint-disable-next-line unicorn/consistent-function-scoping
const formatter = (value: string): number => value.length;

Expand All @@ -399,11 +401,49 @@ describe('loadConfig', () => {
expect(config.variableProperty).toEqual(formatter(variableValue));
});

it('throws if the formatter throws.', () => {
process.env[variableName] = variableValue;
// @ts-ignore
fs._setFiles({[filePath]: Buffer.from(fileContent, 'utf8')});
it('returns the formatted value if explicitly required.', () => {
// eslint-disable-next-line unicorn/consistent-function-scoping
const formatter = (value: string): number => value.length;

const config = loadConfigSync({
fileProperty: {
filePath,
format: formatter,
required: true,
},
variableProperty: {
format: formatter,
required: true,
variableName,
},
});

expect(config.fileProperty).toEqual(formatter(fileContent));
expect(config.variableProperty).toEqual(formatter(variableValue));
});

it('returns the formatted value if not required.', () => {
// eslint-disable-next-line unicorn/consistent-function-scoping
const formatter = (value: string): number => value.length;

const config = loadConfigSync({
fileProperty: {
filePath,
format: formatter,
required: false,
},
variableProperty: {
format: formatter,
required: false,
variableName,
},
});

expect(config.fileProperty).toEqual(formatter(fileContent));
expect(config.variableProperty).toEqual(formatter(variableValue));
});

it('throws if the formatter throws.', () => {
// eslint-disable-next-line unicorn/consistent-function-scoping
const formatter = (value: string): number => {
const integer = Number.parseInt(value, 10);
Expand Down
50 changes: 45 additions & 5 deletions __tests__/load-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -376,11 +376,13 @@ describe('loadConfig', () => {
const variableName = 'VARIABLE_NAME';
const variableValue = 'The value of this environment variable.';

it('returns the formatted value.', async () => {
beforeEach(() => {
process.env[variableName] = variableValue;
// @ts-ignore
fs._setFiles({[filePath]: Buffer.from(fileContent, 'utf8')});
});

it('returns the formatted value.', async () => {
// eslint-disable-next-line unicorn/consistent-function-scoping
const formatter = (value: string): string => value.toLowerCase();

Expand All @@ -399,11 +401,49 @@ describe('loadConfig', () => {
expect(config.variableProperty).toEqual(formatter(variableValue));
});

it('rejects if the formatter throws.', async () => {
process.env[variableName] = variableValue;
// @ts-ignore
fs._setFiles({[filePath]: Buffer.from(fileContent, 'utf8')});
it('returns the formatted value if explicitly required.', async () => {
// eslint-disable-next-line unicorn/consistent-function-scoping
const formatter = (value: string): number => value.length;

const config = await loadConfig({
fileProperty: {
filePath,
format: formatter,
required: true,
},
variableProperty: {
format: formatter,
required: true,
variableName,
},
});

expect(config.fileProperty).toEqual(formatter(fileContent));
expect(config.variableProperty).toEqual(formatter(variableValue));
});

it('returns the formatted value if not required.', async () => {
// eslint-disable-next-line unicorn/consistent-function-scoping
const formatter = (value: string): number => value.length;

const config = await loadConfig({
fileProperty: {
filePath,
format: formatter,
required: false,
},
variableProperty: {
format: formatter,
required: false,
variableName,
},
});

expect(config.fileProperty).toEqual(formatter(fileContent));
expect(config.variableProperty).toEqual(formatter(variableValue));
});

it('rejects if the formatter throws.', async () => {
// eslint-disable-next-line unicorn/consistent-function-scoping
const formatter = (value: string): number => {
const integer = Number.parseInt(value, 10);
Expand Down
15 changes: 3 additions & 12 deletions src/format-property.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
import {
FormattableConfig,
PropertyFormatter,
PropertyResult,
RequirableConfig,
} from './types';
import {BaseConfig, PropertyFormatter, PropertyResult} from './types';

function tryToFormat<Value>(
value: string,
Expand All @@ -18,13 +13,9 @@ function tryToFormat<Value>(

export default function something<Value>(
propertyResult: PropertyResult<string>,
propertyConfig: FormattableConfig<Value> | RequirableConfig,
propertyConfig: BaseConfig<Value>,
): PropertyResult<string | Value> {
const {
defaultValue,
format,
required = true,
} = propertyConfig as FormattableConfig<Value>;
const {defaultValue, format, required = true} = propertyConfig;
const hasDefaultValue = typeof defaultValue !== 'undefined';
const hasError = propertyResult.error !== false;
const hasFormat = typeof format !== 'undefined';
Expand Down
87 changes: 31 additions & 56 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* Configuration for a property that can be formatted.
*/
export interface FormattableConfig<Value> {
export interface BaseConfig<Value> {
/**
* The default value of this property.
*/
Expand All @@ -16,50 +16,35 @@ export interface FormattableConfig<Value> {
* Whether this property is required.
* @defaultValue true
*/
required?: true;
required?: boolean;
}

/**
* Configuration for a property that can be required.
* Configuration for a property loaded from an environment variable.
*/
export interface RequirableConfig {
export interface EnvironmentConfig<Value> extends BaseConfig<Value> {
/**
* Whether this property is required.
* @defaultValue true
* The name of the environment variable.
*/
required?: boolean;
variableName: string;
}

/**
* The result loading a property.
* @internal
* Configuration for a property loaded from a secret.
*/
export interface PropertyResult<Value> {
export interface FileConfig<Value> extends BaseConfig<Value> {
/**
* The error that was thrown, or false if there is none.
* The encoding of the file containing the secret.
* @defaultValue 'utf8'
*/
error: false | Error;
encoding?: BufferEncoding;

/**
* The value that was loaded, or undefined if it could not be loaded.
* The path to the file containing the secret.
*/
value: undefined | Value;
filePath: string;
}

/**
* Configuration for a property loaded from an environment variable.
*/
export type EnvironmentConfig<Value> =
| (BaseEnvironmentConfig & FormattableConfig<Value>)
| (BaseEnvironmentConfig & RequirableConfig);

/**
* Configuration for a property loaded from a secret.
*/
export type FileConfig<Value> =
| (BaseFileConfig & FormattableConfig<Value>)
| (BaseFileConfig & RequirableConfig);

/**
* The configuration for loading a specific property.
*/
Expand All @@ -74,45 +59,35 @@ export type PropertyConfig<Value> =
export type PropertyFormatter<Value> = (raw: string) => Value;

/**
* Unwrap a property config to its resultant value.
* The result loading a property.
* @internal
*/
export type UnwrapPropertyConfig<
PConfig extends PropertyConfig<unknown>
> = PConfig extends string
? string
: PConfig extends {required: false}
? UnwrapResult<PConfig> | void
: UnwrapResult<PConfig>;
export interface PropertyResult<Value> {
/**
* The error that was thrown, or false if there is none.
*/
error: false | Error;

/**
* Base configuration for a property loaded from an environment variable.
*/
interface BaseEnvironmentConfig {
/**
* The name of the environment variable.
* The value that was loaded, or undefined if it could not be loaded.
*/
variableName: string;
value: undefined | Value;
}

/**
* Base configuration for a property loaded from a secret.
* Unwrap a property config to its resultant value.
*/
interface BaseFileConfig {
/**
* The encoding of the file containing the secret.
* @defaultValue 'utf8'
*/
encoding?: BufferEncoding;

/**
* The path to the file containing the secret.
*/
filePath: string;
}
export type UnwrapPropertyConfig<
PConfig extends PropertyConfig<unknown>
> = PConfig extends string
? string
: PConfig extends {required: false}
? UnwrapResult<PConfig> | undefined
: UnwrapResult<PConfig>;

/**
* Unwrap a result to its value.
*/
type UnwrapResult<
PConfig extends PropertyConfig<unknown>
> = PConfig extends FormattableConfig<infer Value> ? Value : string;
> = PConfig extends BaseConfig<infer Value> ? Value : string;

0 comments on commit 4a705e4

Please sign in to comment.