Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better parse error handling #7

Merged
merged 2 commits into from
Mar 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,15 @@ const (
SectionUSPUT SectionID = 11
SectionUSPCT SectionID = 12
)

var SectionNamesByID = map[int]string{
2: "tcfeu2",
3: "gpp header",
6: "uspv1",
7: "uspnat",
8: "uspca",
9: "uspva",
10: "uspco",
11: "usput",
12: "uspct",
}
42 changes: 30 additions & 12 deletions parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,37 +30,37 @@ type Section interface {
GetValue() string // base64 encoding usually, but plaintext for ccpa
}

func Parse(v string) (GppContainer, error) {
func Parse(v string) (GppContainer, []error) {
var gpp GppContainer

sectionStrings := strings.Split(v, "~")

bs, err := util.NewBitStreamFromBase64(sectionStrings[0])
if err != nil {
return gpp, fmt.Errorf("error parsing GPP header, base64 decoding: %s", err)
return gpp, []error{fmt.Errorf("error parsing GPP header, base64 decoding: %s", err)}
}
if bs.Len() < MaxHeaderLength {
return gpp, fmt.Errorf("error parsing GPP header, should be at least %d bytes long", MaxHeaderLength)
return gpp, []error{fmt.Errorf("error parsing GPP header, should be at least %d bytes long", MaxHeaderLength)}
}

// base64 encoding codes just 6 bits into each byte. The first 6 bits of the header must always evaluate
// to the integer '3' as the GPP header type. Short cut the processing of a 6 bit integer with a simple
// byte comparison to shave off a few CPU cycles.
if sectionStrings[0][0] != SectionGPPByte {
return gpp, fmt.Errorf("error parsing GPP header, header must have type=%d", constants.SectionGPP)
return gpp, []error{fmt.Errorf("error parsing GPP header, header must have type=%d", constants.SectionGPP)}
}
// We checked the GPP header type above outside of the bitstream framework, so we advance the bit stream past the first 6 bits.
bs.SetPosition(6)

ver, err := bs.ReadByte6()
if err != nil {
return gpp, fmt.Errorf("error parsing GPP header, unable to parse GPP version: %s", err)
return gpp, []error{fmt.Errorf("error parsing GPP header, unable to parse GPP version: %s", err)}
}
gpp.Version = int(ver)

intRange, err := bs.ReadFibonacciRange()
if err != nil {
return gpp, fmt.Errorf("error parsing GPP header, section identifiers: %s", err)
return gpp, []error{fmt.Errorf("error parsing GPP header, section identifiers: %s", err)}
}

// We do not count the GPP header as a section
Expand All @@ -73,37 +73,55 @@ func Parse(v string) (GppContainer, error) {
}
}
if len(secIDs) != secCount {
return gpp, fmt.Errorf("error parsing GPP header, section IDs do not match the number of sections: found %d IDs, have %d sections", len(secIDs), secCount)
return gpp, []error{fmt.Errorf("error parsing GPP header, section IDs do not match the number of sections: found %d IDs, have %d sections", len(secIDs), secCount)}
}
gpp.SectionTypes = secIDs

sections := make([]Section, secCount)
var errs []error
for i, id := range secIDs {
switch id {
case constants.SectionUSPNAT:
sections[i], err = uspnat.NewUSPNAT(sectionStrings[i+1])
if err != nil {
errs = append(errs, fmt.Errorf("error parsing %s consent string: %s", constants.SectionNamesByID[int(id)], err))
}
case constants.SectionUSPCA:
sections[i], err = uspca.NewUSPCA(sectionStrings[i+1])
if err != nil {
errs = append(errs, fmt.Errorf("error parsing %s consent string: %s", constants.SectionNamesByID[int(id)], err))
}
case constants.SectionUSPVA:
sections[i], err = uspva.NewUSPVA(sectionStrings[i+1])
if err != nil {
errs = append(errs, fmt.Errorf("error parsing %s consent string: %s", constants.SectionNamesByID[int(id)], err))
}
case constants.SectionUSPCO:
sections[i], err = uspco.NewUSPCO(sectionStrings[i+1])
if err != nil {
errs = append(errs, fmt.Errorf("error parsing %s consent string: %s", constants.SectionNamesByID[int(id)], err))
}
case constants.SectionUSPUT:
sections[i], err = usput.NewUSPUT(sectionStrings[i+1])
if err != nil {
errs = append(errs, fmt.Errorf("error parsing %s consent string: %s", constants.SectionNamesByID[int(id)], err))
}
case constants.SectionUSPCT:
sections[i], err = uspct.NewUSPCT(sectionStrings[i+1])
if err != nil {
errs = append(errs, fmt.Errorf("error parsing %s consent string: %s", constants.SectionNamesByID[int(id)], err))
}
default:
sections[i] = GenericSection{sectionID: id, value: sectionStrings[i+1]}
if err != nil {
errs = append(errs, fmt.Errorf("error parsing unsupported (section %d) consent string: %s", int(id), err))
}
}
}

if err != nil {
return gpp, err
}

gpp.Sections = sections

return gpp, nil
return gpp, errs
}

