diff --git a/sdk/azcore/etag.go b/sdk/azcore/etag.go new file mode 100644 index 000000000000..983af5b6524b --- /dev/null +++ b/sdk/azcore/etag.go @@ -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 +// 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. +func (e ETag) Equals(other ETag) bool { + return !e.IsWeak() && !other.IsWeak() && e == other +} + +// WeakEquals does a weak comparison of two ETags. Two ETags are equivalent if their opaque-tags match +// character-by-character, regardless of either or both being tagged as "weak". +func (e ETag) WeakEquals(other ETag) bool { + 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), "\"") +} diff --git a/sdk/azcore/etag_test.go b/sdk/azcore/etag_test.go new file mode 100644 index 000000000000..5541fe05a20a --- /dev/null +++ b/sdk/azcore/etag_test.go @@ -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") +} diff --git a/sdk/azcore/go.mod b/sdk/azcore/go.mod index db8caa57b4fe..d1352741ce9c 100644 --- a/sdk/azcore/go.mod +++ b/sdk/azcore/go.mod @@ -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 ) diff --git a/sdk/azcore/go.sum b/sdk/azcore/go.sum index 62d184faa625..3d9a653b9fb7 100644 --- a/sdk/azcore/go.sum +++ b/sdk/azcore/go.sum @@ -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= @@ -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=