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

[azcore] Add ETag type to Core #15207

Merged
merged 13 commits into from
Aug 9, 2021
47 changes: 47 additions & 0 deletions sdk/azcore/etag.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// +build go1.13

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package azcore

import (
"strings"
)

// ETag is a property used for optimistic concurrency during updates
jhendrixMSFT marked this conversation as resolved.
Show resolved Hide resolved
// ETag is a validator based on https://tools.ietf.org/html/rfc7232#section-2.3.2
// An ETag can be empty ("").
type ETag string

// ETagAny is an ETag that represents everything, the value is "*"
const ETagAny ETag = "*"

// Equals does a strong comparison of two ETags. Equals returns true when both
// ETags are not weak and the values of the underlying strings are equal. If both ETags are "nil" they are considered equal
func (e ETag) Equals(other ETag) bool {
return !e.IsWeak() && !other.IsWeak() && e == other
}

// WeakEquals does a weak compariosn of two ETags. Two ETags are equivalent if their opaque-tags match
// character-by-character, regardless of either or both being tagged as "weak". If both ETags are "nil" they are considered equal
func (e ETag) WeakEquals(other ETag) bool {
Copy link
Member

Choose a reason for hiding this comment

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

This doesn't handle the case of nil ETags per the comments.

Also, I'm leaning towards having the receivers passed by reference. The reason being, if we are always defining ETags as ETag *ETag then calling this method will panic on a nil value. If we make the receiver and parameter byref then we can perform the nil checks on the caller's behalf. Thoughts?

Copy link
Member

Choose a reason for hiding this comment

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

Fix spelling of "comparison". Remove doc sentence about "nil".
We don't want a pointer receiver because we want this to work like other fields in the struct where calling a method on nil panics - this is desired behavior.

getStart := func(e1 ETag) int {
if e1.IsWeak() {
return 2
}
return 0
}
aStart := getStart(e)
bStart := getStart(other)

aVal := e[aStart:]
bVal := other[bStart:]

return aVal == bVal
}

// IsWeak specifies whether the ETag is strong or weak.
func (e ETag) IsWeak() bool {
return len(e) >= 4 && strings.HasPrefix(string(e), "W/\"") && strings.HasSuffix(string(e), "\"")
}
134 changes: 134 additions & 0 deletions sdk/azcore/etag_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
// +build go1.13

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package azcore

import (
"testing"

"github.com/stretchr/testify/require"
)

func createETag(s string) ETag {
return ETag(s)
}

func TestETagEquals(t *testing.T) {
e1 := createETag("tag")
require.Equal(t, string(e1), "tag")

e2 := createETag("\"tag\"")
require.Equal(t, string(e2), "\"tag\"")

e3 := createETag("W/\"weakETag\"")
require.Equal(t, string(e3), "W/\"weakETag\"")
require.Truef(t, e3.IsWeak(), "ETag is expected to be weak")

strongETag := createETag("\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\"")
require.Equal(t, string(strongETag), "\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\"")

require.Falsef(t, ETagAny.IsWeak(), "ETagAny should not be weak")
}

func TestETagWeak(t *testing.T) {
et1 := createETag("tag")
require.Falsef(t, et1.IsWeak(), "expected etag to be strong")

et2 := createETag("\"tag\"")
require.Falsef(t, et2.IsWeak(), "expected etag to be strong")

et3 := createETag("W/\"weakETag\"")
require.Truef(t, et3.IsWeak(), "expected etag to be weak")

et4 := createETag("W/\"\"")
require.Truef(t, et4.IsWeak(), "expected etag to be weak")

et5 := ETagAny
require.Falsef(t, et5.IsWeak(), "expected etag to be strong")
}

func TestETagEquality(t *testing.T) {
weakTag := createETag("W/\"\"")
weakTag1 := createETag("W/\"1\"")
weakTag2 := createETag("W/\"Two\"")
strongTag1 := createETag("\"1\"")
strongTag2 := createETag("\"Two\"")
strongTagValidChars := createETag("\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\"")
weakTagValidChars := createETag("W/\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\"")

require.Falsef(t, weakTag.Equals(weakTag), "Expected etags to not be equal")
require.Falsef(t, weakTag1.Equals( weakTag1), "Expected etags to not be equal")
require.Falsef(t, weakTag2.Equals(weakTag2), "Expected etags to not be equal")
require.Falsef(t, weakTagValidChars.Equals(weakTagValidChars), "Expected etags to not be equal")

require.Truef(t, strongTag1.Equals(strongTag1), "Expected etags to be equal")
require.Truef(t, strongTag2.Equals(strongTag2), "Expected etags to be equal")
require.Truef(t, strongTagValidChars.Equals(strongTagValidChars), "Expected etags to be equal")

require.Falsef(t, weakTag1.Equals(weakTag), "Expected etags to not be equal")
require.Falsef(t, strongTagValidChars.Equals(weakTagValidChars), "Expected etags to not be equal")
require.Falsef(t, weakTag2.Equals(weakTag1), "Expected etags to not be equal")
require.Falsef(t, strongTag1.Equals(weakTag1), "Expected etags to not be equal")
require.Falsef(t, strongTag2.Equals(weakTag2), "Expected etags to not be equal")
}

func TestEtagAny(t *testing.T) {
anyETag := ETagAny
star := createETag("*")
weakStar := createETag("W\"*\"")
quotedStar := createETag("\"*\"")

require.Truef(t, anyETag.Equals(anyETag), "Expected etags to be equal")
require.Truef(t, ETagAny.Equals(anyETag), "Expected etags to be equal")

require.Truef(t, star.Equals(star), "Expected etags to be equal")
require.Truef(t, ETagAny.Equals(star), "Expected etags to be equal")
require.Truef(t, anyETag.Equals(star), "Expected etags to be equal")

require.Falsef(t, star.Equals(weakStar), "Expected etags to be equal")
require.Falsef(t, ETagAny.Equals(weakStar), "Expected etags to be equal")
require.Falsef(t, weakStar.Equals(quotedStar), "Expected etags to be equal")

require.Falsef(t, quotedStar.Equals(star), "Expected etags to be equal")

require.Truef(t, star.Equals(ETagAny), "Expected etags to be equal")
}

func TestETagWeakComparison(t *testing.T) {
// W/""
weakTag := createETag("W/\"\"")
// W/"1"
weakTag1 := createETag("W/\"1\"")
// W/"Two"
weakTagTwo := createETag("W/\"Two\"")
// W/"two"
weakTagtwo := createETag("W/\"two\"")
// "1"
strongTag1 := createETag("\"1\"")
// "Two"
strongTagTwo := createETag("\"Two\"")
// "two"
strongTagtwo := createETag("\"two\"")

require.Truef(t, weakTag.WeakEquals(weakTag), "expected etags to be equal")
require.Truef(t, weakTag1.WeakEquals(weakTag1), "expected etags to be equal")
require.Truef(t, weakTagTwo.WeakEquals(weakTagTwo), "expected etags to be equal")
require.Truef(t, weakTagtwo.WeakEquals(weakTagtwo), "expected etags to be equal")
require.Truef(t, strongTag1.WeakEquals(strongTag1), "expected etags to be equal")
require.Truef(t, strongTagTwo.WeakEquals(strongTagTwo), "expected etags to be equal")
require.Truef(t, strongTagtwo.WeakEquals(strongTagtwo), "expected etags to be equal")

require.Falsef(t, weakTag1.WeakEquals(weakTag), "Expected etags to not be equal")
require.Falsef(t, weakTagTwo.WeakEquals(weakTag1), "Expected etags to not be equal")

require.Truef(t, strongTag1.WeakEquals(weakTag1), "expected etags to be equal")
require.Truef(t, strongTagTwo.WeakEquals(weakTagTwo), "expected etags to be equal")

require.Falsef(t, weakTag1.WeakEquals(strongTagTwo), "Expected etags to not be equal")
require.Falsef(t, weakTagtwo.WeakEquals(strongTagTwo), "Expected etags to not be equal")

require.Falsef(t, strongTagtwo.WeakEquals(strongTagTwo), "Expected etags to not be equal")
require.Falsef(t, weakTagtwo.WeakEquals(weakTagTwo), "Expected etags to not be equal")
}
1 change: 1 addition & 0 deletions sdk/azcore/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ module github.com/Azure/azure-sdk-for-go/sdk/azcore

require (
github.com/Azure/azure-sdk-for-go/sdk/internal v0.5.2
github.com/stretchr/testify v1.7.0 // indirect
golang.org/x/net v0.0.0-20210610132358-84b48f89b13b
)

Expand Down
4 changes: 4 additions & 0 deletions sdk/azcore/go.sum
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
github.com/Azure/azure-sdk-for-go/sdk/internal v0.5.2 h1:E2xwjsWU81O/XuSaxAGa8Jmqz4Vm4NmrpMSO9/XevDg=
github.com/Azure/azure-sdk-for-go/sdk/internal v0.5.2/go.mod h1:Hl9Vte0DDolj9zqzmfnmY9/zfZbiT5KnvXqVwAvnR8Q=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/net v0.0.0-20210610132358-84b48f89b13b h1:k+E048sYJHyVnsr1GDrRZWQ32D2C7lWs9JRc0bel53A=
golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
Expand All @@ -19,4 +22,5 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=