NextGen APIs

Extension Field Propagation System


Important: This system propagates extension fields from DtoExtension only, not extensions from Dyno (the dynamic object system). These are two different extension mechanisms:

  • DtoExtension: Extension fields passed in Integration API requests (e.g., extension.characteristics.color=red) - PROPAGATED ✅

  • Dyno Extensions: Internal dynamic object extensions managed by the Dyno system - NOT PROPAGATED ❌

Additionally: Extension propagation currently works only to Private API GET requests. While you can filter by any HTTP method for Integration API requests (GET, POST, PUT, PATCH, DELETE), the extensions will only be propagated when the service makes a GET call to the Private API. Private API POST, PUT, PATCH, and DELETE calls do not support automatic extension propagation at this time.

Purpose: Automatically propagate extension fields from Integration API requests to Private API GET calls, enabling dynamic filtering and data enrichment without code changes.

📋 Table of Contents

🎯 What is the Extension Field Propagation System?

The Extension Field Propagation System is an automatic mechanism that extracts custom extension fields from Integration API requests and intelligently routes them to the appropriate Private API GET calls based on configurable rules.

Key Benefits

  • Zero Code Changes: Add new filtering capabilities without modifying service code

  • Dynamic Filtering: Clients can pass custom filters that automatically reach the right endpoints

  • Type-Safe Configuration: Use enums and class references for compile-time safety

  • Pattern Matching: Flexible rules using glob patterns for paths and extension fields

  • Conditional Logic: Apply rules only when specific conditions are met

  • Flexible HTTP Method Filtering: Filter by Integration API HTTP method (any method supported)

Scope and Limitations

Current Scope:

  • ✅ Works with DtoExtension extension fields from Integration API requests

  • ✅ Propagates to Private API GET requests only

  • ✅ Enriches GetFilters objects automatically

  • ✅ Can filter by any HTTP method for Integration API requests (GET, POST, PUT, PATCH, DELETE)

  • ❌ Does NOT work with Dyno extensions (different system)

  • ❌ Does NOT propagate to Private API POST/PUT/PATCH/DELETE calls

Example: You can configure a rule that triggers on Integration API POST requests, but the extensions will only propagate if the service internally makes a GET call to the Private API.

Real-World Example

Scenario: A client wants to filter asset characteristics by color when retrieving an asset.

Without Extension Propagation:

1. Client calls: GET /integration/assets/r1/asset?code=ASSET123&extension.characteristics.color=red
2. Integration API receives request
3. Service calls Private API: GET /assets/r1/assets/123/characteristics
4. ❌ The color filter is LOST - Private API returns ALL characteristics

With Extension Propagation:

1. Client calls: GET /integration/assets/r1/asset?code=ASSET123&extension.characteristics.color=red
2. Integration API receives request and extracts extensions
3. Extension Propagation System matches rules and enriches the call
4. Service calls Private API: GET /assets/r1/assets/123/characteristics?extension.color=red
5. ✅ Private API receives the filter and returns only RED characteristics

🔧 System Configuration

Configuration Properties

The system is controlled by two application properties:

application.yml
YAML
integrationlayer:
  extension-propagation:
    # Master switch - enables/disables the entire system
    enabled: true  # Default: true
    
    # Default rules - enables/disables pre-configured rules
    default-rule:
      enabled: false  # Default: false

Property Details

Property

Default

Description

integrationlayer.extension-propagation.enabled

true

Master switch for the entire extension propagation system. When false, no extension fields are propagated to any Private API calls.

integrationlayer.extension-propagation.default-rule.enabled

false

Enables pre-configured default rules for common endpoints (Assets, Accounts, Work Orders). Set to true to use out-of-the-box propagation for standard entities.

Recommendation: Keep enabled: true and default-rule.enabled: false in production. Configure specific rules for your use cases instead of relying on default rules.

Default Rules (When Enabled)

When default-rule.enabled: true, the following rules are automatically configured:

Entity

Integration API Endpoint

Private API Endpoint

Integration API Method

Assets

*/integration/assets/r1/assets

/assets/r1/assets

