backstage
1/*
2* Copyright 2023 The Backstage Authors
3*
4* Licensed under the Apache License, Version 2.0 (the "License");
5* you may not use this file except in compliance with the License.
6* You may obtain a copy of the License at
7*
8* http://www.apache.org/licenses/LICENSE-2.0
9*
10* Unless required by applicable law or agreed to in writing, software
11* distributed under the License is distributed on an "AS IS" BASIS,
12* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13* See the License for the specific language governing permissions and
14* limitations under the License.
15*/
16
17import { Config } from '@backstage/config';18import { InputError } from '@backstage/errors';19import { HumanDuration } from '@backstage/types';20
21export const propsOfHumanDuration = [22'years',23'months',24'weeks',25'days',26'hours',27'minutes',28'seconds',29'milliseconds',30];31
32/**
33* Reads a duration from a config object.
34*
35* @public
36* @remarks
37*
38* This does not support optionality; if you want to support optional durations,
39* you need to first check the presence of the target with `config.has(...)` and
40* then call this function.
41*
42* @param config - A configuration object
43* @param key - If specified, read the duration from the given subkey
44* under the config object
45* @returns A duration object
46*/
47export function readDurationFromConfig(48config: Config,49options?: {50key?: string;51},52): HumanDuration {53let root: Config;54let found = false;55const result: Record<string, number> = {};56
57try {58root = options?.key ? config.getConfig(options.key) : config;59for (const prop of propsOfHumanDuration) {60const value = root.getOptionalNumber(prop);61if (value !== undefined) {62result[prop] = value;63found = true;64}65}66} catch (error) {67// This needs no contextual key prefix since the config reader adds it to68// its own errors69throw new InputError(`Failed to read duration from config, ${error}`);70}71
72try {73if (!found) {74const good = propsOfHumanDuration.map(p => `'${p}'`).join(', ');75throw new Error(`Needs one or more of ${good}`);76}77
78const invalidProps = root79.keys()80.filter(prop => !propsOfHumanDuration.includes(prop));81if (invalidProps.length) {82const what = invalidProps.length === 1 ? 'property' : 'properties';83const bad = invalidProps.map(p => `'${p}'`).join(', ');84const good = propsOfHumanDuration.map(p => `'${p}'`).join(', ');85throw new Error(86`Unknown ${what} ${bad}; expected one or more of ${good}`,87);88}89} catch (error) {90// For our own errors, even though we don't know where we are anchored in91// the config hierarchy, we try to be helpful and at least add the subkey if92// we have it93let prefix = 'Failed to read duration from config';94if (options?.key) {95prefix += ` at '${options.key}'`;96}97throw new InputError(`${prefix}, ${error}`);98}99
100return result as HumanDuration;101}
102