Skip to content

Commit 09b4dec

Browse files
committed
Added some lazy vendor list parsing code.
1 parent 7d8f0ad commit 09b4dec

File tree

6 files changed

+224
-0
lines changed

6 files changed

+224
-0
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
vendor/

Gopkg.lock

+15
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Gopkg.toml

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Gopkg.toml example
2+
#
3+
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
4+
# for detailed Gopkg.toml documentation.
5+
#
6+
# required = ["github.com/user/thing/cmd/thing"]
7+
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
8+
#
9+
# [[constraint]]
10+
# name = "github.com/user/project"
11+
# version = "1.0.0"
12+
#
13+
# [[constraint]]
14+
# name = "github.com/user/project2"
15+
# branch = "dev"
16+
# source = "github.com/myfork/project2"
17+
#
18+
# [[override]]
19+
# name = "github.com/x/y"
20+
# version = "2.4.0"
21+
#
22+
# [prune]
23+
# non-go = false
24+
# go-tests = true
25+
# unused-packages = true
26+
27+
28+
[[constraint]]
29+
name = "github.com/buger/jsonparser"
30+
branch = "master"

vendorlist/lazy-parsing.go

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package vendorlist
2+
3+
import (
4+
"strconv"
5+
6+
"github.com/buger/jsonparser"
7+
)
8+
9+
// ParseLazily returns a view of the data which re-calculates things on each function call.
10+
// This is cheap, but operations on it will be relatively expensive.
11+
//
12+
// This is ideal if you just need to look up information about a single vendor.
13+
// If you need to make many calls to query lots of information about a vendor,
14+
// this is not an efficient way to do it.
15+
//
16+
// PRs for an eager parser are welcome.
17+
func ParseLazily(data []byte) VendorList {
18+
return lazyVendorList(data)
19+
}
20+
21+
type lazyVendorList []byte
22+
23+
func (l lazyVendorList) Version() uint16 {
24+
if val, ok := lazyParseInt(l, "vendorListVersion"); ok {
25+
return uint16(val)
26+
}
27+
return 0
28+
}
29+
30+
func (l lazyVendorList) Vendor(vendorID uint16) Vendor {
31+
var vendorBytes []byte
32+
jsonparser.ArrayEach(l, func(value []byte, dataType jsonparser.ValueType, offset int, err error) {
33+
if val, ok := lazyParseInt(value, "id"); ok {
34+
if uint16(val) == vendorID {
35+
vendorBytes = value
36+
}
37+
}
38+
}, "vendors")
39+
40+
if len(vendorBytes) > 0 {
41+
return lazyVendor(vendorBytes)
42+
}
43+
return nil
44+
}
45+
46+
type lazyVendor []byte
47+
48+
func (l lazyVendor) Purpose(purposeID uint8) bool {
49+
return idExists(l, int(purposeID), "purposeIds")
50+
}
51+
52+
func (l lazyVendor) LegitimateInterest(purposeID uint8) bool {
53+
return idExists(l, int(purposeID), "legIntPurposeIds")
54+
}
55+
56+
// Returns false unless "id" exists in an array located at "data.key".
57+
func idExists(data []byte, id int, key string) bool {
58+
hasID := false
59+
60+
jsonparser.ArrayEach(data, func(value []byte, dataType jsonparser.ValueType, offset int, err error) {
61+
if err == nil && dataType == jsonparser.Number {
62+
if intVal, err := strconv.ParseInt(string(value), 10, 0); err == nil {
63+
if int(intVal) == id {
64+
hasID = true
65+
}
66+
}
67+
}
68+
}, key)
69+
70+
return hasID
71+
}
72+
73+
func lazyParseInt(data []byte, key string) (int, bool) {
74+
if value, dataType, _, err := jsonparser.Get(data, key); err == nil && dataType == jsonparser.Number {
75+
intVal, err := strconv.Atoi(string(value))
76+
if err != nil {
77+
return 0, false
78+
}
79+
return intVal, true
80+
}
81+
return 0, false
82+
}

vendorlist/lazy-parsing_test.go

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package vendorlist
2+
3+
import (
4+
"testing"
5+
)
6+
7+
const testData = `
8+
{
9+
"vendorListVersion": 5,
10+
"vendors": [
11+
{
12+
"id": 32,
13+
"purposeIds": [
14+
1,
15+
2
16+
],
17+
"legIntPurposeIds": [
18+
3
19+
],
20+
"featureIds": [
21+
2,
22+
3
23+
]
24+
}
25+
]
26+
}
27+
`
28+
29+
func TestLazyParsedVendorlist(t *testing.T) {
30+
list := ParseLazily([]byte(testData))
31+
assertIntsEqual(t, 5, int(list.Version()))
32+
assertNil(t, list.Vendor(2), true)
33+
assertNil(t, list.Vendor(32), false)
34+
}
35+
36+
func TestLazyParsedVendor(t *testing.T) {
37+
list := ParseLazily([]byte(testData))
38+
v := list.Vendor(32)
39+
assertBoolsEqual(t, true, v.Purpose(1))
40+
assertBoolsEqual(t, true, v.Purpose(2))
41+
assertBoolsEqual(t, false, v.Purpose(3))
42+
43+
assertBoolsEqual(t, false, v.LegitimateInterest(1))
44+
assertBoolsEqual(t, false, v.LegitimateInterest(2))
45+
assertBoolsEqual(t, true, v.LegitimateInterest(3))
46+
}
47+
48+
func assertIntsEqual(t *testing.T, expected int, actual int) {
49+
t.Helper()
50+
if actual != expected {
51+
t.Errorf("Ints were not equal. Expected %d, actual %d", expected, actual)
52+
}
53+
}
54+
55+
func assertBoolsEqual(t *testing.T, expected bool, actual bool) {
56+
t.Helper()
57+
if actual != expected {
58+
t.Errorf("Bools were not equal. Expected %t, actual %t", expected, actual)
59+
}
60+
}
61+
62+
func assertNil(t *testing.T, value Vendor, expectNil bool) {
63+
t.Helper()
64+
if expectNil && value != nil {
65+
t.Error("The vendor should be nil, but wasn't.")
66+
}
67+
if !expectNil && value == nil {
68+
t.Errorf("The vendor should not be nil, but was.")
69+
}
70+
}

vendorlist/vendorlist.go

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package vendorlist
2+
3+
// VendorList is an interface used to fetch information about an IAB Global Vendor list.
4+
// For the latest version, see: https://vendorlist.consensu.org/vendorlist.json
5+
type VendorList interface {
6+
// Version returns the version of the vendor list which this is.
7+
Version() uint16
8+
9+
// Vendor returns info about the vendor with the given ID, or nil if that vendor isn't in this list.
10+
//
11+
// If callers need to query multiple Purpose or LegitimateInterest statuses from the same vendor,
12+
// they should call this function once and then reuse the object it returns for future queries.
13+
Vendor(vendorID uint16) Vendor
14+
}
15+
16+
// Vendor describes which purposes a given vendor claims to use data for, in this vendor list.
17+
type Vendor interface {
18+
// Purpose returns true if this vendor claims to use data for the given purpose, or false otherwise
19+
Purpose(purposeID uint8) bool
20+
21+
// LegitimateInterest retursn true if this vendor claims a "Legitimate Interest" to
22+
// use data for the given purpose.
23+
//
24+
// For an explanation of legitimate interest, see https://www.gdpreu.org/the-regulation/key-concepts/legitimate-interest/
25+
LegitimateInterest(purposeID uint8) bool
26+
}

0 commit comments

Comments
 (0)