GET

Accounts

*/integration/assets/r1/accounts

/assets/r1/accounts

GET

Work Orders

*/integration/work-orders/r1/work-orders

/work-orders/r1/work-orders

GET

WO Attachments

*/integration/work-orders/r1/attachments

/work-orders/r1/work-orders/{workOrderId}/attachments

GET

Default rules propagate all extension fields (*) without prefix stripping. For more granular control, create custom rules. These rules filter by Integration API GET requests and propagate to Private API GET calls.

📝 How to Configure Custom Rules

Step 1: Create a Configuration Class

Create a new @Configuration class that extends ExtensionPropagationConfigBase:

AssetExtensionPropagationConfig.java
Java
package overit.geocall.integrationapirest.config;

import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import overit.geocall.integrationapirest.common.extension.propagation.ExtensionPropagationRegistry;
import overit.geocall.integrationapirest.common.extension.propagation.ExtensionPropagationRule;
import overit.geocall.integrationapirest.common.extension.propagation.ExtensionPropagationRuleBuilder;
import overit.geocall.integrationapirest.common.extension.propagation.config.ExtensionPropagationConfigBase;
import overit.geocall.integrationapirest.service.AssetService;

import java.util.List;

import static overit.geocall.integrationapirest.common.extension.propagation.HttpMethod.*;

@Configuration
@RequiredArgsConstructor
public class AssetExtensionPropagationConfig extends ExtensionPropagationConfigBase {

    private final ExtensionPropagationRegistry registry;

    @PostConstruct
    public void configure() {
        registerRules(registry, getRules());
    }

    @Override
    protected List<ExtensionPropagationRule> getRules() {
        return List.of(
            // Rule 1: Propagate characteristics filters from Integration API GET requests
            ExtensionPropagationRuleBuilder.forPath("characteristics.*")
                .toPrivateApiEndpoint("/assets/r1/assets/{assetId}/characteristics")
                .fromIntegrationApiEndpoint("/*/integration/assets/**")
                .fromIntegrationApiMethod(GET)  // Filter Integration API by GET method
                .fromMethod(AssetService.class, "getAsset")
                .stripPrefix()
                .named("Asset Characteristics Propagation")
                .build(),

            // Rule 2: Propagate validation extensions from Integration API POST requests
            // Extensions will only propagate if the service makes a GET call to Private API
            ExtensionPropagationRuleBuilder.forPath("validation.*")
                .toPrivateApiEndpoint("/assets/r1/assets/{assetId}/characteristics")
                .fromIntegrationApiEndpoint("/*/integration/assets/**")
                .fromIntegrationApiMethod(POST)  // Filter Integration API by POST method
                .stripPrefix()
                .named("Asset Validation Propagation")
                .build()
        );
    }
}

Important: Extension propagation to Private APIs works only for GET requests. The .fromIntegrationApiMethod() filter controls which Integration API requests trigger the rule, but the actual propagation only happens when the service makes a GET call to the Private API. The underlying system only enriches GetFilters objects used in GET operations.

Example: You can create a rule with .fromIntegrationApiMethod(POST) to capture extensions from Integration API POST requests, but those extensions will only be propagated if the service internally calls a Private API GET endpoint.

Step 2: Understanding Rule Components

Extension Field Pattern

Java
.forPath("characteristics.*")  // Match extension.characteristics.color, extension.characteristics.size, etc.

Pattern Syntax:

  • characteristics.* - Matches single level: characteristics.color ✓, characteristics.size ✓, characteristics.sub.field

  • metadata.** - Matches multiple levels: metadata.a ✓, metadata.a.b.c

  • *.color - Matches any prefix: characteristics.color ✓, metadata.color

  • * - Matches all extension fields

Target Private API Endpoint

Java
.toPrivateApiEndpoint("/assets/r1/assets/{assetId}/characteristics")

Supports path variables: {assetId}, {id}, {workOrderId}, etc.

Matches:

  • /assets/r1/assets/123/characteristics

  • /assets/r1/assets/456/characteristics

  • /assets/r1/assets/ABC-789/characteristics

