import { Case } from '@rsdk/common'; import { LoggerFactory } from '@rsdk/logging'; import { cloneDeep, isEqual } from 'lodash'; import { LegacyAppNameStrategy } from './strategy/app-name-strategy/legacy.app-name-strategy'; import { PlatformAppNameStrategy } from './strategy/app-name-strategy/platform.app-name-strategy'; import { PropertyException } from './exceptions'; import { type AppNameStrategy, AppNameStrategyType, type Changes, type PropertyMetadata, type RawValues, } from './types'; export enum SetResult { CREATED, UPDATED, UNCHANGED, } export class Vars { private readonly values = new Map(); private readonly appNameStrategies: AppNameStrategy[] = [ new LegacyAppNameStrategy(), new PlatformAppNameStrategy(), ]; /** * We don't use class as context here because it is undefined if * tsc target is ES2022 */ private readonly logger = LoggerFactory.create('Vars'); /** * Expecting prefix is already converted to upper snake case * @param prefix */ constructor( private readonly prefix: string, private readonly appNameStrategyType?: AppNameStrategyType, ) {} set(key: string, value: unknown): [SetResult, string] { const preparedKey = Case.toUpperSnake(key); const preparedValue = Array.isArray(value) ? cloneDeep(value).sort() : value; const existing = this.values.get(preparedKey); if (existing === undefined) { this.values.set(preparedKey, preparedValue); return [SetResult.CREATED, preparedKey]; } if (isEqual(preparedValue, existing)) { return [SetResult.UNCHANGED, preparedKey]; } this.values.set(preparedKey, preparedValue); return [SetResult.UPDATED, preparedKey]; } merge(values: RawValues): Changes { const createdKeys: string[] = []; const updatedKeys: string[] = []; for (const [key, value] of values.entries()) { const [result, preparedKey] = this.set(key, value); switch (result) { case SetResult.CREATED: createdKeys.push(preparedKey); break; case SetResult.UPDATED: updatedKeys.push(preparedKey); break; } } return { createdKeys, updatedKeys }; } extract(propertyMetadata: PropertyMetadata): T | undefined { const { key, parser, defaultValue } = propertyMetadata; const preparedKey = Case.toUpperSnake(key); const { platformEnvironmentValue, platformEnvironmentKey } = this.getEnvironmentValueWithPlatformAppNameStrategy(preparedKey); const { appNameStrategy, appEnvironmentValue, appEnvironmentKey } = this.getEnvironmentValueWithAppNameStrategyFromConfig(preparedKey); let raw: unknown | undefined; if (platformEnvironmentValue !== undefined) { raw = platformEnvironmentValue; } if (appEnvironmentValue !== undefined) { raw = appEnvironmentValue; this.logger.warn( `You are using the "${appNameStrategy.type}" format for naming environment variables, for "${appEnvironmentKey}", you should use the "${platformEnvironmentKey}"`, ); } if (raw === undefined) { raw = this.values.get(preparedKey); } if (raw === undefined) { // Важная проверка на то что поле там вообще было if ('defaultValue' in propertyMetadata) { return defaultValue; } throw new PropertyException(`No value by key ${key} was provided!`, { propertyMetadata, preparedKey, }); } try { return parser.parse(raw); } catch (error) { throw PropertyException.fromError(error, { preparedKey, propertyMetadata, }); } } private getEnvironmentValueWithAppNameStrategyFromConfig( preparedKey: string, ): { appNameStrategy: AppNameStrategy; appEnvironmentKey: string | undefined; appEnvironmentValue: unknown | undefined; } { const appNameStrategy = this.appNameStrategies.find( (s) => s.type === this.appNameStrategyType, )!; if (appNameStrategy) { const appEnvironmentKey = `${Case.toUpperSnake( appNameStrategy.transform(this.prefix), )}_${preparedKey}`; const appEnvironmentValue = this.values.get(appEnvironmentKey); return { appNameStrategy, appEnvironmentKey, appEnvironmentValue }; } return { appNameStrategy, appEnvironmentKey: undefined, appEnvironmentValue: undefined, }; } private getEnvironmentValueWithPlatformAppNameStrategy(preparedKey: string): { platformEnvironmentValue: unknown; platformEnvironmentKey: string; } { const platformAppNameStrategy = this.appNameStrategies.find( (s) => s.type === AppNameStrategyType.PLATFORM, )!; const platformEnvironmentKey = `${Case.toUpperSnake( platformAppNameStrategy.transform(this.prefix), )}_${preparedKey}`; const platformEnvironmentValue = this.values.get(platformEnvironmentKey); return { platformEnvironmentValue, platformEnvironmentKey }; } }