Skip to content

Commit

Permalink
feat: add parameters and restructure code
Browse files Browse the repository at this point in the history
  • Loading branch information
franklinkim committed Mar 8, 2024
1 parent 7100ab8 commit b45f2c8
Show file tree
Hide file tree
Showing 27 changed files with 567 additions and 363 deletions.
17 changes: 2 additions & 15 deletions integration/watermill/gtm/publisher.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,25 +79,12 @@ func (p *Publisher) Publish(topic string, messages ...*message.Message) error {
return err
}

values, body, err := mpv2.Marshal(event)
values, body, err := mpv2.Encode(event)
if err != nil {
return err
}

// NOTE: `richsstsse` seems to be last parameter in the query to let's ensure it stays that way
var richsstsse bool
if values.Has("richsstsse") {
richsstsse = true
values.Del("richsstsse")
}

url := fmt.Sprintf("%s?%s", p.url, values.Encode())

if richsstsse {
url += "&richsstsse"
}

req, err := http.NewRequestWithContext(msg.Context(), http.MethodPost, url, body)
req, err := http.NewRequestWithContext(msg.Context(), http.MethodPost, fmt.Sprintf("%s?%s", p.url, mpv2.EncodeValues(values)), body)
if err != nil {
return errors.Wrap(err, "failed to create request")
}
Expand Down
2 changes: 1 addition & 1 deletion integration/watermill/keel/subscriber.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ func (s *Subscriber) ServeHTTP(w http.ResponseWriter, r *http.Request) {

// unmarshal event
var event *mpv2.Event
if err := mpv2.UnmarshalURLValues(values, &event); err != nil {
if err := mpv2.Decode(values, &event); err != nil {
keelhttputils.InternalServerError(l, w, r, errors.Wrap(err, "failed to marshal url values"))
return
}
Expand Down
5 changes: 3 additions & 2 deletions measurementprotocol/v2/client.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package v2

import (
"fmt"
"io"
"net/http"
"net/url"
Expand Down Expand Up @@ -129,15 +130,15 @@ func (c *Client) Send(r *http.Request, event *Event) error {
}

func (c *Client) SendRaw(r *http.Request, event *Event) error {
values, body, err := Marshal(event)
values, body, err := Encode(event)
if err != nil {
return errors.Wrap(err, "failed to marshall event")
}

req, err := http.NewRequestWithContext(
r.Context(),
http.MethodPost,
c.url+"?"+values.Encode(),
fmt.Sprintf("%s?%s", c.url, EncodeValues(values)),
body,
)
if err != nil {
Expand Down
13 changes: 13 additions & 0 deletions measurementprotocol/v2/contants.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package v2

import (
"regexp"
)

var (
RegexProduct = regexp.MustCompile(`pr([1-9]|[1-9][0-9]|1[0-9]{2}|200)`)
)

const (
ParameterItem = "pr"
)
3 changes: 3 additions & 0 deletions measurementprotocol/v2/data.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package v2

type Data map[string]any
98 changes: 98 additions & 0 deletions measurementprotocol/v2/decode.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package v2

import (
"net/url"
"regexp"
"strings"

"github.com/mitchellh/mapstructure"
"github.com/pkg/errors"
)

func Decode(input url.Values, output interface{}) error {
data := Data{}

// decode values
for k, v := range input {
// handle maps
if ok, err := DecodeMapValue(k, v, data); err != nil {
return err
} else if ok {
continue
}

// handle slices
if ok, err := DecodeRegexValue(k, v, RegexProduct, data, ParameterItem); err != nil {
return err
} else if ok {
continue
}

// default
v, err := url.QueryUnescape(v[0])
if err != nil {
return err
}
data[k] = v
}

if err := mapstructure.WeakDecode(data, output); err != nil {
return errors.Wrap(err, "failed to weakly decode query")
}

return nil
}

func DecodeMapValue(k string, v []string, data Data) (bool, error) {
if strings.Contains(k, ".") {
parts := strings.Split(k, ".")
if _, ok := data[parts[0]]; !ok {
data[parts[0]] = map[string]any{}
}
if value, ok := data[parts[0]].(map[string]any); ok {
v, err := url.QueryUnescape(v[0])
if err != nil {
return false, err
}
value[strings.Join(parts[1:], ".")] = v
}
return true, nil
}
return false, nil
}

// DecodeRegexValue e.g. `pr1=idSKU_123456` = map["pr"][]map["id"]="SKU_123456"
func DecodeRegexValue(k string, v []string, r *regexp.Regexp, data Data, key string) (bool, error) {
if r.MatchString(k) {
value, err := DecodeObjectValue(v[0])
if err != nil {
return false, err
}
if value != nil {
v, ok := data[key].([]map[string]any)
if !ok {
v = []map[string]any{}
}
v = append(v, value)
data[key] = v
return true, nil
}
}
return false, nil
}

// DecodeObjectValue e.g. `idSKU_123456` = map["id"]="SKU_123456"
func DecodeObjectValue(s string) (map[string]any, error) {
if len(s) == 0 {
return nil, nil //nolint:nilnil
}
ret := map[string]any{}
for _, part := range strings.Split(s, "~") {
v, err := url.QueryUnescape(part[2:])
if err != nil {
return nil, err
}
ret[part[0:2]] = v
}
return ret, nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
"github.com/stretchr/testify/require"
)

func TestUnmarshalURLValues(t *testing.T) {
func TestDecode(t *testing.T) {
tests := []struct {
name string
args string
Expand All @@ -32,48 +32,15 @@ func TestUnmarshalURLValues(t *testing.T) {
u, err := url.ParseQuery(tt.args)
require.NoError(t, err)
e := &mpv2.Event{}
if err := mpv2.UnmarshalURLValues(u, e); !assert.ErrorIs(t, err, tt.want) {
t.Errorf("UnmarshalURLValues() = %v, want %v", err, tt.want)
if err := mpv2.Decode(u, e); !assert.ErrorIs(t, err, tt.want) {
t.Errorf("Decode() = %v, want %v", err, tt.want)
t.Log(e)
}
})
}
}

func TestMarshalURLValues(t *testing.T) {
tests := []struct {
name string
args string
want error
}{
{
name: "page_view",
args: "v=2&tid=G-123456&gtm=45je42s0v893689383z8894072534za200&_p=1709286636310&gcd=13l3l3l3l1&npa=0&dma_cps=sypham&dma=1&cid=292234677.1707898933&ul=en-us&sr=3840x1600&uaa=arm&uab=64&uafvl=Chromium&uamb=0&uam=&uap=macOS&uapv=14.3.1&uaw=0&are=1&pae=1&pscdl=noapi&_s=1&sid=1709286452&sct=7&seg=1&dl=https%3A%2F%2Fwww.homemade.ch%2Fprodukt%2Fkuhn-rikon-wiegemesser-900047100%3Fid%3D900047100&dr=https%3A%2F%2Fwww.homemade.ch%2Fmesser-besteck&dt=Wiegemesser&en=page_view&tfd=5682",
want: nil,
},
//{
// name: "add_to_cart",
// args: "v=2&tid=G-123456&gtm=45je42s0v9175354889z89175348963za200&_p=1709297934217&_dbg=1&gcd=13l3l3l3l1&npa=0&dma_cps=sypham&dma=1&cid=1220643501.1708014725&ul=en-us&sr=3840x1600&_fplc=0&ur=DE-BY&uaa=arm&uab=64&uafvl=Chromium&uamb=0&uam=&uap=macOS&uapv=14.3.1&uaw=0&are=1&pscdl=noapi&_eu=IA&sst.uc=DE&sst.etld=google.de&sst.gcsub=region1&sst.gcd=13l3l3l3l1&sst.tft=1709297934217&_s=8&cu=USD&sid=1709296380&sct=7&seg=1&dl=https%3A%2F%2Fsniffer.cloud.bestbytes.net%2F%3Fgtm_debug%3D1709297933868&dr=https%3A%2F%2Ftagassistant.google.com%2F&dt=Server%20Side%20Tracking%20Prototype%20(codename%3A%20sniffer)&en=add_to_cart&pr1=idSKU_12345~nmStan%20and%20Friends%20Tee~afGoogle%20Store~cpSUMMER_FUN~ds2.22~lp5~brGoogle~caApparel~c2Adult~c3Shirts~c4Crew~c5Short%20sleeve~lirelated_products~lnRelated%20products~vagreen~loChIJIQBpAG2ahYAR_6128GcTUEo~pr10.01~qt3&epn.value=30.03&tfd=15129&richsstsse",
// want: nil,
// },
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
u, err := url.ParseQuery(tt.args)
require.NoError(t, err)
e := &mpv2.Event{}
require.NoError(t, mpv2.UnmarshalURLValues(u, e))
if !assert.Empty(t, e.Unknown) {
t.Errorf("MarshalURLValues() = %v, want %v", e.Unknown, nil)
}
if out, _, err := mpv2.Marshal(e); assert.NoError(t, err) {
assert.EqualValues(t, u, out)
}
})
}
}

func TestCollect_decodeMapValue(t *testing.T) {
func TestDecodeMapValue(t *testing.T) {
tests := []struct {
name string
args string
Expand All @@ -99,7 +66,7 @@ func TestCollect_decodeMapValue(t *testing.T) {
require.NoError(t, err)
for k, v := range values {
if got, err := mpv2.DecodeMapValue(k, v, d); assert.NoError(t, err) && got != tt.want {
t.Errorf("decodeMapValue() = %v, want %v", got, tt.want)
t.Errorf("DecodeMapValue() = %v, want %v", got, tt.want)
t.Log(d)
}
}
Expand Down
102 changes: 102 additions & 0 deletions measurementprotocol/v2/encode.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package v2

import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/url"
"sort"
"strings"

"github.com/pkg/errors"
)

func Encode(input *Event) (url.Values, io.Reader, error) {
var richsstsse bool
// NOTE: `richsstsse` seems to be last parameter in the query to let's ensure it stays that way
if input.Richsstsse != nil {
richsstsse = true
input.Richsstsse = nil
}

a, err := json.Marshal(input)
if err != nil {
return nil, nil, err
}

data := Data{}
if err := json.Unmarshal(a, &data); err != nil {
return nil, nil, errors.Wrap(err, "failed to decode into map")
}

ret := url.Values{}
for k, v := range data {
switch t := v.(type) {
case []interface{}:
for key, value := range t {
switch tt := value.(type) {
case map[string]interface{}:
ret[fmt.Sprintf("%s%d", k, key+1)] = []string{EncodeObjectValue(tt)}
default:
panic("unhandled")
}
}
case map[string]string:
for key, value := range t {
ret[fmt.Sprintf("%s.%s", k, key)] = []string{value}
}
case map[string]interface{}:
for key, value := range t {
ret[fmt.Sprintf("%s.%s", k, key)] = []string{fmt.Sprintf("%v", value)}
}
case *string:
ret[k] = []string{*t}
case string:
ret[k] = []string{t}
default:
panic("unhandled")
}
}

var body []string
var reader io.Reader
maxQueryLength := 2048 //
if richsstsse {
maxQueryLength -= len("&richsstsse")
}
for len(ret.Encode()) > maxQueryLength {
for s, i := range ret {
ret.Del(s)
body = append(body, s+"="+i[0])
break
}
}

if richsstsse {
ret.Add("richsstsse", "")
}

if len(body) > 0 {
reader = bytes.NewReader([]byte(strings.Join(body, "&")))
}

return ret, reader, nil
}

// EncodeObjectValue e.g. `idSKU_123456` = map["id"]="SKU_123456"
func EncodeObjectValue(s map[string]any) string {
if len(s) == 0 {
return ""
}
keys := make([]string, 0, len(s))
for k := range s {
keys = append(keys, k)
}
sort.Strings(keys)
ret := make([]string, 0, len(keys))
for _, k := range keys {
ret = append(ret, k+fmt.Sprintf("%s", s[k]))
}
return strings.Join(ret, "~")
}
43 changes: 43 additions & 0 deletions measurementprotocol/v2/encode_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package v2_test

import (
"net/url"
"testing"

mpv2 "github.com/foomo/sesamy-go/measurementprotocol/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestEncode(t *testing.T) {
tests := []struct {
name string
args string
want error
}{
{
name: "page_view",
args: "v=2&tid=G-123456&gtm=45je42s0v893689383z8894072534za200&_p=1709286636310&gcd=13l3l3l3l1&npa=0&dma_cps=sypham&dma=1&cid=292234677.1707898933&ul=en-us&sr=3840x1600&uaa=arm&uab=64&uafvl=Chromium&uamb=0&uam=&uap=macOS&uapv=14.3.1&uaw=0&are=1&pae=1&pscdl=noapi&_s=1&sid=1709286452&sct=7&seg=1&dl=https%3A%2F%2Fwww.homemade.ch%2Fprodukt%2Fkuhn-rikon-wiegemesser-900047100%3Fid%3D900047100&dr=https%3A%2F%2Fwww.homemade.ch%2Fmesser-besteck&dt=Wiegemesser&en=page_view&tfd=5682",
want: nil,
},
// {
// name: "add_to_cart",
// args: "v=2&tid=G-123456&gtm=45je42s0v9175354889z89175348963za200&_p=1709297934217&_dbg=1&gcd=13l3l3l3l1&npa=0&dma_cps=sypham&dma=1&cid=1220643501.1708014725&ul=en-us&sr=3840x1600&_fplc=0&ur=DE-BY&uaa=arm&uab=64&uafvl=Chromium&uamb=0&uam=&uap=macOS&uapv=14.3.1&uaw=0&are=1&pscdl=noapi&_eu=IA&sst.uc=DE&sst.etld=google.de&sst.gcsub=region1&sst.gcd=13l3l3l3l1&sst.tft=1709297934217&_s=8&cu=USD&sid=1709296380&sct=7&seg=1&dl=https%3A%2F%2Fsniffer.cloud.bestbytes.net%2F%3Fgtm_debug%3D1709297933868&dr=https%3A%2F%2Ftagassistant.google.com%2F&dt=Server%20Side%20Tracking%20Prototype%20(codename%3A%20sniffer)&en=add_to_cart&pr1=idSKU_12345~nmStan%20and%20Friends%20Tee~afGoogle%20Store~cpSUMMER_FUN~ds2.22~lp5~brGoogle~caApparel~c2Adult~c3Shirts~c4Crew~c5Short%20sleeve~lirelated_products~lnRelated%20products~vagreen~loChIJIQBpAG2ahYAR_6128GcTUEo~pr10.01~qt3&epn.value=30.03&tfd=15129&richsstsse",
// want: nil,
// },
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
u, err := url.ParseQuery(tt.args)
require.NoError(t, err)
e := &mpv2.Event{}
require.NoError(t, mpv2.Decode(u, e))
if !assert.Empty(t, e.Unknown) {
t.Errorf("Encode() = %v, want %v", e.Unknown, nil)
}
if out, _, err := mpv2.Encode(e); assert.NoError(t, err) {
assert.EqualValues(t, u, out)
}
})
}
}
Loading

0 comments on commit b45f2c8

Please sign in to comment.