Note: Only GET requests to these Private API endpoints will have extensions propagated. The endpoint must use GetFilters parameter.

Source Integration API Filter (Optional)

Java
.fromIntegrationApiEndpoint("/*/integration/assets/**")

Pattern Syntax:

  • /assets/r1/asset - Exact match

  • /assets/r1/* - Single level wildcard

  • /assets/** - Multi-level wildcard

  • /*/integration/assets/** - Any context path + multi-level

HTTP Method Filter (Optional, Type-Safe)

Java
import static overit.geocall.integrationapirest.common.extension.propagation.HttpMethod.*;

// Single method - filter Integration API requests by GET
.fromIntegrationApiMethod(GET)

// Multiple methods (chained) - filter Integration API by POST or PUT
.fromIntegrationApiMethod(POST)
.fromIntegrationApiMethod(PUT)

// Multiple methods (varargs) - filter Integration API by any of these methods
.fromIntegrationApiMethods(GET, POST, PUT, PATCH)

Supported HTTP Methods: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS

Important Distinction:

  • Integration API HTTP Method: The .fromIntegrationApiMethod() filter controls which Integration API requests (incoming client requests) trigger the rule. You can use any HTTP method (GET, POST, PUT, PATCH, DELETE).

  • Private API HTTP Method: The actual propagation to Private APIs only works for GET requests. Extensions are only added to GetFilters objects.

Use Case Example: Create a rule with .fromIntegrationApiMethod(POST) to capture validation extensions from Integration API POST requests. If the service then makes a GET call to the Private API to fetch related data, those validation extensions will be propagated to that GET call.

Service Method Filter (Optional, Type-Safe)

Java
// Type-safe (recommended)
.fromMethod(AssetService.class, "getAsset")

// String-based (backward compatible)
.fromMethod("AssetServiceImpl.getAsset")

Prefix Handling

Java
// Strip prefix (default)
.stripPrefix()  // "characteristics.color" → "color"

// Keep prefix
.keepPrefix()   // "characteristics.color" → "characteristics.color"

Rule Name (Optional but Recommended)

Java
.named("Asset Characteristics Propagation")  // Helps with debugging and logging

Step 3: Common Configuration Patterns

Pattern 1: Simple Propagation (Integration API GET)

Use Case: Propagate all extension fields matching a pattern from Integration API GET requests.

Java
ExtensionPropagationRuleBuilder.forPath("characteristics.*")
    .toPrivateApiEndpoint("/assets/r1/assets/{assetId}/characteristics")
    .fromIntegrationApiMethod(GET)  // Filter Integration API GET requests
    .stripPrefix()
    .build()

Example:

  • Input: GET /integration/assets/r1/asset?code=A123&extension.characteristics.color=red

  • Service calls: GET /assets/r1/assets/123/characteristics (Private API)

  • Output: GET /assets/r1/assets/123/characteristics?extension.color=red

Pattern 2: Integration API POST with Private API GET Propagation

Use Case: Capture extensions from Integration API POST requests and propagate them when the service makes GET calls to Private API.

Java
import static overit.geocall.integrationapirest.common.extension.propagation.HttpMethod.*;

// Capture extensions from Integration API POST requests
ExtensionPropagationRuleBuilder.forPath("validation.*")
    .toPrivateApiEndpoint("/assets/r1/assets/{assetId}/characteristics")
    .fromIntegrationApiMethod(POST)  // Filter Integration API POST requests
    .stripPrefix()
    .build()

Example:

  • Input: POST /integration/assets/r1/asset with body and extension.validation.strict=true

  • Service internally calls: GET /assets/r1/assets/123/characteristics (to fetch existing data)

  • Output: GET /assets/r1/assets/123/characteristics?extension.strict=true

Pattern 3: Multiple Integration API Methods

Use Case: Apply the same propagation rule for multiple Integration API HTTP methods.

Java
import static overit.geocall.integrationapirest.common.extension.propagation.HttpMethod.*;

// Filter Integration API by GET, POST, or PUT
ExtensionPropagationRuleBuilder.forPath("filter.*")
    .toPrivateApiEndpoint("/assets/r1/assets")
    .fromIntegrationApiMethods(GET, POST, PUT)  // Any of these Integration API methods
    .stripPrefix()
    .build()

Example:

  • Input: POST /integration/assets/r1/asset with extension.filter.status=active

  • Service internally calls: GET /assets/r1/assets (Private API)

  • Output: GET /assets/r1/assets?extension.status=active

Pattern 4: Conditional Propagation

Use Case: Only propagate when a specific flag is set.

Java
ExtensionPropagationRuleBuilder.forPath("details.*")
    .toPrivateApiEndpoint("/assets/r1/assets/{assetId}/details")
    .fromIntegrationApiMethod(GET)
    .when(ctx -> Boolean.TRUE.equals(ctx.getExtensions().get("includeDetails")))
    .stripPrefix()
    .build()

Example:

  • With flag: extension.includeDetails=true&extension.details.level=full → Propagates ✓

  • Without flag: extension.details.level=full → Does NOT propagate ✗

Pattern 5: Combined Filters

Use Case: Apply multiple filters for precise control.

Java
import static overit.geocall.integrationapirest.common.extension.propagation.HttpMethod.*;

ExtensionPropagationRuleBuilder.forPath("characteristics.*")
    .toPrivateApiEndpoint("/assets/r1/assets/{assetId}/characteristics")
    .fromIntegrationApiEndpoint("/*/integration/assets/**")  // Any asset endpoint
    .fromIntegrationApiMethod(GET)                          // Integration API GET requests
    .fromMethod(AssetService.class, "getAsset")             // Only from getAsset method
    .when(ctx -> Boolean.TRUE.equals(ctx.getExtensions().get("includeCharacteristics")))
    .stripPrefix()
    .named("Asset Characteristics Propagation")
    .build()

All conditions must be met:

  • Integration API request path matches /*/integration/assets/**

  • Integration API HTTP method is GET

  • Called from AssetService.getAsset

  • Extension field includeCharacteristics = true

  • Service makes a GET call to Private API

Pattern 6: Extension Transformation

Use Case: Modify extension values before propagation.

Java
ExtensionPropagationRuleBuilder.forPath("metadata.*")
    .toPrivateApiEndpoint("/assets/r1/assets/{assetId}/metadata")
    .fromIntegrationApiMethod(GET)
    .stripPrefix()
    .transform(extensions -> {
        // Add audit metadata
        extensions.put("_source", "integration-api");
        extensions.put("_timestamp", System.currentTimeMillis());
        return extensions;
    })
    .build()

🔍 How the System Works

Architecture Flow

1. Integration API Request (any HTTP method: GET, POST, PUT, etc.)
   ↓
   GET /integration/assets/r1/asset?code=A123&extension.characteristics.color=red
   
2. DtoExtensionArgumentResolver
   ↓
   Converts query params to nested map: {"characteristics": {"color": "red"}}
   (Only DtoExtension fields, NOT Dyno extensions)
   
3. ReactiveContextInitializerAspect
   ↓
   Extracts extensions + metadata (path, HTTP method, service method)
   Stores in Reactor Context
   
4. Service Layer (AssetService.getAsset)
   ↓
   Business logic executes
   
5. Private API Call (MUST be GET for propagation to work)
   ↓
   privateApiClient.getReactive("/assets/r1/assets/123/characteristics", ...)
   
6. ExtensionAwarePrivateApiHttpClient (Wrapper)
   ↓
   Intercepts the call (only enriches GET operations)
   
7. ExtensionPropagationService
   ↓
   - Retrieves extension context from Reactor Context
   - Matches endpoint against registered rules
   - Applies filters (API path, Integration API HTTP method, service method, conditions)
   - Flattens nested extensions to dot notation
   - Strips prefix if configured
   - Applies transformations
   - Enriches GetFilters with matched extensions (GET only)
   
8. Private API Call (Enriched GET)
   ↓
   GET /assets/r1/assets/123/characteristics?extension.color=red
   
9. Private API Response
   ↓
   Returns filtered characteristics (only red items)

Key Components

Component

Responsibility

DtoExtensionArgumentResolver

Converts dot notation query params to nested maps (DtoExtension only)

ReactiveContextInitializerAspect

Extracts extensions and metadata, stores in Reactor Context

ExtensionContext

Holds extension data + metadata (Integration API path, HTTP method, service method)

ExtensionPropagationRegistry

Stores and retrieves propagation rules

ExtensionPropagationRule

Defines a single propagation rule with filters (can filter by any Integration API HTTP method)

ExtensionPropagationService

Applies rules and enriches Private API GET calls only

ExtensionAwarePrivateApiHttpClient

Wrapper that intercepts Private API calls (only enriches GET requests)

PathMatcher

Glob-style pattern matching for paths

HttpMethod

Type-safe enum for Integration API HTTP method filtering

🧪 Testing Your Rules

Unit Test Example

Java
import static overit.geocall.integrationapirest.common.extension.propagation.HttpMethod.*;

@Test
void testCharacteristicsPropagation() {
    // Given: A rule for characteristics from Integration API GET requests
    ExtensionPropagationRule rule = ExtensionPropagationRuleBuilder
        .forPath("characteristics.*")
        .toPrivateApiEndpoint("/assets/r1/assets/{assetId}/characteristics")
        .fromIntegrationApiMethod(GET)  // Filter Integration API by GET
        .stripPrefix()
        .build();

    // Given: Extension context with characteristics
    Map<String, Object> extensions = Map.of(
        "characteristics", Map.of("color", "red", "size", "large")
    );
    
    ExtensionContext context = ExtensionContext.of(
        extensions,
        "/integration/assets/r1/asset",
        "GET",  // Integration API HTTP method
        "getAsset",
        "AssetService"
    );

    // When: Checking if rule matches the Private API endpoint
    boolean matches = rule.matches("/assets/r1/assets/123/characteristics", context);

    // Then: Rule should match
    assertThat(matches).isTrue();
    
    // When: Extracting extensions
    Map<String, Object> extracted = rule.extractExtensions(extensions);
    
    // Then: Extensions should be flattened and prefix stripped
    assertThat(extracted)
        .containsEntry("color", "red")
        .containsEntry("size", "large");
}

Integration Test Example

Java
@Test
void testExtensionPropagationEndToEnd() {
    // Given: Integration API GET request with extension fields (DtoExtension)
    webTestClient.get()
        .uri(uriBuilder -> uriBuilder
            .path("/integration/assets/r1/asset")
            .queryParam("code", "ASSET123")
            .queryParam("extension.characteristics.color", "red")
            .queryParam("extension.characteristics.size", "large")
            .build())
        .exchange()
        .expectStatus().isOk()
        .expectBody()
        .jsonPath("$.characteristics[?(@.color=='red')]").exists()
        .jsonPath("$.characteristics[?(@.size=='large')]").exists();
}

@Test
void testExtensionPropagationFromPost() {
    // Given: Integration API POST request with extension fields
    // Extensions will propagate if service makes GET calls to Private API
    webTestClient.post()
        .uri(uriBuilder -> uriBuilder
            .path("/integration/assets/r1/asset")
            .queryParam("extension.validation.strict", "true")
            .build())
        .bodyValue(assetData)
        .exchange()
        .expectStatus().isOk();
    // Extensions propagated to any Private API GET calls made during processing
}

🐛 Debugging and Troubleshooting

Enable Debug Logging

application.yml
YAML
logging:
  level:
    overit.geocall.integrationapirest.aspect.controller.reactive: DEBUG
    overit.geocall.integrationapirest.aspect.http: DEBUG
    overit.geocall.integrationapirest.common.extension: DEBUG

Log Output Examples

DEBUG ReactiveContextInitializerAspect - Extracted 4 extension fields from DtoExtension
DEBUG ReactiveContextInitializerAspect - Adding extension context for /integration/assets/r1/asset (GET): 4 fields
DEBUG ExtensionPropagationService - Matching endpoint: /assets/r1/assets/123/characteristics
DEBUG ExtensionPropagationService - Applied rule 'Asset Characteristics Propagation': 2 extension fields
DEBUG ExtensionPropagationService - Enriched GetFilters with extensions: {color=red, size=large}

Common Issues

Issue 1: Extensions Not Propagating

Checklist:

  • Is integrationlayer.extension-propagation.enabled: true?

  • Is the service method annotated with @ReactiveContextInitialize?

  • Does the DTO implement DtoExtension? (Not Dyno extensions!)

  • Is the rule registered in the registry?

  • Does the Private API endpoint pattern match the actual call?

  • Is the Private API call a GET request? (POST/PUT/PATCH/DELETE not supported)

  • Does the Private API method use GetFilters parameter?

  • Do all filters match (Integration API path, Integration API HTTP method, service method, condition)?

Issue 2: Wrong Extensions Propagated

Check:

  • Is the extension field pattern correct? (characteristics.* vs characteristics.**)

  • Is stripPrefix() or keepPrefix() configured correctly?

  • Are there multiple rules matching the same endpoint? (Check rule order)

Issue 3: Dyno Extensions Not Propagating

This is expected behavior!

The Extension Field Propagation System only works with DtoExtension fields from Integration API requests. Dyno extensions (internal dynamic object extensions) are not propagated by this system. They are managed separately by the Dyno framework.

Issue 4: Private API POST/PUT/PATCH/DELETE Not Receiving Extensions

This is a current limitation!

Extension propagation to Private APIs currently only works for GET requests. The system enriches GetFilters objects which are only used in GET operations.

Important: The .fromIntegrationApiMethod() filter can be set to any HTTP method (GET, POST, PUT, PATCH, DELETE) to control which Integration API requests trigger the rule. However, the actual propagation to the Private API only happens for GET calls.

Example: You can have a rule with .fromIntegrationApiMethod(POST) that captures extensions from Integration API POST requests. If the service then makes a GET call to the Private API, those extensions will be propagated. But if the service makes a POST/PUT/DELETE call to the Private API, the extensions will NOT be propagated.

Issue 5: HTTP Method Filter Not Working

Check:

  • Are you using the HttpMethod enum (not strings)?

  • Is the HTTP method being captured in the ExtensionContext?

  • Does the actual Integration API request HTTP method match the filter?

  • Remember: The filter controls Integration API requests, but propagation only works for Private API GET calls

Java
// ✅ Correct - using enum
import static overit.geocall.integrationapirest.common.extension.propagation.HttpMethod.*;
.fromIntegrationApiMethod(GET)  // Filter Integration API GET requests
.fromIntegrationApiMethod(POST) // Filter Integration API POST requests

// ❌ Wrong - strings not supported
// .fromIntegrationApiMethod("GET")

Issue 6: Path Variables Not Matching

Check:

  • Is the endpoint pattern using {variableName} syntax?

  • Does the actual endpoint have a value in that position?

Java
// Rule
.toPrivateApiEndpoint("/assets/r1/assets/{assetId}/characteristics")

// Actual Private API call
privateApiClient.getReactive("/assets/r1/assets/123/characteristics", ...)
// ✅ Matches - {assetId} = 123

📚 Best Practices

1. Use Descriptive Rule Names

Java
// ✅ Good
.named("Asset Characteristics Color Filter Propagation")

// ❌ Avoid
.named("Rule1")

2. Use Type-Safe Filters

Java
import static overit.geocall.integrationapirest.common.extension.propagation.HttpMethod.*;

// ✅ Good - type-safe
.fromIntegrationApiMethod(GET)
.fromMethod(AssetService.class, "getAsset")

// ❌ Avoid - error-prone
// .fromIntegrationApiMethod("GET")
// .fromMethod("AssetServiceImpl.getAsset")

3. Understand Integration API vs Private API HTTP Methods

Java
import static overit.geocall.integrationapirest.common.extension.propagation.HttpMethod.*;

// ✅ Good - clear understanding
// This rule captures extensions from Integration API POST requests
// and propagates them to Private API GET calls
.fromIntegrationApiMethod(POST)  // Integration API filter
// Private API must be GET for propagation to work

// ✅ Also good - explicit GET filter
.fromIntegrationApiMethod(GET)  // Integration API GET requests
// Propagates to Private API GET calls

4. Use Pattern Matching for Flexibility

Java
// ✅ Good - one rule for all versions
.fromIntegrationApiEndpoint("/*/integration/assets/**")

// ❌ Avoid - multiple rules for each version
.fromIntegrationApiEndpoint("/integration/assets/r1/asset")
.fromIntegrationApiEndpoint("/integration/assets/r2/asset")

5. Order Rules by Specificity

Java
// Specific rule - higher priority (lower order number)
ExtensionPropagationRuleBuilder.forPath("characteristics.color")
    .toPrivateApiEndpoint("/assets/r1/assets/{assetId}/characteristics")
    .withOrder(1)
    .build(),

// General rule - lower priority (higher order number)
ExtensionPropagationRuleBuilder.forPath("characteristics.*")
    .toPrivateApiEndpoint("/assets/r1/assets/{assetId}/characteristics")
    .withOrder(10)
    .build()

6. Document Your Rules

Java
@Override
protected List<ExtensionPropagationRule> getRules() {
    return List.of(
        // Rule 1: Propagate color/size filters to characteristics endpoint
        // Use case: Client sends GET request with filter extensions
        // Example: extension.characteristics.color=red → extension.color=red
        // Note: Propagates to Private API GET calls only
        ExtensionPropagationRuleBuilder.forPath("characteristics.*")
            .toPrivateApiEndpoint("/assets/r1/assets/{assetId}/characteristics")
            .fromIntegrationApiMethod(GET)
            .stripPrefix()
            .named("Asset Characteristics Filter Propagation")
            .build(),
            
        // Rule 2: Propagate validation extensions from POST requests
        // Use case: Client sends POST request with validation extensions
        // Extensions propagate to any Private API GET calls made during processing
        ExtensionPropagationRuleBuilder.forPath("validation.*")
            .toPrivateApiEndpoint("/assets/r1/assets/{assetId}/characteristics")
            .fromIntegrationApiMethod(POST)
            .stripPrefix()
            .named("Asset Validation Propagation from POST")
            .build()
    );
}

7. Test Your Rules

Always write unit tests for your propagation rules to ensure they work as expected.

📖 Quick Reference

Extension Field Patterns

Pattern

Matches

Example

characteristics.*

Single level

characteristics.color
characteristics.size
characteristics.sub.field

metadata.**

Multiple levels

metadata.a
metadata.a.b.c

*.color

Any prefix

characteristics.color
metadata.color

*

All fields

Any extension field

API Path Patterns

Pattern

Matches

Example

/assets/r1/asset

Exact match

/assets/r1/asset
/assets/r1/assets

/assets/r1/*

Single level

/assets/r1/asset
/assets/r1/assets
/assets/r2/asset

/assets/**

Multiple levels

/assets/r1/asset
/assets/r2/assets
/assets/v3/asset/details

/*/integration/assets/**

Any context + multi-level

/api/integration/assets/r1/asset
/integration/assets/r2/assets

HTTP Methods

Method

Integration API Filter

Private API Propagation

GET

✅ Supported

Supported

POST

✅ Supported

❌ Not supported

PUT

✅ Supported

❌ Not supported

PATCH

✅ Supported

❌ Not supported

DELETE

✅ Supported

❌ Not supported

HEAD

✅ Supported

❌ Not supported

OPTIONS

✅ Supported

❌ Not supported

Key Distinction:

  • Integration API Filter: The .fromIntegrationApiMethod() can filter by any HTTP method. This controls which incoming Integration API requests trigger the rule.

  • Private API Propagation: Extensions are only propagated to GET requests made to Private APIs, regardless of the Integration API HTTP method.

💡 Real-World Examples

Example 1: Asset Characteristics Filtering (Integration API GET)

Requirement: Allow clients to filter asset characteristics by color, size, and material using GET requests.

Java
import static overit.geocall.integrationapirest.common.extension.propagation.HttpMethod.*;

ExtensionPropagationRuleBuilder.forPath("characteristics.*")
    .toPrivateApiEndpoint("/assets/r1/assets/{assetId}/characteristics")
    .fromIntegrationApiEndpoint("/*/integration/assets/**")
    .fromIntegrationApiMethod(GET)  // Filter Integration API GET requests
    .stripPrefix()
    .named("Asset Characteristics Filter")
    .build()

Usage:

Integration API Request:  GET /integration/assets/r1/asset?code=A123&extension.characteristics.color=red&extension.characteristics.size=large
Service calls Private API: GET /assets/r1/assets/123/characteristics
Private API receives:      GET /assets/r1/assets/123/characteristics?extension.color=red&extension.size=large ✅

Example 2: Validation Extensions from POST Requests

Requirement: Capture validation extensions from Integration API POST requests and propagate them to Private API GET calls made during processing.

Java
import static overit.geocall.integrationapirest.common.extension.propagation.HttpMethod.*;

ExtensionPropagationRuleBuilder.forPath("validation.*")
    .toPrivateApiEndpoint("/assets/r1/assets/{assetId}/characteristics")
    .fromIntegrationApiEndpoint("/*/integration/assets/**")
    .fromIntegrationApiMethod(POST)  // Filter Integration API POST requests
    .stripPrefix()
    .named("Asset Validation Propagation")
    .build()

Usage:

Integration API Request:   POST /integration/assets/r1/asset?extension.validation.strict=true (with body)
Service calls Private API: GET /assets/r1/assets/123/characteristics (to fetch existing data)
Private API receives:      GET /assets/r1/assets/123/characteristics?extension.strict=true ✅

Note: If service calls POST /assets/r1/assets/123 (Private API), extensions are NOT propagated ❌

Example 3: Multiple Integration API Methods

Requirement: Apply the same filter propagation for GET, POST, and PUT Integration API requests.

Java
import static overit.geocall.integrationapirest.common.extension.propagation.HttpMethod.*;

ExtensionPropagationRuleBuilder.forPath("filter.*")
    .toPrivateApiEndpoint("/assets/r1/assets")
    .fromIntegrationApiEndpoint("/*/integration/assets/**")
    .fromIntegrationApiMethods(GET, POST, PUT)  // Any of these Integration API methods
    .stripPrefix()
    .named("Asset Filter Propagation")
    .build()

Usage:

Scenario 1 - Integration API GET:
  Integration API: GET /integration/assets/r1/asset?extension.filter.status=active
  Private API:     GET /assets/r1/assets?extension.status=active ✅

Scenario 2 - Integration API POST:
  Integration API: POST /integration/assets/r1/asset?extension.filter.status=active
  Private API:     GET /assets/r1/assets?extension.status=active ✅

Scenario 3 - Integration API PUT:
  Integration API: PUT /integration/assets/r1/asset/123?extension.filter.status=active
  Private API:     GET /assets/r1/assets?extension.status=active ✅

Example 4: Conditional Detail Propagation

Requirement: Only propagate detail extensions when includeDetails=true.

Java
import static overit.geocall.integrationapirest.common.extension.propagation.HttpMethod.*;

ExtensionPropagationRuleBuilder.forPath("details.*")
    .toPrivateApiEndpoint("/assets/r1/assets/{assetId}/details")
    .fromIntegrationApiMethod(GET)
    .when(ctx -> Boolean.TRUE.equals(ctx.getExtensions().get("includeDetails")))
    .stripPrefix()
    .named("Asset Details Conditional Propagation")
    .build()

Usage:

Request (with flag):  GET /integration/assets/r1/asset?code=A123&extension.includeDetails=true&extension.details.level=full
Private API receives: GET /assets/r1/assets/123/details?extension.level=full ✅

Request (no flag):    GET /integration/assets/r1/asset?code=A123&extension.details.level=full
Private API receives: GET /assets/r1/assets/123/details (no extensions) ✗

Need Help? If you have questions or need assistance configuring extension propagation rules, please reach out to the Integration API team.