Skip to content

Conversation

@shubhamdeodia
Copy link

@shubhamdeodia shubhamdeodia commented Nov 13, 2025

Merge Provider Configuration Metadata into Hook Context

Description

This PR implements metadata merging functionality in the RequestContext.metadata getter that allows hooks to access metadata defined in provider configurations (like conf.json) by merging it with HTTP header metadata. The implementation maintains backward compatibility while unlocking powerful new capabilities for hook developers.

Motivation

Previously, hooks could only access metadata passed via HTTP headers (x-portkey-metadata), making configuration-based metadata completely inaccessible. This limitation prevented hooks from implementing:

  • Performance tier optimizations using deployment metadata (e.g., higher limits for GPU tiers)
  • Environment-aware behavior without duplicating metadata in headers and config
  • Fallback tracking using configuration metadata (e.g., logging which backend was used)

This change enables hooks to access rich metadata from provider configurations while maintaining the ability to override via HTTP headers, creating a flexible single source of truth for metadata.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Refactoring (no functional changes)

How Has This Been Tested?

  • Unit Tests
  • Integration Tests
  • Manual Testing

Test Details

Unit Tests Added (5 test cases):

  1. ✅ Config metadata only - Returns provider configuration metadata when no headers present
  2. ✅ Header metadata only - Returns header metadata when no config metadata (existing behavior)
  3. ✅ Merged metadata - Merges config and header metadata with header taking precedence
  4. ✅ Invalid JSON fallback - Gracefully falls back to config metadata when header JSON is invalid
  5. ✅ Empty metadata handling - Returns empty object when no metadata exists anywhere

Manual Testing:

  • ✅ All 5 test scenarios passed successfully
  • ✅ TypeScript compilation successful
  • ✅ Verified data flow through RequestContext → HooksService → HooksManager → HookSpan → Plugin handlers

Implementation Details

Core Changes

File: src/handlers/services/requestContext.ts

Before:

get metadata(): Record<string, string> {
  try {
    return JSON.parse(this.requestHeaders[HEADER_KEYS.METADATA] ?? '{}');
  } catch (error) {
    return {};
  }
}

After:

get metadata(): Record<string, string> {
  try {
    const headerMetadata = JSON.parse(
      this.requestHeaders[HEADER_KEYS.METADATA] ?? '{}'
    );
    const configMetadata = this.providerOption.metadata || {};

    // Merge: config metadata + header metadata (headers win conflicts)
    return {
      ...configMetadata,
      ...headerMetadata,
    };
  } catch (error) {
    // Fallback to config metadata only
    return this.providerOption.metadata || {};
  }
}

Architecture Flow

conf.json (providerOption.metadata)
          ↓
RequestContext.metadata getter  [MERGE POINT]
          ↓
HooksService.createSpan()
          ↓
HooksManager.createSpan()
          ↓
HookSpan.createContext()
          ↓
Plugin Handler (context.metadata)

Merge Priority

  • HTTP Headers override Configuration Metadata in case of conflicts
  • Backward Compatible: Existing header-only metadata continues to work unchanged
  • Graceful Fallback: If header JSON parsing fails, config metadata is used

Use Cases Unlocked

1. Regional Compliance Hook

// conf.json
{
  "metadata": {
    "region": "eu-west-1",
    "compliance_zone": "gdpr"
  }
}

// Hook handler
if (context.metadata.compliance_zone === 'gdpr') {
  enforceGDPRRules(context);
}

2. Performance Tier Hook

// conf.json
{
  "metadata": {
    "deployment_tier": "platform_gpu",
    "priority": "high"
  }
}

// Hook handler
if (context.metadata.deployment_tier === 'platform_gpu') {
  allowHigherTokenLimits(context);
}

3. Fallback Tracking Hook

// conf.json fallback target
{
  "metadata": {
    "backend": "FALLBACK",
    "fallback_reason": "primary_unavailable"
  }
}

// Hook handler
if (context.metadata.fallback_reason) {
  logFallbackEvent(context.metadata);
}

Checklist

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

Related Issues

This addresses a limitation discovered during plugin development where configuration-based metadata was inaccessible to hooks, requiring unnecessary duplication of metadata in both configuration files and HTTP headers.


Additional Technical Details

Performance Characteristics

  • Memory: Negligible increase (object spread operation)
  • Network: No impact (no additional calls)
  • Caching: Metadata accessed once per request via HooksService

Security Considerations

  • Data Flow: Internal metadata merging only
  • Exposure: No new data exposed externally
  • Access Control: Same as existing metadata access
  • Validation: Maintains existing JSON parsing error handling

Backward Compatibility

  • 100% compatible with existing code
  • ✅ Header-only metadata continues to work
  • ✅ No changes required to existing hooks
  • ✅ No breaking changes to API signatures
  • ✅ Works with all existing plugins

Reviewer Notes

Files Changed

  1. src/handlers/services/requestContext.ts - Core implementation (metadata getter)
  2. tests/unit/src/handlers/services/requestContext.test.ts - New test cases

@shubhamdeodia
Copy link
Author

shubhamdeodia commented Nov 16, 2025

@narengogi @VisargD - Appreciate your review here, thanks.

@narengogi
Copy link
Collaborator

This makes sense, I have some thoughts on if this could be better implemented by merging headers passed in config directly with the headers instead of just handling it per case like this, I'm still to update the documentation on using the local configuration file for deploying hte gateway. will revisit this in a couple days post that

@shubhamdeodia
Copy link
Author

@narengogi Got it , so you’re thinking a future, more generalized mechanism could merge config-level values directly into the request context rather than handling metadata as a special case. For now this PR focuses on metadata because that’s what hooks consume and expose. Happy to revisit a broader merge strategy later.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants