import type { RateLimitConfig, BackoffConfig, HttpConfig, SegmentAPISettings, SegmentAPIIntegrations, LoggerType, DeepPartial, } from './types'; import { defaultHttpConfig } from './constants'; export const validateRateLimitConfig = ( config: RateLimitConfig, logger?: LoggerType ): RateLimitConfig => { const validated = { ...config }; if (validated.maxRetryInterval < 0.1) { logger?.warn( `maxRetryInterval ${validated.maxRetryInterval}s clamped to 0.1s` ); validated.maxRetryInterval = 0.1; } else if (validated.maxRetryInterval > 86400) { logger?.warn( `maxRetryInterval ${validated.maxRetryInterval}s clamped to 86400s` ); validated.maxRetryInterval = 86400; } if (validated.maxRateLimitDuration < 60) { logger?.warn( `maxRateLimitDuration ${validated.maxRateLimitDuration}s clamped to 60s` ); validated.maxRateLimitDuration = 60; } else if (validated.maxRateLimitDuration > 604800) { logger?.warn( `maxRateLimitDuration ${validated.maxRateLimitDuration}s clamped to 604800s` ); validated.maxRateLimitDuration = 604800; } if (validated.maxRetryCount < 1) { logger?.warn(`maxRetryCount ${validated.maxRetryCount} clamped to 1`); validated.maxRetryCount = 1; } else if (validated.maxRetryCount > 100) { logger?.warn(`maxRetryCount ${validated.maxRetryCount} clamped to 100`); validated.maxRetryCount = 100; } // Relational: maxRateLimitDuration >= 2x maxRetryInterval const minRateLimitDuration = validated.maxRetryInterval * 2; if (validated.maxRateLimitDuration < minRateLimitDuration) { logger?.warn( `maxRateLimitDuration ${validated.maxRateLimitDuration}s clamped to ${minRateLimitDuration}s (2x maxRetryInterval)` ); validated.maxRateLimitDuration = minRateLimitDuration; } return validated; }; export const validateBackoffConfig = ( config: BackoffConfig, logger?: LoggerType, rateLimitConfig?: RateLimitConfig ): BackoffConfig => { const validated = { ...config }; if (validated.maxBackoffInterval < 0.1) { logger?.warn( `maxBackoffInterval ${validated.maxBackoffInterval}s clamped to 0.1s` ); validated.maxBackoffInterval = 0.1; } else if (validated.maxBackoffInterval > 86400) { logger?.warn( `maxBackoffInterval ${validated.maxBackoffInterval}s clamped to 86400s` ); validated.maxBackoffInterval = 86400; } if (validated.baseBackoffInterval < 0.1) { logger?.warn( `baseBackoffInterval ${validated.baseBackoffInterval}s clamped to 0.1s` ); validated.baseBackoffInterval = 0.1; } else if (validated.baseBackoffInterval > 300) { logger?.warn( `baseBackoffInterval ${validated.baseBackoffInterval}s clamped to 300s` ); validated.baseBackoffInterval = 300; } if (validated.maxTotalBackoffDuration < 60) { logger?.warn( `maxTotalBackoffDuration ${validated.maxTotalBackoffDuration}s clamped to 60s` ); validated.maxTotalBackoffDuration = 60; } else if (validated.maxTotalBackoffDuration > 604800) { logger?.warn( `maxTotalBackoffDuration ${validated.maxTotalBackoffDuration}s clamped to 604800s` ); validated.maxTotalBackoffDuration = 604800; } if (validated.jitterPercent < 0) { logger?.warn(`jitterPercent ${validated.jitterPercent} clamped to 0`); validated.jitterPercent = 0; } else if (validated.jitterPercent > 100) { logger?.warn(`jitterPercent ${validated.jitterPercent} clamped to 100`); validated.jitterPercent = 100; } if (validated.maxRetryCount < 1) { logger?.warn(`maxRetryCount ${validated.maxRetryCount} clamped to 1`); validated.maxRetryCount = 1; } else if (validated.maxRetryCount > 100) { logger?.warn(`maxRetryCount ${validated.maxRetryCount} clamped to 100`); validated.maxRetryCount = 100; } // Relational: baseBackoffInterval <= maxBackoffInterval if (validated.baseBackoffInterval > validated.maxBackoffInterval) { logger?.warn( `baseBackoffInterval ${validated.baseBackoffInterval}s clamped to maxBackoffInterval ${validated.maxBackoffInterval}s` ); validated.baseBackoffInterval = validated.maxBackoffInterval; } // Relational: maxTotalBackoffDuration >= 2x max(maxBackoffInterval, rateLimitConfig.maxRetryInterval) const maxInterval = Math.max( validated.maxBackoffInterval, rateLimitConfig?.maxRetryInterval ?? 0 ); const minTotalDuration = maxInterval * 2; if (validated.maxTotalBackoffDuration < minTotalDuration) { logger?.warn( `maxTotalBackoffDuration ${validated.maxTotalBackoffDuration}s clamped to ${minTotalDuration}s (2x max interval)` ); validated.maxTotalBackoffDuration = minTotalDuration; } return validated; }; /** * Validates and normalizes integrations from CDN response. * Returns null if integrations are malformed (triggers fallback to defaults). */ export const validateIntegrations = ( settings: SegmentAPISettings, logger?: LoggerType ): SegmentAPIIntegrations | null => { // A valid 200 with missing integrations means "no integrations configured" if (settings.integrations == null) { return {}; } // Only fall back to defaults for truly malformed types (non-object or array) if ( typeof settings.integrations !== 'object' || Array.isArray(settings.integrations) ) { logger?.warn( 'CDN response has invalid integrations, falling back to defaults' ); return null; } return settings.integrations; }; /** * Extracts, merges, and validates httpConfig from CDN response. * Deep-merges with defaults and validates all values. */ export const extractHttpConfig = ( serverConfig: HttpConfig, logger?: LoggerType ): HttpConfig => { const mergedRateLimit = serverConfig.rateLimitConfig ? { ...defaultHttpConfig.rateLimitConfig!, ...serverConfig.rateLimitConfig, } : defaultHttpConfig.rateLimitConfig!; const mergedBackoff = serverConfig.backoffConfig ? { ...defaultHttpConfig.backoffConfig!, ...serverConfig.backoffConfig, statusCodeOverrides: { ...defaultHttpConfig.backoffConfig!.statusCodeOverrides, ...serverConfig.backoffConfig.statusCodeOverrides, }, } : defaultHttpConfig.backoffConfig!; const validatedRateLimit = validateRateLimitConfig(mergedRateLimit, logger); return { rateLimitConfig: validatedRateLimit, backoffConfig: validateBackoffConfig( mergedBackoff, logger, validatedRateLimit ), }; }; /** * Merge httpConfig from 3 sources with priority: default < CDN < client overrides * Used during settings initialization to combine default, CDN, and user configs. */ export const mergeHttpConfig = ( cdnConfig: HttpConfig | undefined, clientConfig: DeepPartial | undefined, logger?: LoggerType ): HttpConfig => { const mergedRateLimit = { ...defaultHttpConfig.rateLimitConfig!, ...(cdnConfig?.rateLimitConfig ?? {}), ...(clientConfig?.rateLimitConfig ?? {}), }; const mergedBackoff = { ...defaultHttpConfig.backoffConfig!, ...(cdnConfig?.backoffConfig ?? {}), ...(clientConfig?.backoffConfig ?? {}), statusCodeOverrides: { ...defaultHttpConfig.backoffConfig!.statusCodeOverrides, ...(cdnConfig?.backoffConfig?.statusCodeOverrides ?? {}), ...(clientConfig?.backoffConfig?.statusCodeOverrides ?? {}), }, }; const validatedRateLimit = validateRateLimitConfig(mergedRateLimit, logger); return { rateLimitConfig: validatedRateLimit, backoffConfig: validateBackoffConfig( mergedBackoff, logger, validatedRateLimit ), }; };