-
Notifications
You must be signed in to change notification settings - Fork 34
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add bearertoken, datetime, rid, safelong, and uuid packages (#132)
- Loading branch information
Showing
17 changed files
with
1,031 additions
and
0 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
// Copyright (c) 2018 Palantir Technologies. All rights reserved. | ||
// Use of this source code is governed by a BSD-style | ||
// license that can be found in the LICENSE file. | ||
|
||
package bearertoken | ||
|
||
// Token represents a bearer token, generally sent by a REST client in a | ||
// Authorization or Cookie header for authentication purposes. | ||
type Token string |
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,49 @@ | ||
// Copyright (c) 2018 Palantir Technologies. All rights reserved. | ||
// Use of this source code is governed by a BSD-style | ||
// license that can be found in the LICENSE file. | ||
|
||
package datetime | ||
|
||
import ( | ||
"strings" | ||
"time" | ||
) | ||
|
||
// DateTime is an alias for time.Time which implements serialization matching the | ||
// conjure wire specification at https://github.com/palantir/conjure/blob/master/docs/spec/wire.md | ||
type DateTime time.Time | ||
|
||
func (d DateTime) String() string { | ||
return time.Time(d).Format(time.RFC3339Nano) | ||
} | ||
|
||
// MarshalText implements encoding.TextMarshaler (used by encoding/json and others). | ||
func (d DateTime) MarshalText() ([]byte, error) { | ||
return []byte(d.String()), nil | ||
} | ||
|
||
// UnmarshalText implements encoding.TextUnmarshaler (used by encoding/json and others). | ||
func (d *DateTime) UnmarshalText(b []byte) error { | ||
t, err := ParseDateTime(string(b)) | ||
if err != nil { | ||
return err | ||
} | ||
*d = t | ||
return nil | ||
} | ||
|
||
// ParseDateTime parses a DateTime from a string. Conjure supports DateTime inputs that end with an optional | ||
// zone identifier enclosed in square brackets (for example, "2017-01-02T04:04:05.000000000+01:00[Europe/Berlin]"). | ||
func ParseDateTime(s string) (DateTime, error) { | ||
// If the input string ends in a ']' and contains a '[', parse the string up to '['. | ||
if strings.HasSuffix(s, "]") { | ||
if openBracketIdx := strings.LastIndex(s, "["); openBracketIdx != -1 { | ||
s = s[:openBracketIdx] | ||
} | ||
} | ||
timeVal, err := time.Parse(time.RFC3339Nano, s) | ||
if err != nil { | ||
return DateTime(time.Time{}), err | ||
} | ||
return DateTime(timeVal), nil | ||
} |
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,121 @@ | ||
// Copyright (c) 2018 Palantir Technologies. All rights reserved. | ||
// Use of this source code is governed by a BSD-style | ||
// license that can be found in the LICENSE file. | ||
|
||
package datetime_test | ||
|
||
import ( | ||
"encoding/json" | ||
"testing" | ||
"time" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
|
||
"github.com/palantir/pkg/datetime" | ||
) | ||
|
||
var dateTimeJSONs = []struct { | ||
sec int64 | ||
zoneOffset int | ||
str string | ||
json string | ||
}{ | ||
{ | ||
sec: 1483326245, | ||
str: `2017-01-02T03:04:05Z`, | ||
json: `"2017-01-02T03:04:05Z"`, | ||
}, | ||
{ | ||
sec: 1483326245, | ||
str: `2017-01-02T03:04:05Z`, | ||
json: `"2017-01-02T03:04:05.000Z"`, | ||
}, | ||
{ | ||
sec: 1483326245, | ||
str: `2017-01-02T03:04:05Z`, | ||
json: `"2017-01-02T03:04:05.000000000Z"`, | ||
}, | ||
{ | ||
sec: 1483326245, | ||
zoneOffset: 3600, | ||
str: `2017-01-02T04:04:05+01:00`, | ||
json: `"2017-01-02T04:04:05.000000000+01:00"`, | ||
}, | ||
{ | ||
sec: 1483326245, | ||
zoneOffset: 7200, | ||
str: `2017-01-02T05:04:05+02:00`, | ||
json: `"2017-01-02T05:04:05.000000000+02:00"`, | ||
}, | ||
{ | ||
sec: 1483326245, | ||
zoneOffset: 3600, | ||
str: `2017-01-02T04:04:05+01:00`, | ||
json: `"2017-01-02T04:04:05.000000000+01:00[Europe/Berlin]"`, | ||
}, | ||
} | ||
|
||
func TestDateTimeString(t *testing.T) { | ||
for i, currCase := range dateTimeJSONs { | ||
currDateTime := datetime.DateTime(time.Unix(currCase.sec, 0).In(time.FixedZone("", currCase.zoneOffset))) | ||
assert.Equal(t, currCase.str, currDateTime.String(), "Case %d", i) | ||
} | ||
} | ||
|
||
func TestDateTimeMarshal(t *testing.T) { | ||
for i, currCase := range dateTimeJSONs { | ||
currDateTime := datetime.DateTime(time.Unix(currCase.sec, 0).In(time.FixedZone("", currCase.zoneOffset))) | ||
bytes, err := json.Marshal(currDateTime) | ||
require.NoError(t, err, "Case %d: marshal %q", i, currDateTime.String()) | ||
|
||
var unmarshaledFromMarshal datetime.DateTime | ||
err = json.Unmarshal(bytes, &unmarshaledFromMarshal) | ||
require.NoError(t, err, "Case %d: unmarshal %q", i, string(bytes)) | ||
|
||
var unmarshaledFromCase datetime.DateTime | ||
err = json.Unmarshal([]byte(currCase.json), &unmarshaledFromCase) | ||
require.NoError(t, err, "Case %d: unmarshal %q", i, currCase.json) | ||
|
||
assert.Equal(t, unmarshaledFromCase, unmarshaledFromMarshal, "Case %d", i) | ||
} | ||
} | ||
|
||
func TestDateTimeUnmarshal(t *testing.T) { | ||
for i, currCase := range dateTimeJSONs { | ||
wantDateTime := time.Unix(currCase.sec, 0).UTC() | ||
if currCase.zoneOffset != 0 { | ||
wantDateTime = wantDateTime.In(time.FixedZone("", currCase.zoneOffset)) | ||
} | ||
|
||
var gotDateTime datetime.DateTime | ||
err := json.Unmarshal([]byte(currCase.json), &gotDateTime) | ||
require.NoError(t, err, "Case %d", i) | ||
|
||
assert.Equal(t, wantDateTime, time.Time(gotDateTime), "Case %d", i) | ||
} | ||
} | ||
|
||
func TestDateTimeUnmarshalInvalid(t *testing.T) { | ||
for i, currCase := range []struct { | ||
input string | ||
wantErr string | ||
}{ | ||
{ | ||
input: `"foo"`, | ||
wantErr: "parsing time \"foo\" as \"2006-01-02T15:04:05.999999999Z07:00\": cannot parse \"foo\" as \"2006\"", | ||
}, | ||
{ | ||
input: `"2017-01-02T04:04:05.000000000+01:00[Europe/Berlin"`, | ||
wantErr: "parsing time \"2017-01-02T04:04:05.000000000+01:00[Europe/Berlin\": extra text: [Europe/Berlin", | ||
}, | ||
{ | ||
input: `"2017-01-02T04:04:05.000000000+01:00[[Europe/Berlin]]"`, | ||
wantErr: "parsing time \"2017-01-02T04:04:05.000000000+01:00[\": extra text: [", | ||
}, | ||
} { | ||
var gotDateTime *datetime.DateTime | ||
err := json.Unmarshal([]byte(currCase.input), &gotDateTime) | ||
assert.EqualError(t, err, currCase.wantErr, "Case %d", i) | ||
} | ||
} |
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,97 @@ | ||
// Copyright (c) 2018 Palantir Technologies. All rights reserved. | ||
// Use of this source code is governed by a BSD-style | ||
// license that can be found in the LICENSE file. | ||
|
||
package rid | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"regexp" | ||
"strings" | ||
) | ||
|
||
// A ResourceIdentifier is a four-part identifier string for a resource | ||
// whose format is specified at https://github.com/palantir/resource-identifier. | ||
// | ||
// Resource Identifiers offer a common encoding for wrapping existing unique | ||
// identifiers with some additional context that can be useful when storing | ||
// those identifiers in other applications. Additionally, the context can be | ||
// used to disambiguate application-unique, but not globally-unique, | ||
// identifiers when used in a common space. | ||
type ResourceIdentifier struct { | ||
// Service is a string that represents the service (or application) that namespaces the rest of the identifier. | ||
// Must conform with regex pattern [a-z][a-z0-9\-]*. | ||
Service string | ||
// Instance is an optionally empty string that represents a specific service cluster, to allow disambiguation of artifacts from different service clusters. | ||
// Must conform to regex pattern ([a-z0-9][a-z0-9\-]*)?. | ||
Instance string | ||
// Type is a service-specific resource type to namespace a group of locators. | ||
// Must conform to regex pattern [a-z][a-z0-9\-]*. | ||
Type string | ||
// Locator is a string used to uniquely locate the specific resource. | ||
// Must conform to regex pattern [a-zA-Z0-9\-\._]+. | ||
Locator string | ||
} | ||
|
||
func (rid ResourceIdentifier) String() string { | ||
return rid.Service + "." + rid.Instance + "." + rid.Type + "." + rid.Locator | ||
} | ||
|
||
// MarshalText implements encoding.TextMarshaler (used by encoding/json and others). | ||
func (rid ResourceIdentifier) MarshalText() (text []byte, err error) { | ||
return []byte(rid.String()), rid.validate() | ||
} | ||
|
||
// UnmarshalText implements encoding.TextUnmarshaler (used by encoding/json and others). | ||
func (rid *ResourceIdentifier) UnmarshalText(text []byte) error { | ||
var err error | ||
parsed, err := ParseRID(string(text)) | ||
if err != nil { | ||
return err | ||
} | ||
*rid = parsed | ||
return nil | ||
} | ||
|
||
// ParseRID parses a string into a 4-part resource identifier. | ||
func ParseRID(s string) (ResourceIdentifier, error) { | ||
segments := strings.SplitN(s, ".", 4) | ||
if len(segments) != 4 { | ||
return ResourceIdentifier{}, errors.New("invalid resource identifier") | ||
} | ||
rid := ResourceIdentifier{ | ||
Service: segments[0], | ||
Instance: segments[1], | ||
Type: segments[2], | ||
Locator: segments[3], | ||
} | ||
return rid, rid.validate() | ||
} | ||
|
||
var ( | ||
servicePattern = regexp.MustCompile(`^[a-z][a-z0-9\-]*$`) | ||
instancePattern = regexp.MustCompile(`^[a-z0-9][a-z0-9\-]*$`) | ||
typePattern = regexp.MustCompile(`^[a-z][a-z0-9\-]*$`) | ||
locatorPattern = regexp.MustCompile(`^[a-zA-Z0-9\-\._]+$`) | ||
) | ||
|
||
func (rid ResourceIdentifier) validate() error { | ||
var msgs []string | ||
if !servicePattern.MatchString(rid.Service) { | ||
msgs = append(msgs, fmt.Sprintf("rid first segment (service) does not match %s pattern", servicePattern)) | ||
} | ||
if !instancePattern.MatchString(rid.Instance) { | ||
msgs = append(msgs, fmt.Sprintf("rid second segment (instance) does not match %s pattern", instancePattern)) | ||
} | ||
if !typePattern.MatchString(rid.Type) { | ||
msgs = append(msgs, fmt.Sprintf("rid third segment (type) does not match %s pattern", typePattern)) | ||
} | ||
if !locatorPattern.MatchString(rid.Locator) { | ||
msgs = append(msgs, fmt.Sprintf("rid fourth segment (locator) does not match %s pattern", locatorPattern)) | ||
} | ||
if len(msgs) != 0 { | ||
return errors.New(strings.Join(msgs, ": ")) | ||
} | ||
return nil | ||
} |
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,69 @@ | ||
// Copyright (c) 2018 Palantir Technologies. All rights reserved. | ||
// Use of this source code is governed by a BSD-style | ||
// license that can be found in the LICENSE file. | ||
|
||
package rid_test | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
|
||
"github.com/palantir/pkg/rid" | ||
) | ||
|
||
func TestResourceIdentifier(t *testing.T) { | ||
for _, test := range []struct { | ||
Name string | ||
Input rid.ResourceIdentifier | ||
Expected string | ||
ExpectedErr string | ||
}{ | ||
{ | ||
Name: "basic RID", | ||
Input: rid.ResourceIdentifier{ | ||
Service: "my-service", | ||
Instance: "my-instance", | ||
Type: "my-type", | ||
Locator: "my.locator.with.dots", | ||
}, | ||
Expected: "my-service.my-instance.my-type.my.locator.with.dots", | ||
}, | ||
{ | ||
Name: "invalid casing", | ||
Input: rid.ResourceIdentifier{ | ||
Service: "myService", | ||
Instance: "myInstance", | ||
Type: "myType", | ||
Locator: "my.locator.with.dots", | ||
}, | ||
ExpectedErr: `rid first segment (service) does not match ^[a-z][a-z0-9\-]*$ pattern: rid second segment (instance) does not match ^[a-z0-9][a-z0-9\-]*$ pattern: rid third segment (type) does not match ^[a-z][a-z0-9\-]*$ pattern`, | ||
}, | ||
} { | ||
t.Run(test.Name, func(t *testing.T) { | ||
type ridContainer struct { | ||
RID rid.ResourceIdentifier `json:"rid"` | ||
} | ||
|
||
// Test Marshal | ||
jsonBytes, err := json.Marshal(ridContainer{RID: test.Input}) | ||
if test.ExpectedErr != "" { | ||
require.Error(t, err) | ||
require.Contains(t, err.Error(), test.ExpectedErr) | ||
return | ||
} | ||
require.NoError(t, err) | ||
require.Equal(t, fmt.Sprintf(`{"rid":%q}`, test.Expected), string(jsonBytes)) | ||
|
||
// Test Unmarshal | ||
var unmarshaled ridContainer | ||
err = json.Unmarshal(jsonBytes, &unmarshaled) | ||
require.NoError(t, err, "failed to unmarshal json: %s", string(jsonBytes)) | ||
assert.Equal(t, test.Expected, unmarshaled.RID.String()) | ||
assert.Equal(t, test.Input, unmarshaled.RID) | ||
}) | ||
} | ||
} |
Oops, something went wrong.