Skip to content

Commit

Permalink
First Party Data: Updates OpenRTB Field Merging (prebid#2825)
Browse files Browse the repository at this point in the history
Co-authored-by: VeronikaSolovei9 <kalypsonika@gmail.com>
  • Loading branch information
SyntaxNode and VeronikaSolovei9 authored Jun 19, 2023
1 parent 4c3b2c1 commit 833101c
Show file tree
Hide file tree
Showing 39 changed files with 1,638 additions and 1,318 deletions.
60 changes: 60 additions & 0 deletions firstpartydata/extmerger.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package firstpartydata

import (
"encoding/json"
"errors"
"fmt"

"github.com/prebid/prebid-server/util/sliceutil"
jsonpatch "gopkg.in/evanphx/json-patch.v4"
)

var (
ErrBadRequest = fmt.Errorf("invalid request ext")
ErrBadFPD = fmt.Errorf("invalid first party data ext")
)

// extMerger tracks a JSON `ext` field within an OpenRTB request. The value of the
// `ext` field is expected to be modified when calling unmarshal on the same object
// and will later be updated when invoking Merge.
type extMerger struct {
ext *json.RawMessage // Pointer to the JSON `ext` field.
snapshot json.RawMessage // Copy of the original state of the JSON `ext` field.
}

// Track saves a copy of the JSON `ext` field and stores a reference to the extension
// object for comparison later in the Merge call.
func (e *extMerger) Track(ext *json.RawMessage) {
e.ext = ext
e.snapshot = sliceutil.Clone(*ext)
}

// Merge applies a JSON merge of the stored extension snapshot on top of the current
// JSON of the tracked extension object.
func (e extMerger) Merge() error {
if e.ext == nil {
return nil
}

if len(e.snapshot) == 0 {
return nil
}

if len(*e.ext) == 0 {
*e.ext = e.snapshot
return nil
}

merged, err := jsonpatch.MergePatch(e.snapshot, *e.ext)
if err != nil {
if errors.Is(err, jsonpatch.ErrBadJSONDoc) {
return ErrBadRequest
} else if errors.Is(err, jsonpatch.ErrBadJSONPatch) {
return ErrBadFPD
}
return err
}

*e.ext = merged
return nil
}
109 changes: 109 additions & 0 deletions firstpartydata/extmerger_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package firstpartydata

import (
"encoding/json"
"testing"

"github.com/prebid/prebid-server/util/sliceutil"
"github.com/stretchr/testify/assert"
)

func TestExtMerger(t *testing.T) {
t.Run("nil", func(t *testing.T) {
merger := extMerger{ext: nil, snapshot: json.RawMessage(`{"a":1}`)}
assert.NoError(t, merger.Merge())
assert.Nil(t, merger.ext)
})

testCases := []struct {
name string
givenOriginal json.RawMessage
givenFPD json.RawMessage
expectedExt json.RawMessage
expectedErr string
}{
{
name: "both-populated",
givenOriginal: json.RawMessage(`{"a":1,"b":2}`),
givenFPD: json.RawMessage(`{"b":200,"c":3}`),
expectedExt: json.RawMessage(`{"a":1,"b":200,"c":3}`),
},
{
name: "both-nil",
givenFPD: nil,
givenOriginal: nil,
expectedExt: nil,
},
{
name: "both-empty",
givenOriginal: json.RawMessage(`{}`),
givenFPD: json.RawMessage(`{}`),
expectedExt: json.RawMessage(`{}`),
},
{
name: "ext-nil",
givenOriginal: json.RawMessage(`{"b":2}`),
givenFPD: nil,
expectedExt: json.RawMessage(`{"b":2}`),
},
{
name: "ext-empty",
givenOriginal: json.RawMessage(`{"b":2}`),
givenFPD: json.RawMessage(`{}`),
expectedExt: json.RawMessage(`{"b":2}`),
},
{
name: "ext-malformed",
givenOriginal: json.RawMessage(`{"b":2}`),
givenFPD: json.RawMessage(`malformed`),
expectedErr: "invalid first party data ext",
},
{
name: "snapshot-nil",
givenOriginal: nil,
givenFPD: json.RawMessage(`{"a":1}`),
expectedExt: json.RawMessage(`{"a":1}`),
},
{
name: "snapshot-empty",
givenOriginal: json.RawMessage(`{}`),
givenFPD: json.RawMessage(`{"a":1}`),
expectedExt: json.RawMessage(`{"a":1}`),
},
{
name: "snapshot-malformed",
givenOriginal: json.RawMessage(`malformed`),
givenFPD: json.RawMessage(`{"a":1}`),
expectedErr: "invalid request ext",
},
}

for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
// Initialize A Ext Raw Message For Testing
simulatedExt := json.RawMessage(sliceutil.Clone(test.givenOriginal))

// Begin Tracking
var merger extMerger
merger.Track(&simulatedExt)

// Unmarshal
simulatedExt.UnmarshalJSON(test.givenFPD)

// Merge
actualErr := merger.Merge()

if test.expectedErr == "" {
assert.NoError(t, actualErr, "error")

if test.expectedExt == nil {
assert.Nil(t, simulatedExt, "json")
} else {
assert.JSONEq(t, string(test.expectedExt), string(simulatedExt), "json")
}
} else {
assert.EqualError(t, actualErr, test.expectedErr, "error")
}
})
}
}
Loading

0 comments on commit 833101c

Please sign in to comment.