type GenericSection struct {
Expand Down
50 changes: 37 additions & 13 deletions parse_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package gpp

import (
"fmt"
"testing"

"github.com/prebid/go-gpp/constants"
Expand All @@ -11,14 +12,15 @@ import (
)

type gppTestData struct {
description string
gppString string
expected GppContainer
description string
gppString string
expected GppContainer
expectedError []error
}

func TestParse(t *testing.T) {
testData := []gppTestData{
{
testData := map[string]gppTestData{
"GPP-tcf": {
description: "GPP string with EU TCF V2",
gppString: "DBABMA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA",
expected: GppContainer{
Expand All @@ -28,7 +30,7 @@ func TestParse(t *testing.T) {
value: "CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA"}},
},
},
{
"GPP-tcv-usp": {
description: "GPP string with EU TCF v2 and US Privacy",
gppString: "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN",
expected: GppContainer{
Expand All @@ -40,7 +42,7 @@ func TestParse(t *testing.T) {
value: "1YNN"}},
},
},
{
"GPP-tcfca-usp": {
description: "GPP string with Canadian TCF and US Privacy",
gppString: "DBABjw~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN",
expected: GppContainer{
Expand All @@ -52,7 +54,7 @@ func TestParse(t *testing.T) {
value: "1YNN"}},
},
},
{
"GPP-uspca": {
description: "GPP string with USPCA",
gppString: "DBABBgA~xlgWEYCZAA",
expected: GppContainer{
Expand Down Expand Up @@ -86,7 +88,7 @@ func TestParse(t *testing.T) {
},
},
},
{
"GPP-uspva": {
description: "GPP string with USPVA",
gppString: "DBABRgA~bSFgmiU",
expected: GppContainer{
Expand All @@ -113,12 +115,34 @@ func TestParse(t *testing.T) {
},
},
},
"GPP-tcf-error": {
description: "GPP string with EU TCF V2",
gppString: "DBGBMA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA",
expected: GppContainer{
Version: 1,
SectionTypes: []constants.SectionID{2},
Sections: []Section{GenericSection{sectionID: 2,
value: "CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA"}},
},
expectedError: []error{fmt.Errorf("error parsing GPP header, section identifiers: error reading an int offset value in a Range(Fibonacci) entry(1): error reading bit 4 of Integer(Fibonacci): expected 1 bit at bit 32, but the byte array was only 4 bytes long")},
},
"GPP-uspca-error": {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice naming. 👍

description: "GPP string with USPCA",
gppString: "DBABBgA~xlgWE",
expectedError: []error{fmt.Errorf("error parsing uspca consent string: illegal base64 data at input byte 4")},
},
}

for _, test := range testData {
result, err := Parse(test.gppString)
for name, test := range testData {
t.Run(name, func(t *testing.T) {
result, err := Parse(test.gppString)

assert.Nil(t, err)
assert.Equal(t, test.expected, result)
if len(test.expectedError) == 0 {
assert.Nil(t, err)
assert.Equal(t, test.expected, result)
} else {
assert.Equal(t, test.expectedError, err)
}
})
}
}