diff --git a/adapters/vastbidder/bidder_macro.go b/adapters/vastbidder/bidder_macro.go index a88c8845027..f914cc3fb65 100644 --- a/adapters/vastbidder/bidder_macro.go +++ b/adapters/vastbidder/bidder_macro.go @@ -1,7 +1,6 @@ package vastbidder import ( - "bytes" "encoding/json" "fmt" "net/http" @@ -177,16 +176,23 @@ func (tag *BidderMacro) GetHeaders() http.Header { return http.Header{} } -// GetValueFromKV returns the value from KV map wrt key -func (tag *BidderMacro) GetValueFromKV(key string) string { - if tag.KV == nil { - return "" - } - key = strings.TrimPrefix(key, kvPrefix) - if value, found := tag.KV[key]; found { - return fmt.Sprintf("%v", value) +// GetValue returns the value from KV map wrt key +func (tag *BidderMacro) GetValue(key string) (string, bool) { + tempKeys := strings.Split(key, ".") + + if tempKeys[0] == prefixkv || tempKeys[0] == prefixkvm { + if tag.KV != nil { + if value, found := tag.KV[tempKeys[1]]; found { + if isMap(value) { + return getJsonString(value), true + } + return fmt.Sprintf("%v", value), true + } + return "", true + } + return "", true } - return "" + return "", false } /********************* Request *********************/ @@ -1212,10 +1218,14 @@ func (tag *BidderMacro) MacroKV(key string) string { keyval := "" for key, val := range tag.KV { + if isMap(val) { + jsonString := getJsonString(val) + keyval += fmt.Sprintf("%s=%v&", key, jsonString) + continue + } keyval += fmt.Sprintf("%s=%v&", key, val) } return strings.TrimSuffix(keyval, "&") - } // MacroKVM replace the kvm macro @@ -1223,18 +1233,7 @@ func (tag *BidderMacro) MacroKVM(key string) string { if tag.KV == nil { return "" } - var buf bytes.Buffer - encoder := json.NewEncoder(&buf) - - // Disable HTML escaping for special characters - encoder.SetEscapeHTML(false) - - if err := encoder.Encode(tag.KV); err != nil { - return "" - } - jsonString := strings.TrimRight(buf.String(), "\n") - - return jsonString + return getJsonString(tag.KV) } /********************* Request Headers *********************/ diff --git a/adapters/vastbidder/bidder_macro_test.go b/adapters/vastbidder/bidder_macro_test.go index e5f27dfc2be..1d36c9b6200 100644 --- a/adapters/vastbidder/bidder_macro_test.go +++ b/adapters/vastbidder/bidder_macro_test.go @@ -3,6 +3,9 @@ package vastbidder import ( "fmt" "net/http" + "reflect" + "sort" + "strings" "testing" "github.com/prebid/openrtb/v19/adcom1" @@ -1264,7 +1267,7 @@ func TestBidderMacro_MacroTest(t *testing.T) { } } -func TestBidderGetValueFromKV(t *testing.T) { +func TestBidderGetValue(t *testing.T) { type fields struct { KV map[string]interface{} } @@ -1308,13 +1311,74 @@ func TestBidderGetValueFromKV(t *testing.T) { args: args{key: "kv.anykey"}, want: "", }, + + { + name: "key_with_value_as_map", + fields: fields{KV: map[string]interface{}{ + "name": "test", + "country": map[string]interface{}{ + "state": "MH", + "pincode": 411041, + "url": "http//example.com?k1=v1&k2=v2", + }, + }}, + args: args{key: "kvm.country"}, + want: "{\"pincode\":411041,\"state\":\"MH\",\"url\":\"http//example.com?k1=v1&k2=v2\"}", + }, + { + name: "key_with_value_as_map", + fields: fields{KV: map[string]interface{}{ + "name": "test", + "country": map[string]interface{}{ + "state": "MH", + "pincode": 411041, + "url": "http//example.com?k1=v1&k2=v2", + }, + }}, + args: args{key: "kvm.country"}, + want: "{\"pincode\":411041,\"state\":\"MH\",\"url\":\"http//example.com?k1=v1&k2=v2\"}", + }, + { + name: "key_with_value_as_nested_map", + fields: fields{KV: map[string]interface{}{ + "name": "test", + "country": map[string]interface{}{ + "state": "MH", + "pincode": 411041, + "url": "http//example.com?k1=v1&k2=v2", + "metadata": map[string]interface{}{ + "k1": "v1", + "k2": "v2", + }, + }, + }}, + args: args{key: "kvm.country"}, + want: "{\"metadata\":{\"k1\":\"v1\",\"k2\":\"v2\"},\"pincode\":411041,\"state\":\"MH\",\"url\":\"http//example.com?k1=v1&k2=v2\"}", + }, + { + name: "key_with_value_as_nested_map_", + fields: fields{KV: map[string]interface{}{ + "name": "test", + "country": map[string]interface{}{ + "state": "MH", + "pincode": 411041, + "url": "http//example.com?k1=v1&k2=v2", + "metadata": map[string]interface{}{ + "k1": "v1", + "k2": "v2", + }, + }, + }}, + args: args{key: "someprefix.country"}, + want: "", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tag := &BidderMacro{ KV: tt.fields.KV, } - value := tag.GetValueFromKV(tt.args.key) + value, _ := tag.GetValue(tt.args.key) assert.Equal(t, tt.want, value, tt.name) }) } @@ -1328,10 +1392,11 @@ func TestBidderMacroKV(t *testing.T) { key string } tests := []struct { - name string - fields fields - args args - want string + name string + fields fields + args args + want string + isMatched bool }{ { name: "Valid_test", @@ -1339,30 +1404,73 @@ func TestBidderMacroKV(t *testing.T) { "name": "test", "age": "22", }}, - args: args{key: "kv"}, - want: "name=test&age=22", + args: args{key: "kv"}, + want: "name=test&age=22", + isMatched: true, }, { name: "Valid_test_with_url", fields: fields{KV: map[string]interface{}{ - "name": "test", - "age": "22", - "url": "http://example.com?k1=v1&k2=v2", + "age": "22", + "url": "http://example.com?k1=v1&k2=v2", }}, - args: args{key: "kv"}, - want: "name=test&age=22&url=http://example.com?k1=v1&k2=v2", + args: args{key: "kv"}, + want: "age=22&url=http://example.com?k1=v1&k2=v2", + isMatched: true, }, { - name: "Empty_KV_map", - fields: fields{KV: nil}, - args: args{key: "kv"}, - want: "", + name: "Empty_KV_map", + fields: fields{KV: nil}, + args: args{key: "kv"}, + want: "", + isMatched: true, }, { - name: "KV_map_with_no_key_val_pair", - fields: fields{KV: map[string]interface{}{}}, - args: args{key: "kv"}, - want: "", + name: "KV_map_with_no_key_val_pair", + fields: fields{KV: map[string]interface{}{}}, + args: args{key: "kv"}, + want: "", + isMatched: true, + }, + { + name: "key_with_value_map", + fields: fields{KV: map[string]interface{}{ + "age": 22, + "country": map[string]interface{}{ + "state": "MH", + "pincode": 411041, + }, + }}, + args: args{key: "kv"}, + want: "age=22&country={\"pincode\":411041,\"state\":\"MH\"}", + isMatched: true, + }, + { + name: "key_with_value_as_nested_map", + fields: fields{KV: map[string]interface{}{ + "age": 22, + "country": map[string]interface{}{ + "state": "MH", + "pincode": 411041, + "metadata": map[string]interface{}{ + "k1": "v1", + "k2": "v2", + }, + }, + }}, + args: args{key: "kv"}, + want: "age=22&country={\"metadata\":{\"k1\":\"v1\",\"k2\":\"v2\"},\"pincode\":411041,\"state\":\"MH\"}", + isMatched: true, + }, + { + name: "string_not_matched", + fields: fields{KV: map[string]interface{}{ + "name": "test", + "age": "22", + }}, + args: args{key: "kv"}, + want: "name=test1&age=22", + isMatched: false, }, } for _, tt := range tests { @@ -1371,11 +1479,26 @@ func TestBidderMacroKV(t *testing.T) { KV: tt.fields.KV, } got := tag.MacroKV(tt.args.key) - assert.Equal(t, tt.want, got, tt.name) + + isEqual := isEqual(tt.want, got) + assert.Equal(t, tt.isMatched, isEqual) }) } } +// isEqual compare two shuffled strings containing key-value pairs and determine if they have the same key-value pairs regardless of their order within the string. +func isEqual(want, got string) bool { + + w := strings.Split(want, "&") + g := strings.Split(got, "&") + + sort.Strings(w) + sort.Strings(g) + + return reflect.DeepEqual(w, g) + +} + func TestBidderMacroKVM(t *testing.T) { type fields struct { KV map[string]interface{} @@ -1437,6 +1560,23 @@ func TestBidderMacroKVM(t *testing.T) { args: args{key: "kvm"}, want: "{\"name\":\"test\",\"url\":\"http://example.com?k1=v1&k2=v2\"}", }, + { + name: "key_with_value_as_nested_map", + fields: fields{KV: map[string]interface{}{ + "name": "test", + "age": 22, + "country": map[string]interface{}{ + "state": "MH", + "pincode": 411041, + "metadata": map[string]interface{}{ + "k1": "v1", + "k2": "v2", + }, + }, + }}, + args: args{key: "kvm"}, + want: "{\"age\":22,\"country\":{\"metadata\":{\"k1\":\"v1\",\"k2\":\"v2\"},\"pincode\":411041,\"state\":\"MH\"},\"name\":\"test\"}", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/adapters/vastbidder/constant.go b/adapters/vastbidder/constant.go index 6a0d707fab5..ddcc7a75d65 100644 --- a/adapters/vastbidder/constant.go +++ b/adapters/vastbidder/constant.go @@ -169,9 +169,10 @@ const ( ) const ( - prebid = "prebid" - keyval = "keyval" - kvPrefix = "kv." + prebid = "prebid" + keyval = "keyval" + prefixkv = "kv" + prefixkvm = "kvm" ) var ParamKeys = []string{"param1", "param2", "param3", "param4", "param5"} diff --git a/adapters/vastbidder/ibidder_macro.go b/adapters/vastbidder/ibidder_macro.go index 6d7e555408c..a503c667e52 100644 --- a/adapters/vastbidder/ibidder_macro.go +++ b/adapters/vastbidder/ibidder_macro.go @@ -18,7 +18,7 @@ type IBidderMacro interface { SetAdapterConfig(*config.Adapter) GetURI() string GetHeaders() http.Header - GetValueFromKV(string) string + GetValue(string) (string, bool) //getAllHeaders returns default and custom heades getAllHeaders() http.Header diff --git a/adapters/vastbidder/macro_processor.go b/adapters/vastbidder/macro_processor.go index 354d922e1db..c6f2f3d871e 100644 --- a/adapters/vastbidder/macro_processor.go +++ b/adapters/vastbidder/macro_processor.go @@ -88,10 +88,8 @@ func (mp *MacroProcessor) processKey(key string) (string, bool) { tmpKey = tmpKey[0 : len(tmpKey)-macroEscapeSuffixLen] nEscaping++ continue - } else if strings.HasPrefix(tmpKey, kvPrefix) { - value = mp.bidderMacro.GetValueFromKV(tmpKey) - found = true - break + } else { + value, found = mp.bidderMacro.GetValue(tmpKey) } break } diff --git a/adapters/vastbidder/util.go b/adapters/vastbidder/util.go index 8ad02535ec6..2547bd7917d 100644 --- a/adapters/vastbidder/util.go +++ b/adapters/vastbidder/util.go @@ -6,6 +6,7 @@ import ( "fmt" "math/rand" "strconv" + "strings" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/openrtb_ext" @@ -68,3 +69,23 @@ func NormalizeJSON(obj map[string]interface{}) map[string]string { var GetRandomID = func() string { return strconv.FormatInt(rand.Int63(), intBase) } + +func getJsonString(kvmap any) string { + + var buf bytes.Buffer + encoder := json.NewEncoder(&buf) + + // Disable HTML escaping for special characters + encoder.SetEscapeHTML(false) + + if err := encoder.Encode(kvmap); err != nil { + return "" + } + return strings.TrimRight(buf.String(), "\n") + +} + +func isMap(data any) bool { + t := fmt.Sprintf("%T", data) + return strings.HasPrefix(t, "map[") +}