|
| 1 | +import { Injectable, Logger } from '@nestjs/common'; |
| 2 | +import { |
| 3 | + FEATURE_FLAGS_DISABLED_STATUS_ENV, |
| 4 | + FEATURE_FLAGS_JSON_ENV, |
| 5 | +} from './constants'; |
| 6 | + |
| 7 | +type FeatureMap = Record<string, boolean>; |
| 8 | + |
| 9 | +@Injectable() |
| 10 | +export class FeatureFlagsService { |
| 11 | + private readonly logger = new Logger(FeatureFlagsService.name); |
| 12 | + private readonly featureMap: FeatureMap; |
| 13 | + private readonly disabledStatusCode: 403 | 404; |
| 14 | + |
| 15 | + constructor() { |
| 16 | + this.featureMap = this.parseFlags(process.env[FEATURE_FLAGS_JSON_ENV]); |
| 17 | + this.disabledStatusCode = |
| 18 | + process.env[FEATURE_FLAGS_DISABLED_STATUS_ENV] === '403' ? 403 : 404; |
| 19 | + } |
| 20 | + |
| 21 | + isEnabled(featureName: string): boolean { |
| 22 | + return this.featureMap[featureName] === true; |
| 23 | + } |
| 24 | + |
| 25 | + getDisabledStatusCode(): 403 | 404 { |
| 26 | + return this.disabledStatusCode; |
| 27 | + } |
| 28 | + |
| 29 | + getFlags(): FeatureMap { |
| 30 | + return { ...this.featureMap }; |
| 31 | + } |
| 32 | + |
| 33 | + private parseFlags(rawValue: string | undefined): FeatureMap { |
| 34 | + if (!rawValue) { |
| 35 | + return {}; |
| 36 | + } |
| 37 | + |
| 38 | + try { |
| 39 | + const parsed = JSON.parse(rawValue) as Record<string, unknown>; |
| 40 | + return Object.entries(parsed).reduce<FeatureMap>((acc, [key, value]) => { |
| 41 | + acc[key] = value === true; |
| 42 | + return acc; |
| 43 | + }, {}); |
| 44 | + } catch (error) { |
| 45 | + const details = error instanceof Error ? error.message : String(error); |
| 46 | + this.logger.error( |
| 47 | + `${FEATURE_FLAGS_JSON_ENV} is not valid JSON; defaulting all features to disabled. ${details}`, |
| 48 | + ); |
| 49 | + return {}; |
| 50 | + } |
| 51 | + } |
| 52 | +} |
0 commit comments