Commit ebd6039d authored by nanahira's avatar nanahira

add getJSON

parent 7a5176fb
......@@ -37,6 +37,23 @@ export class ConfigurerInstance<T extends Record<string, string>> {
const defaultBoolean = parseConfigBoolean(this.defaultConfig[key], false);
return convertBooleanArray(this.getString(key), defaultBoolean);
}
getJSON<R = unknown, K extends keyof T = keyof T>(key: K): R {
const value = this.getString(key);
const jsonString =
typeof value === 'string' && !value.trim()
? (this.defaultConfig[key] as string)
: value;
try {
return JSON.parse(jsonString) as R;
} catch (error) {
const message =
error instanceof Error ? `: ${error.message}` : '';
throw new Error(
`Failed to parse JSON config "${String(key)}"${message}`,
);
}
}
}
export class Configurer<T extends Record<string, string>> {
......@@ -62,19 +79,24 @@ export class Configurer<T extends Record<string, string>> {
);
}
generateExampleObject(): Record<
string,
string | number | string[] | number[]
> {
generateExampleObject(): Record<string, unknown> {
return Object.fromEntries(
Object.entries(this.defaultConfig).map(([key, value]) => {
const typedValue = toTypedValue(value);
if (
typedValue &&
typeof typedValue === 'object' &&
!Array.isArray(typedValue)
) {
return [toCamelCaseKey(key), typedValue];
}
if (value.includes(',')) {
return [
toCamelCaseKey(key),
value.split(',').map((v) => toTypedValue(v)),
];
}
return [toCamelCaseKey(key), toTypedValue(value)];
return [toCamelCaseKey(key), typedValue];
}),
);
}
......@@ -104,6 +126,9 @@ function normalizeConfigValue(value: unknown): string | undefined {
if (Array.isArray(value)) {
return value.map((item) => normalizeArrayItem(item)).join(',');
}
if (typeof value === 'object') {
return JSON.stringify(value);
}
return String(value);
}
......@@ -205,10 +230,31 @@ function convertBooleanArray(str: string, defaultValue = false): boolean[] {
);
}
function toTypedValue(value: string): string | number {
function toTypedValue(value: string): string | number | Record<string, unknown> {
const trimmed = value.trim();
if (/^\d+$/.test(trimmed)) {
return Number.parseInt(trimmed, 10);
}
const jsonObject = parseJsonObjectString(trimmed);
if (jsonObject !== undefined) {
return jsonObject;
}
return trimmed;
}
function parseJsonObjectString(
value: string,
): Record<string, unknown> | undefined {
if (!value.startsWith('{') || !value.endsWith('}')) {
return undefined;
}
try {
const parsed = JSON.parse(value);
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
return parsed as Record<string, unknown>;
}
} catch {
return undefined;
}
return undefined;
}
......@@ -8,6 +8,7 @@ type TestConfig = {
ALT_VERSIONS: string;
FLOAT_VALUES: string;
BOOL_VALUES: string;
JSON_CONFIG: string;
};
const defaultConfig: TestConfig = {
......@@ -18,6 +19,7 @@ const defaultConfig: TestConfig = {
ALT_VERSIONS: '2330,2331',
FLOAT_VALUES: '1.5,2.75',
BOOL_VALUES: '1,0,true,false,null',
JSON_CONFIG: '{"mode":"default","retry":3}',
};
describe('Configurer', () => {
......@@ -87,7 +89,65 @@ describe('Configurer', () => {
]);
});
test('generateExampleObject converts key to camelCase and parses number/array values', () => {
test('getString keeps whitespace string value', () => {
const configurer = new Configurer(defaultConfig);
const instance = configurer.loadConfig({
env: {
HOST: ' ',
},
});
expect(instance.getString('HOST')).toBe(' ');
});
test('getJSON parses JSON and falls back to default for empty value', () => {
const configurer = new Configurer(defaultConfig);
const withCustom = configurer.loadConfig({
env: {
JSON_CONFIG: '{"mode":"custom","retry":1}',
},
});
const withEmpty = configurer.loadConfig({
env: {
JSON_CONFIG: ' ',
},
});
expect(withCustom.getJSON<{ mode: string; retry: number }>('JSON_CONFIG')).toEqual({
mode: 'custom',
retry: 1,
});
expect(withEmpty.getJSON<{ mode: string; retry: number }>('JSON_CONFIG')).toEqual({
mode: 'default',
retry: 3,
});
});
test('getJSON throws when JSON parse fails', () => {
const configurer = new Configurer(defaultConfig);
const instance = configurer.loadConfig({
env: {
JSON_CONFIG: '{invalid json}',
},
});
expect(() => instance.getJSON('JSON_CONFIG')).toThrow(
'Failed to parse JSON config "JSON_CONFIG"',
);
});
test('normalizeConfigValue treats object as JSON string', () => {
const configurer = new Configurer(defaultConfig);
const instance = configurer.loadConfig({
obj: {
jsonConfig: { mode: 'obj', retry: 8 },
},
});
expect(instance.getString('JSON_CONFIG')).toBe('{"mode":"obj","retry":8}');
});
test('generateExampleObject converts key to camelCase and parses number/array/json-object values', () => {
const configurer = new Configurer(defaultConfig);
const example = configurer.generateExampleObject();
......@@ -95,5 +155,6 @@ describe('Configurer', () => {
expect(example.port).toBe(7911);
expect(example.altVersions).toEqual([2330, 2331]);
expect(example.floatValues).toEqual(['1.5', '2.75']);
expect(example.jsonConfig).toEqual({ mode: 'default', retry: 3 });
});
});
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment