Merge pull request #575 from Beckn-One/573-reqmapper

573 - New plugin - schema mapper
This commit is contained in:
Mayuresh A Nirhali
2025-12-05 16:15:15 +05:30
committed by GitHub
9 changed files with 765 additions and 5 deletions

105
CONFIG.md
View File

@@ -624,6 +624,109 @@ middleware:
---
#### 11. Reqmapper Plugin
**Purpose**: Transform Beckn payloads between protocol versions or shapes using JSONata before the request continues through the handler. Mount it inside the `middleware` list wherever translation is required.
```yaml
middleware:
- id: reqmapper
config:
role: bap # Use `bpp` when running inside a BPP handler
mappingsFile: ./config/mappings.yaml
```
**Parameters**:
- `role`: Required. Determines which JSONata expression is evaluated (`bapMappings` or `bppMappings`) for the current action.
- `mappingsFile`: Required. Absolute or relative path to a YAML file that contains the JSONata expressions for every action.
**Mapping file structure**:
```yaml
mappings:
<action-name>:
bapMappings: |
# JSONata expression applied when `role: bap`
bppMappings: |
# JSONata expression applied when `role: bpp`
```
Each action entry is optional—if no mapping exists for the current action, the original request body is passed through unchanged. JSONata expressions receive the entire Beckn request as input (`$`) and must return the full payload that should replace it.
**Sample mapping file**:
```yaml
mappings:
search:
bapMappings: |
{
"context": {
"action": "discover",
"version": "2.0.0",
"domain": "beckn.one:retail",
"bap_id": $.context.bap_id,
"bap_uri": $.context.bap_uri,
"transaction_id": $.context.transaction_id,
"message_id": $.context.message_id,
"timestamp": $.context.timestamp
},
"message": {
"filters": $.message.intent.category ? {
"type": "jsonpath",
"expression": "$[?(@.category.code == '" & $.message.intent.category.descriptor.code & "')]"
} : null
}
}
bppMappings: |
{
"context": {
"action": "search",
"version": "1.1.0",
"domain": "retail",
"bap_id": $.context.bap_id,
"bap_uri": $.context.bap_uri,
"transaction_id": $.context.transaction_id,
"message_id": $.context.message_id,
"timestamp": $.context.timestamp
},
"message": {
"intent": {
"category": $.message.filters ? {
"descriptor": {
"code": $substringAfter($substringBefore($.message.filters.expression, "'"), "== '")
}
} : null
}
}
}
on_search:
bapMappings: |
{
"context": $.context,
"message": {
"catalog": {
"descriptor": $.message.catalogs[0]."beckn:descriptor" ? {
"name": $.message.catalogs[0]."beckn:descriptor"."schema:name"
} : null
}
}
}
bppMappings: |
{
"context": $.context,
"message": {
"catalogs": [{
"@type": "beckn:Catalog",
"beckn:items": $.message.catalog.providers[].items[].
{
"@type": "beckn:Item",
"beckn:id": id
}
}]
}
}
```
The sample illustrates how a single mapping file can convert `search` requests and `on_search` responses between Beckn 1.1.0 (BAP) and Beckn 2.0.0 (BPP) payload shapes. You can define as many action entries as needed, and the plugin will compile and cache the JSONata expressions on startup.
---
## Routing Configuration
### Routing Rules File Structure
@@ -1066,4 +1169,4 @@ modules:
httpClientConfig:
maxIdleConns: 1000
maxIdleConnsPerHost: 200
idleConnTimeout: 300
idleConnTimeout: 300

5
go.mod
View File

@@ -20,7 +20,7 @@ require (
require github.com/zenazn/pkcs7pad v0.0.0-20170308005700-253a5b1f0e03
require golang.org/x/text v0.23.0 // indirect
require golang.org/x/text v0.26.0 // indirect
require (
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
@@ -50,7 +50,7 @@ require (
github.com/ryanuber/go-glob v1.0.0 // indirect
github.com/woodsbury/decimal128 v1.3.0 // indirect
golang.org/x/net v0.38.0 // indirect
golang.org/x/sys v0.31.0 // indirect
golang.org/x/sys v0.38.0 // indirect
golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 // indirect
)
@@ -59,6 +59,7 @@ require (
github.com/google/uuid v1.6.0
github.com/hashicorp/go-retryablehttp v0.7.7
github.com/hashicorp/vault/api v1.16.0
github.com/jsonata-go/jsonata v0.0.0-20250709164031-599f35f32e5f
github.com/rabbitmq/amqp091-go v1.10.0
github.com/redis/go-redis/v9 v9.8.0
github.com/rs/zerolog v1.34.0

8
go.sum
View File

@@ -62,6 +62,8 @@ github.com/hashicorp/vault/api v1.16.0 h1:nbEYGJiAPGzT9U4oWgaaB0g+Rj8E59QuHKyA5L
github.com/hashicorp/vault/api v1.16.0/go.mod h1:KhuUhzOD8lDSk29AtzNjgAu2kxRA9jL9NAbkFlqvkBA=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/jsonata-go/jsonata v0.0.0-20250709164031-599f35f32e5f h1:JnGon8QHtmjFPq0NcSu8OTEnQDDEgFME7gtj/NkjCUo=
github.com/jsonata-go/jsonata v0.0.0-20250709164031-599f35f32e5f/go.mod h1:rYUEOEiieWXHNCE/eDXV/o5s7jZ2VyUzQKbqVns9pik=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@@ -134,8 +136,10 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 h1:NusfzzA6yGQ+ua51ck7E3omNUX/JuqbFSaRGqU8CcLI=
golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@@ -16,6 +16,7 @@ plugins=(
"registry"
"dediregistry"
"reqpreprocessor"
"reqmapper"
"router"
"schemavalidator"
"schemav2validator"

View File

@@ -0,0 +1,23 @@
package main
import (
"context"
"net/http"
"github.com/beckn-one/beckn-onix/pkg/plugin/implementation/reqmapper"
)
type provider struct{}
func (p provider) New(ctx context.Context, c map[string]string) (func(http.Handler) http.Handler, error) {
config := &reqmapper.Config{}
if role, ok := c["role"]; ok {
config.Role = role
}
if mappingsFile, ok := c["mappingsFile"]; ok {
config.MappingsFile = mappingsFile
}
return reqmapper.NewReqMapper(config)
}
var Provider = provider{}

View File

@@ -0,0 +1,84 @@
package main
import (
"bytes"
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
)
func TestProviderNewSuccess(t *testing.T) {
p := provider{}
middleware, err := p.New(context.Background(), map[string]string{"role": "bap"})
if err != nil {
t.Fatalf("provider.New returned unexpected error: %v", err)
}
if middleware == nil {
t.Fatalf("provider.New returned nil middleware")
}
payload := map[string]interface{}{
"context": map[string]interface{}{
"action": "search",
"domain": "retail",
"version": "1.1.0",
"bap_id": "bap.example",
"bap_uri": "https://bap.example/api",
"transaction_id": "txn-1",
"message_id": "msg-1",
"timestamp": "2023-01-01T10:00:00Z",
},
"message": map[string]interface{}{
"intent": map[string]interface{}{
"fulfillment": map[string]interface{}{
"start": map[string]interface{}{
"location": map[string]interface{}{"gps": "0,0"},
},
"end": map[string]interface{}{
"location": map[string]interface{}{"gps": "1,1"},
},
},
},
},
}
body, err := json.Marshal(payload)
if err != nil {
t.Fatalf("failed to marshal payload: %v", err)
}
called := false
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
called = true
w.WriteHeader(http.StatusNoContent)
})
req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(body))
rec := httptest.NewRecorder()
middleware(next).ServeHTTP(rec, req)
if !called {
t.Fatalf("expected downstream handler to be invoked")
}
if rec.Code != http.StatusNoContent {
t.Fatalf("unexpected response code: got %d want %d", rec.Code, http.StatusNoContent)
}
}
func TestProviderNewMissingRole(t *testing.T) {
p := provider{}
if _, err := p.New(context.Background(), map[string]string{}); err == nil {
t.Fatalf("expected error when role is missing")
}
}
func TestProviderNewInvalidRole(t *testing.T) {
p := provider{}
_, err := p.New(context.Background(), map[string]string{"role": "invalid"})
if err == nil {
t.Fatalf("expected error for invalid role")
}
}

View File

@@ -0,0 +1,287 @@
package reqmapper
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"os"
"sync"
"github.com/beckn-one/beckn-onix/pkg/log"
"github.com/jsonata-go/jsonata"
"gopkg.in/yaml.v3"
)
// Config represents the configuration for the request mapper middleware.
type Config struct {
Role string `yaml:"role"` // "bap" or "bpp"
MappingsFile string `yaml:"mappingsFile"` // required path to mappings YAML
}
// MappingEngine handles JSONata-based transformations
type MappingEngine struct {
config *Config
jsonataInstance jsonata.JSONataInstance
bapMaps map[string]jsonata.Expression
bppMaps map[string]jsonata.Expression
mappings map[string]builtinMapping
mappingSource string
mutex sync.RWMutex
initialized bool
}
type builtinMapping struct {
BAP string `yaml:"bapMappings"`
BPP string `yaml:"bppMappings"`
}
type mappingFile struct {
Mappings map[string]builtinMapping `yaml:"mappings"`
}
var (
engineInstance *MappingEngine
engineOnce sync.Once
)
// NewReqMapper returns a middleware that maps requests using JSONata expressions
func NewReqMapper(cfg *Config) (func(http.Handler) http.Handler, error) {
if err := validateConfig(cfg); err != nil {
return nil, err
}
// Initialize the mapping engine
engine, err := initMappingEngine(cfg)
if err != nil {
return nil, fmt.Errorf("failed to initialize mapping engine: %w", err)
}
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "Failed to read request body", http.StatusBadRequest)
return
}
var req map[string]interface{}
ctx := r.Context()
if err := json.Unmarshal(body, &req); err != nil {
http.Error(w, "Failed to decode request body", http.StatusBadRequest)
return
}
reqContext, ok := req["context"].(map[string]interface{})
if !ok {
http.Error(w, "context field not found or invalid", http.StatusBadRequest)
return
}
action, ok := reqContext["action"].(string)
if !ok {
http.Error(w, "action field not found or invalid", http.StatusBadRequest)
return
}
// Apply transformation
mappedBody, err := engine.Transform(ctx, action, req, cfg.Role)
if err != nil {
log.Errorf(ctx, err, "Transformation failed for action %s", action)
// Fall back to original body on error
mappedBody = body
}
r.Body = io.NopCloser(bytes.NewBuffer(mappedBody))
r.ContentLength = int64(len(mappedBody))
r = r.WithContext(ctx)
next.ServeHTTP(w, r)
})
}, nil
}
// initMappingEngine initializes or returns existing mapping engine
func initMappingEngine(cfg *Config) (*MappingEngine, error) {
var initErr error
engineOnce.Do(func() {
engineInstance = &MappingEngine{
config: cfg,
bapMaps: make(map[string]jsonata.Expression),
bppMaps: make(map[string]jsonata.Expression),
}
instance, err := jsonata.OpenLatest()
if err != nil {
initErr = fmt.Errorf("failed to initialize jsonata: %w", err)
return
}
engineInstance.jsonataInstance = instance
if err := engineInstance.loadBuiltinMappings(); err != nil {
initErr = err
return
}
engineInstance.initialized = true
})
if initErr != nil {
return nil, initErr
}
if !engineInstance.initialized {
return nil, errors.New("mapping engine failed to initialize")
}
return engineInstance, nil
}
func (e *MappingEngine) loadMappingsFromConfig() (map[string]builtinMapping, string, error) {
if e.config == nil || e.config.MappingsFile == "" {
return nil, "", errors.New("mappingsFile must be provided in config")
}
data, err := os.ReadFile(e.config.MappingsFile)
if err != nil {
return nil, "", fmt.Errorf("failed to read mappings file %s: %w", e.config.MappingsFile, err)
}
source := e.config.MappingsFile
var parsed mappingFile
if err := yaml.Unmarshal(data, &parsed); err != nil {
return nil, "", fmt.Errorf("failed to parse mappings from %s: %w", source, err)
}
if len(parsed.Mappings) == 0 {
return nil, "", fmt.Errorf("no mappings found in %s", source)
}
return parsed.Mappings, source, nil
}
// loadBuiltinMappings compiles JSONata expressions for every action/direction pair from the configured mappings file.
func (e *MappingEngine) loadBuiltinMappings() error {
mappings, source, err := e.loadMappingsFromConfig()
if err != nil {
return err
}
e.bapMaps = make(map[string]jsonata.Expression, len(mappings))
e.bppMaps = make(map[string]jsonata.Expression, len(mappings))
for action, mapping := range mappings {
bapExpr, err := e.jsonataInstance.Compile(mapping.BAP, false)
if err != nil {
return fmt.Errorf("failed to compile BAP mapping for action %s: %w", action, err)
}
bppExpr, err := e.jsonataInstance.Compile(mapping.BPP, false)
if err != nil {
return fmt.Errorf("failed to compile BPP mapping for action %s: %w", action, err)
}
e.bapMaps[action] = bapExpr
e.bppMaps[action] = bppExpr
}
e.mappings = mappings
e.mappingSource = source
log.Infof(
context.Background(),
"Loaded %d BAP mappings and %d BPP mappings from %s",
len(e.bapMaps),
len(e.bppMaps),
source,
)
return nil
}
// Transform applies the appropriate mapping based on role and action
func (e *MappingEngine) Transform(ctx context.Context, action string, req map[string]interface{}, role string) ([]byte, error) {
e.mutex.RLock()
defer e.mutex.RUnlock()
var expr jsonata.Expression
var found bool
// Select appropriate mapping based on role
switch role {
case "bap":
expr, found = e.bapMaps[action]
case "bpp":
expr, found = e.bppMaps[action]
default:
return json.Marshal(req)
}
// If no mapping found, return original request
if !found || expr == nil {
log.Debugf(ctx, "No mapping found for action: %s, role: %s", action, role)
return json.Marshal(req)
}
// Marshal request for JSONata evaluation
input, err := json.Marshal(req)
if err != nil {
return nil, fmt.Errorf("failed to marshal request for mapping: %w", err)
}
// Apply JSONata transformation
result, err := expr.Evaluate(input, nil)
if err != nil {
return nil, fmt.Errorf("JSONata evaluation failed: %w", err)
}
log.Debugf(ctx, "Successfully transformed %s request using %s mapping, %s", action, role, result)
return result, nil
}
// ReloadMappings reloads all mapping files (useful for hot-reload scenarios)
func (e *MappingEngine) ReloadMappings() error {
e.mutex.Lock()
defer e.mutex.Unlock()
return e.loadBuiltinMappings()
}
// GetMappingInfo returns information about loaded mappings
func (e *MappingEngine) GetMappingInfo() map[string]interface{} {
e.mutex.RLock()
defer e.mutex.RUnlock()
bapActions := make([]string, 0, len(e.bapMaps))
for action := range e.bapMaps {
bapActions = append(bapActions, action)
}
bppActions := make([]string, 0, len(e.bppMaps))
for action := range e.bppMaps {
bppActions = append(bppActions, action)
}
return map[string]interface{}{
"bap_mappings": bapActions,
"bpp_mappings": bppActions,
"mappings_source": e.mappingSource,
"action_count": len(e.mappings),
}
}
func validateConfig(cfg *Config) error {
if cfg == nil {
return errors.New("config cannot be nil")
}
if cfg.Role != "bap" && cfg.Role != "bpp" {
return errors.New("role must be either 'bap' or 'bpp'")
}
if cfg.MappingsFile == "" {
return errors.New("mappingsFile is required")
}
return nil
}

View File

@@ -0,0 +1,240 @@
package reqmapper
import (
"bytes"
"context"
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"reflect"
"sync"
"testing"
)
func resetEngineState(t *testing.T) {
t.Helper()
engineInstance = nil
engineOnce = sync.Once{}
}
func testMappingsFile(t *testing.T) string {
t.Helper()
path := filepath.Join("testdata", "mappings.yaml")
if _, err := os.Stat(path); err != nil {
t.Fatalf("test mappings file missing: %v", err)
}
return path
}
func initTestEngine(t *testing.T) *MappingEngine {
t.Helper()
resetEngineState(t)
engine, err := initMappingEngine(&Config{
Role: "bap",
MappingsFile: testMappingsFile(t),
})
if err != nil {
t.Fatalf("failed to init mapping engine: %v", err)
}
return engine
}
func TestNewReqMapper_InvalidConfig(t *testing.T) {
t.Run("nil config", func(t *testing.T) {
if _, err := NewReqMapper(nil); err == nil {
t.Fatalf("expected error for nil config")
}
})
t.Run("invalid role", func(t *testing.T) {
if _, err := NewReqMapper(&Config{Role: "invalid"}); err == nil {
t.Fatalf("expected error for invalid role")
}
})
}
func TestNewReqMapper_MiddlewareTransformsRequest(t *testing.T) {
resetEngineState(t)
mw, err := NewReqMapper(&Config{
Role: "bap",
MappingsFile: testMappingsFile(t),
})
if err != nil {
t.Fatalf("NewReqMapper returned error: %v", err)
}
startLocation := map[string]interface{}{
"gps": "12.9716,77.5946",
"city": "Bengaluru",
}
endLocation := map[string]interface{}{
"gps": "13.0827,80.2707",
"city": "Chennai",
}
requestPayload := map[string]interface{}{
"context": map[string]interface{}{
"domain": "retail",
"action": "search",
"version": "1.1.0",
"bap_id": "bap.example",
"bap_uri": "https://bap.example/api",
"transaction_id": "txn-1",
"message_id": "msg-1",
"timestamp": "2023-01-01T10:00:00Z",
},
"message": map[string]interface{}{
"intent": map[string]interface{}{
"item": map[string]interface{}{
"id": "item-1",
},
"provider": map[string]interface{}{
"id": "provider-1",
},
"fulfillment": map[string]interface{}{
"start": map[string]interface{}{
"location": startLocation,
},
"end": map[string]interface{}{
"location": endLocation,
},
},
},
},
}
body, err := json.Marshal(requestPayload)
if err != nil {
t.Fatalf("failed to marshal request payload: %v", err)
}
var captured map[string]interface{}
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
data, err := io.ReadAll(r.Body)
if err != nil {
t.Fatalf("failed to read request in handler: %v", err)
}
if err := json.Unmarshal(data, &captured); err != nil {
t.Fatalf("failed to unmarshal transformed payload: %v", err)
}
w.WriteHeader(http.StatusOK)
})
req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(body))
rec := httptest.NewRecorder()
mw(next).ServeHTTP(rec, req)
if captured == nil {
t.Fatalf("middleware did not forward request to next handler")
}
message, ok := captured["message"].(map[string]interface{})
if !ok {
t.Fatalf("expected message field in transformed payload")
}
filters, ok := message["filters"].(map[string]interface{})
if !ok {
t.Fatalf("expected filters in transformed payload")
}
if pickup := filters["pickup"]; !reflect.DeepEqual(pickup, startLocation) {
t.Fatalf("pickup location mismatch\ngot: %#v\nwant: %#v", pickup, startLocation)
}
if drop := filters["drop"]; !reflect.DeepEqual(drop, endLocation) {
t.Fatalf("drop location mismatch\ngot: %#v\nwant: %#v", drop, endLocation)
}
}
func TestMappingEngine_TransformFallbackForUnknownAction(t *testing.T) {
engine := initTestEngine(t)
req := map[string]interface{}{
"context": map[string]interface{}{
"action": "unknown_action",
},
"message": map[string]interface{}{},
}
expected, err := json.Marshal(req)
if err != nil {
t.Fatalf("failed to marshal expected payload: %v", err)
}
result, err := engine.Transform(context.Background(), "unknown_action", req, "bap")
if err != nil {
t.Fatalf("Transform returned error: %v", err)
}
if !bytes.Equal(result, expected) {
t.Fatalf("expected Transform to return original payload")
}
}
func TestMappingEngine_TransformFallbackForUnknownRole(t *testing.T) {
engine := initTestEngine(t)
req := map[string]interface{}{
"context": map[string]interface{}{
"action": "search",
},
"message": map[string]interface{}{},
}
expected, err := json.Marshal(req)
if err != nil {
t.Fatalf("failed to marshal expected payload: %v", err)
}
result, err := engine.Transform(context.Background(), "search", req, "unknown-role")
if err != nil {
t.Fatalf("Transform returned error: %v", err)
}
if !bytes.Equal(result, expected) {
t.Fatalf("expected Transform to return original payload when role is unknown")
}
}
func TestMappingEngine_ReloadMappings(t *testing.T) {
engine := initTestEngine(t)
engine.mutex.RLock()
originalBAP := len(engine.bapMaps)
originalBPP := len(engine.bppMaps)
engine.mutex.RUnlock()
if originalBAP == 0 || originalBPP == 0 {
t.Fatalf("expected test mappings to be loaded")
}
engine.mutex.Lock()
for action := range engine.bapMaps {
delete(engine.bapMaps, action)
break
}
engine.mutex.Unlock()
engine.mutex.RLock()
if len(engine.bapMaps) == originalBAP {
engine.mutex.RUnlock()
t.Fatalf("expected BAP map to be altered before reload")
}
engine.mutex.RUnlock()
if err := engine.ReloadMappings(); err != nil {
t.Fatalf("ReloadMappings returned error: %v", err)
}
engine.mutex.RLock()
defer engine.mutex.RUnlock()
if len(engine.bapMaps) != originalBAP {
t.Fatalf("expected BAP mappings to be reloaded, got %d want %d", len(engine.bapMaps), originalBAP)
}
if len(engine.bppMaps) != originalBPP {
t.Fatalf("expected BPP mappings to be reloaded, got %d want %d", len(engine.bppMaps), originalBPP)
}
}

View File

@@ -0,0 +1,17 @@
mappings:
search:
bapMappings: |
{
"context": $.context,
"message": {
"filters": {
"pickup": $.message.intent.fulfillment.start.location,
"drop": $.message.intent.fulfillment.end.location
}
}
}
bppMappings: |
{
"context": $.context,
"message": $.message
}