This repository has been archived by the owner on Dec 8, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
New: Add JSON support for encoding/transfer
- Loading branch information
Showing
7 changed files
with
258 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package transfer | ||
|
||
import "errors" | ||
|
||
var ( | ||
ErrUnknownEncodingType = errors.New("transfer: unknown encoding type") | ||
ErrNotEncodable = errors.New("transfer: the given value cannot be encoded for this transfer mechanism") | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
package transfer | ||
|
||
import "encoding/json" | ||
|
||
// JSON is a convenient way to represent an encoding and data tuple in JSON. | ||
type JSON struct { | ||
EncodingType EncodingType `json:"$encoding"` | ||
Data string `json:"data"` | ||
|
||
// Factories allows this struct to be configured to use a different set of | ||
// encoder/decoders than the default. | ||
Factories EncodeDecoderFactories `json:"-"` | ||
} | ||
|
||
// Decode finds the given encoder for this JSON data and decodes the data using | ||
// it. | ||
func (t JSON) Decode() ([]byte, error) { | ||
factories := t.Factories | ||
if factories == nil { | ||
factories = Encoders | ||
} | ||
|
||
encoder, found := factories[t.EncodingType] | ||
if !found { | ||
return nil, ErrUnknownEncodingType | ||
} | ||
|
||
return encoder().DecodeFromTransfer(t.Data) | ||
} | ||
|
||
// JSONOrStr is like the JSON type, but also allows NoEncodingType to be | ||
// represented as a raw JSON string. | ||
type JSONOrStr struct{ JSON } | ||
|
||
func (tos JSONOrStr) MarshalJSON() ([]byte, error) { | ||
if tos.EncodingType == NoEncodingType { | ||
return json.Marshal(tos.Data) | ||
} | ||
|
||
return json.Marshal(tos.JSON) | ||
} | ||
|
||
func (tos *JSONOrStr) UnmarshalJSON(data []byte) error { | ||
var s string | ||
if err := json.Unmarshal(data, &s); err == nil { | ||
tos.JSON.Data = s | ||
return nil | ||
} | ||
|
||
return json.Unmarshal(data, &tos.JSON) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
package transfer_test | ||
|
||
import ( | ||
"encoding/hex" | ||
"encoding/json" | ||
"fmt" | ||
"testing" | ||
|
||
"github.com/puppetlabs/horsehead/v2/encoding/transfer" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
type testJSONHexEncoding struct{} | ||
|
||
func (testJSONHexEncoding) EncodeForTransfer(value []byte) (string, error) { | ||
return fmt.Sprintf("hex:%s", hex.EncodeToString(value)), nil | ||
} | ||
|
||
func (testJSONHexEncoding) EncodeJSON(value []byte) (transfer.JSONOrStr, error) { | ||
return transfer.JSONOrStr{JSON: transfer.JSON{ | ||
EncodingType: transfer.EncodingType("hex"), | ||
Data: hex.EncodeToString(value), | ||
}}, nil | ||
} | ||
|
||
func (testJSONHexEncoding) DecodeFromTransfer(value string) ([]byte, error) { | ||
return hex.DecodeString(value) | ||
} | ||
|
||
var testJSONFactories = transfer.EncodeDecoderFactories{ | ||
transfer.EncodingType("hex"): func() transfer.EncodeDecoder { return &testJSONHexEncoding{} }, | ||
} | ||
|
||
func TestJSONUnmarshal(t *testing.T) { | ||
var cases = []struct { | ||
description string | ||
json string | ||
expected string | ||
factories transfer.EncodeDecoderFactories | ||
err error | ||
}{ | ||
{ | ||
description: "Base64 encoding succeeds", | ||
json: `{"$encoding": "base64", "data": "c3VwZXIgc2VjcmV0IHRva2Vu"}`, | ||
expected: "super secret token", | ||
}, | ||
{ | ||
description: "Explicit empty encoding succeeds", | ||
json: `{"$encoding": "", "data": "blah blah blee bloo"}`, | ||
expected: "blah blah blee bloo", | ||
}, | ||
{ | ||
description: "Invalid encoding errors", | ||
json: `{"$encoding": "invalid", "data": "blah blah blee bloo"}`, | ||
err: transfer.ErrUnknownEncodingType, | ||
}, | ||
{ | ||
description: "Custom encoder factory", | ||
json: `{"$encoding": "hex", "data": "48656c6c6f20476f7068657221"}`, | ||
expected: "Hello Gopher!", | ||
factories: testJSONFactories, | ||
}, | ||
} | ||
|
||
for _, c := range cases { | ||
t.Run(c.description, func(t *testing.T) { | ||
j := transfer.JSON{ | ||
Factories: c.factories, | ||
} | ||
|
||
require.NoError(t, json.Unmarshal([]byte(c.json), &j)) | ||
b, err := j.Decode() | ||
if c.err != nil { | ||
require.Equal(t, c.err, err) | ||
return | ||
} | ||
|
||
require.NoError(t, err) | ||
require.Equal(t, c.expected, string(b)) | ||
}) | ||
} | ||
} | ||
|
||
func TestJSONOrStrMarshalUnmarshal(t *testing.T) { | ||
var cases = []struct { | ||
description string | ||
input string | ||
expected string | ||
}{ | ||
{ | ||
description: "Properly encodes utf8 strings", | ||
input: "This is a normal string", | ||
expected: `"This is a normal string"`, | ||
}, | ||
{ | ||
description: "Properly encodes non-utf8 strings", | ||
input: "Hello, \x90\xA2\x8A\x45", | ||
expected: `{"$encoding":"base64","data":"SGVsbG8sIJCiikU="}`, | ||
}, | ||
} | ||
|
||
for _, c := range cases { | ||
t.Run(c.description, func(t *testing.T) { | ||
j, err := transfer.EncodeJSON([]byte(c.input)) | ||
require.NoError(t, err) | ||
|
||
js, err := json.Marshal(j) | ||
require.NoError(t, err) | ||
require.JSONEq(t, c.expected, string(js)) | ||
|
||
var ju transfer.JSONOrStr | ||
require.NoError(t, json.Unmarshal(js, &ju)) | ||
|
||
d, err := ju.Decode() | ||
require.Equal(t, c.input, string(d)) | ||
}) | ||
} | ||
} |