diff --git a/sdk/storage/Azure.Storage.Blobs/src/Generated/BlobRestClient.cs b/sdk/storage/Azure.Storage.Blobs/src/Generated/BlobRestClient.cs index ea4d7b331c34d..cc5a93b5f0718 100644 --- a/sdk/storage/Azure.Storage.Blobs/src/Generated/BlobRestClient.cs +++ b/sdk/storage/Azure.Storage.Blobs/src/Generated/BlobRestClient.cs @@ -815,20 +815,22 @@ internal static Azure.Core.Pipeline.HttpPipelineMessage GetAccountInfoAsync_Crea { case 200: { - // Create the result - Azure.Storage.Blobs.Models.AccountInfo _value = new Azure.Storage.Blobs.Models.AccountInfo(); // Get response headers string _header; + Azure.Storage.Blobs.Models.SkuName skuName = default; + Azure.Storage.Blobs.Models.AccountKind accountKind = default; if (response.Headers.TryGetValue("x-ms-sku-name", out _header)) { - _value.SkuName = Azure.Storage.Blobs.BlobRestClient.Serialization.ParseSkuName(_header); + skuName = Azure.Storage.Blobs.BlobRestClient.Serialization.ParseSkuName(_header); } if (response.Headers.TryGetValue("x-ms-account-kind", out _header)) { - _value.AccountKind = (Azure.Storage.Blobs.Models.AccountKind)System.Enum.Parse(typeof(Azure.Storage.Blobs.Models.AccountKind), _header, false); + accountKind = (Azure.Storage.Blobs.Models.AccountKind)System.Enum.Parse(typeof(Azure.Storage.Blobs.Models.AccountKind), _header, false); } + Azure.Storage.Blobs.Models.AccountInfo _value = new Azure.Storage.Blobs.Models.AccountInfo(skuName, accountKind); + // Create the response return Response.FromValue(response, _value); } @@ -7639,36 +7641,42 @@ internal static Azure.Core.Pipeline.HttpPipelineMessage UploadPagesAsync_CreateM { case 201: { - // Create the result - Azure.Storage.Blobs.Models.PageInfo _value = new Azure.Storage.Blobs.Models.PageInfo(); // Get response headers string _header; + Azure.Core.Http.ETag eTag = default; + System.DateTimeOffset lastModified = default; + byte[] contentHash = default; + byte[] contentCrc64 = default; + long blobSequenceNumber = default; + string encryptionKeySha256 = default; if (response.Headers.TryGetValue("ETag", out _header)) { - _value.ETag = new Azure.Core.Http.ETag(_header); + eTag = new Azure.Core.Http.ETag(_header); } if (response.Headers.TryGetValue("Last-Modified", out _header)) { - _value.LastModified = System.DateTimeOffset.Parse(_header, System.Globalization.CultureInfo.InvariantCulture); + lastModified = System.DateTimeOffset.Parse(_header, System.Globalization.CultureInfo.InvariantCulture); } if (response.Headers.TryGetValue("Content-MD5", out _header)) { - _value.ContentHash = System.Convert.FromBase64String(_header); + contentHash = System.Convert.FromBase64String(_header); } if (response.Headers.TryGetValue("x-ms-content-crc64", out _header)) { - _value.ContentCrc64 = System.Convert.FromBase64String(_header); + contentCrc64 = System.Convert.FromBase64String(_header); } if (response.Headers.TryGetValue("x-ms-blob-sequence-number", out _header)) { - _value.BlobSequenceNumber = long.Parse(_header, System.Globalization.CultureInfo.InvariantCulture); + blobSequenceNumber = long.Parse(_header, System.Globalization.CultureInfo.InvariantCulture); } if (response.Headers.TryGetValue("x-ms-encryption-key-sha256", out _header)) { - _value.EncryptionKeySha256 = _header; + encryptionKeySha256 = _header; } + Azure.Storage.Blobs.Models.PageInfo _value = new Azure.Storage.Blobs.Models.PageInfo(eTag, lastModified, contentHash, contentCrc64, blobSequenceNumber, encryptionKeySha256); + // Create the response return Response.FromValue(response, _value); } @@ -7872,32 +7880,42 @@ internal static Azure.Core.Pipeline.HttpPipelineMessage ClearPagesAsync_CreateMe { case 201: { - // Create the result - Azure.Storage.Blobs.Models.PageInfo _value = new Azure.Storage.Blobs.Models.PageInfo(); // Get response headers string _header; + Azure.Core.Http.ETag eTag = default; + System.DateTimeOffset lastModified = default; + byte[] contentHash = default; + byte[] contentCrc64 = default; + long blobSequenceNumber = default; + string encryptionKeySha256 = default; if (response.Headers.TryGetValue("ETag", out _header)) { - _value.ETag = new Azure.Core.Http.ETag(_header); + eTag = new Azure.Core.Http.ETag(_header); } if (response.Headers.TryGetValue("Last-Modified", out _header)) { - _value.LastModified = System.DateTimeOffset.Parse(_header, System.Globalization.CultureInfo.InvariantCulture); + lastModified = System.DateTimeOffset.Parse(_header, System.Globalization.CultureInfo.InvariantCulture); } if (response.Headers.TryGetValue("Content-MD5", out _header)) { - _value.ContentHash = System.Convert.FromBase64String(_header); + contentHash = System.Convert.FromBase64String(_header); } if (response.Headers.TryGetValue("x-ms-content-crc64", out _header)) { - _value.ContentCrc64 = System.Convert.FromBase64String(_header); + contentCrc64 = System.Convert.FromBase64String(_header); } if (response.Headers.TryGetValue("x-ms-blob-sequence-number", out _header)) { - _value.BlobSequenceNumber = long.Parse(_header, System.Globalization.CultureInfo.InvariantCulture); + blobSequenceNumber = long.Parse(_header, System.Globalization.CultureInfo.InvariantCulture); + } + if (response.Headers.TryGetValue("x-ms-encryption-key-sha256", out _header)) + { + encryptionKeySha256 = _header; } + Azure.Storage.Blobs.Models.PageInfo _value = new Azure.Storage.Blobs.Models.PageInfo(eTag, lastModified, contentHash, contentCrc64, blobSequenceNumber, encryptionKeySha256); + // Create the response return Response.FromValue(response, _value); } @@ -8161,36 +8179,42 @@ internal static Azure.Core.Pipeline.HttpPipelineMessage UploadPagesFromUriAsync_ { case 201: { - // Create the result - Azure.Storage.Blobs.Models.PageInfo _value = new Azure.Storage.Blobs.Models.PageInfo(); // Get response headers string _header; + Azure.Core.Http.ETag eTag = default; + System.DateTimeOffset lastModified = default; + byte[] contentHash = default; + byte[] contentCrc64 = default; + long blobSequenceNumber = default; + string encryptionKeySha256 = default; if (response.Headers.TryGetValue("ETag", out _header)) { - _value.ETag = new Azure.Core.Http.ETag(_header); + eTag = new Azure.Core.Http.ETag(_header); } if (response.Headers.TryGetValue("Last-Modified", out _header)) { - _value.LastModified = System.DateTimeOffset.Parse(_header, System.Globalization.CultureInfo.InvariantCulture); + lastModified = System.DateTimeOffset.Parse(_header, System.Globalization.CultureInfo.InvariantCulture); } if (response.Headers.TryGetValue("Content-MD5", out _header)) { - _value.ContentHash = System.Convert.FromBase64String(_header); + contentHash = System.Convert.FromBase64String(_header); } if (response.Headers.TryGetValue("x-ms-content-crc64", out _header)) { - _value.ContentCrc64 = System.Convert.FromBase64String(_header); + contentCrc64 = System.Convert.FromBase64String(_header); } if (response.Headers.TryGetValue("x-ms-blob-sequence-number", out _header)) { - _value.BlobSequenceNumber = long.Parse(_header, System.Globalization.CultureInfo.InvariantCulture); + blobSequenceNumber = long.Parse(_header, System.Globalization.CultureInfo.InvariantCulture); } if (response.Headers.TryGetValue("x-ms-encryption-key-sha256", out _header)) { - _value.EncryptionKeySha256 = _header; + encryptionKeySha256 = _header; } + Azure.Storage.Blobs.Models.PageInfo _value = new Azure.Storage.Blobs.Models.PageInfo(eTag, lastModified, contentHash, contentCrc64, blobSequenceNumber, encryptionKeySha256); + // Create the response return Response.FromValue(response, _value); } @@ -12502,29 +12526,76 @@ public partial struct AccessTier : System.IEquatable } #endregion enum strings AccessTier -#region class AccountInfo +#region struct AccountInfo namespace Azure.Storage.Blobs.Models { /// /// AccountInfo /// - public partial class AccountInfo + public readonly partial struct AccountInfo: System.IEquatable { /// /// Identifies the sku name of the account /// - public Azure.Storage.Blobs.Models.SkuName SkuName { get; internal set; } + public Azure.Storage.Blobs.Models.SkuName SkuName { get; } /// /// Identifies the account kind /// - public Azure.Storage.Blobs.Models.AccountKind AccountKind { get; internal set; } + public Azure.Storage.Blobs.Models.AccountKind AccountKind { get; } /// /// Prevent direct instantiation of AccountInfo instances. /// You can use BlobsModelFactory.AccountInfo instead. /// - internal AccountInfo() { } + internal AccountInfo( + Azure.Storage.Blobs.Models.SkuName skuName, + Azure.Storage.Blobs.Models.AccountKind accountKind) + { + SkuName = skuName; + AccountKind = accountKind; + } + + /// + /// Check if two AccountInfo instances are equal. + /// + /// The instance to compare to. + /// True if they're equal, false otherwise. + [System.ComponentModel.EditorBrowsable((System.ComponentModel.EditorBrowsableState.Never))] + public bool Equals(AccountInfo other) + { + if (!SkuName.Equals(other.SkuName)) + { + return false; + } + if (!AccountKind.Equals(other.AccountKind)) + { + return false; + } + + return true; + } + + /// + /// Check if two AccountInfo instances are equal. + /// + /// The instance to compare to. + /// True if they're equal, false otherwise. + [System.ComponentModel.EditorBrowsable((System.ComponentModel.EditorBrowsableState.Never))] + public override bool Equals(object obj) => obj is AccountInfo && Equals((AccountInfo)obj); + + /// + /// Get a hash code for the AccountInfo. + /// + [System.ComponentModel.EditorBrowsable((System.ComponentModel.EditorBrowsableState.Never))] + public override int GetHashCode() + { + var hashCode = new Azure.Core.HashCodeBuilder(); + hashCode.Add(SkuName); + hashCode.Add(AccountKind); + + return hashCode.ToHashCode(); + } } /// @@ -12539,15 +12610,11 @@ public static AccountInfo AccountInfo( Azure.Storage.Blobs.Models.SkuName skuName, Azure.Storage.Blobs.Models.AccountKind accountKind) { - return new AccountInfo() - { - SkuName = skuName, - AccountKind = accountKind, - }; + return new AccountInfo(skuName, accountKind); } } } -#endregion class AccountInfo +#endregion struct AccountInfo #region enum AccountKind namespace Azure.Storage.Blobs.Models @@ -17558,53 +17625,131 @@ public static PageBlobInfo PageBlobInfo( } #endregion class PageBlobInfo -#region class PageInfo +#region struct PageInfo namespace Azure.Storage.Blobs.Models { /// /// PageInfo /// - public partial class PageInfo + public readonly partial struct PageInfo: System.IEquatable { /// /// The ETag contains a value that you can use to perform operations conditionally. If the request version is 2011-08-18 or newer, the ETag value will be in quotes. /// - public Azure.Core.Http.ETag ETag { get; internal set; } + public Azure.Core.Http.ETag ETag { get; } /// /// Returns the date and time the container was last modified. Any operation that modifies the blob, including an update of the blob's metadata or properties, changes the last-modified time of the blob. /// - public System.DateTimeOffset LastModified { get; internal set; } + public System.DateTimeOffset LastModified { get; } /// /// If the blob has an MD5 hash and this operation is to read the full blob, this response header is returned so that the client can check for message content integrity. /// #pragma warning disable CA1819 // Properties should not return arrays - public byte[] ContentHash { get; internal set; } + public byte[] ContentHash { get; } #pragma warning restore CA1819 // Properties should not return arrays /// /// This header is returned so that the client can check for message content integrity. The value of this header is computed by the Blob service; it is not necessarily the same value specified in the request headers. /// #pragma warning disable CA1819 // Properties should not return arrays - public byte[] ContentCrc64 { get; internal set; } + public byte[] ContentCrc64 { get; } #pragma warning restore CA1819 // Properties should not return arrays /// /// The current sequence number for the page blob. This is only returned for page blobs. /// - public long BlobSequenceNumber { get; internal set; } + public long BlobSequenceNumber { get; } /// /// The SHA-256 hash of the encryption key used to encrypt the pages. This header is only returned when the pages were encrypted with a customer-provided key. /// - public string EncryptionKeySha256 { get; internal set; } + public string EncryptionKeySha256 { get; } /// /// Prevent direct instantiation of PageInfo instances. /// You can use BlobsModelFactory.PageInfo instead. /// - internal PageInfo() { } + internal PageInfo( + Azure.Core.Http.ETag eTag, + System.DateTimeOffset lastModified, + byte[] contentHash, + byte[] contentCrc64, + long blobSequenceNumber, + string encryptionKeySha256) + { + ETag = eTag; + LastModified = lastModified; + ContentHash = contentHash; + ContentCrc64 = contentCrc64; + BlobSequenceNumber = blobSequenceNumber; + EncryptionKeySha256 = encryptionKeySha256; + } + + /// + /// Check if two PageInfo instances are equal. + /// + /// The instance to compare to. + /// True if they're equal, false otherwise. + [System.ComponentModel.EditorBrowsable((System.ComponentModel.EditorBrowsableState.Never))] + public bool Equals(PageInfo other) + { + if (!ETag.Equals(other.ETag)) + { + return false; + } + if (!LastModified.Equals(other.LastModified)) + { + return false; + } + if (!System.Collections.StructuralComparisons.StructuralEqualityComparer.Equals(ContentHash, other.ContentHash)) + { + return false; + } + if (!System.Collections.StructuralComparisons.StructuralEqualityComparer.Equals(ContentCrc64, other.ContentCrc64)) + { + return false; + } + if (!BlobSequenceNumber.Equals(other.BlobSequenceNumber)) + { + return false; + } + if (!System.StringComparer.Ordinal.Equals(EncryptionKeySha256, other.EncryptionKeySha256)) + { + return false; + } + + return true; + } + + /// + /// Check if two PageInfo instances are equal. + /// + /// The instance to compare to. + /// True if they're equal, false otherwise. + [System.ComponentModel.EditorBrowsable((System.ComponentModel.EditorBrowsableState.Never))] + public override bool Equals(object obj) => obj is PageInfo && Equals((PageInfo)obj); + + /// + /// Get a hash code for the PageInfo. + /// + [System.ComponentModel.EditorBrowsable((System.ComponentModel.EditorBrowsableState.Never))] + public override int GetHashCode() + { + var hashCode = new Azure.Core.HashCodeBuilder(); + hashCode.Add(ETag); + hashCode.Add(LastModified); + hashCode.Add(System.Collections.StructuralComparisons.StructuralEqualityComparer.GetHashCode(ContentHash)); + hashCode.Add(System.Collections.StructuralComparisons.StructuralEqualityComparer.GetHashCode(ContentCrc64)); + hashCode.Add(BlobSequenceNumber); + if (EncryptionKeySha256 != null) + { + hashCode.Add(EncryptionKeySha256, System.StringComparer.Ordinal); + } + + return hashCode.ToHashCode(); + } } /// @@ -17623,19 +17768,11 @@ public static PageInfo PageInfo( long blobSequenceNumber, string encryptionKeySha256) { - return new PageInfo() - { - ETag = eTag, - LastModified = lastModified, - ContentHash = contentHash, - ContentCrc64 = contentCrc64, - BlobSequenceNumber = blobSequenceNumber, - EncryptionKeySha256 = encryptionKeySha256, - }; + return new PageInfo(eTag, lastModified, contentHash, contentCrc64, blobSequenceNumber, encryptionKeySha256); } } } -#endregion class PageInfo +#endregion struct PageInfo #region class PageList namespace Azure.Storage.Blobs.Models @@ -17722,29 +17859,76 @@ public static PageList PageList( } #endregion class PageList -#region class PageRange +#region struct PageRange namespace Azure.Storage.Blobs.Models { /// /// PageRange /// - public partial class PageRange + public readonly partial struct PageRange: System.IEquatable { /// /// Start /// - public long Start { get; internal set; } + public long Start { get; } /// /// End /// - public long End { get; internal set; } + public long End { get; } /// /// Prevent direct instantiation of PageRange instances. /// You can use BlobsModelFactory.PageRange instead. /// - internal PageRange() { } + internal PageRange( + long start, + long end) + { + Start = start; + End = end; + } + + /// + /// Check if two PageRange instances are equal. + /// + /// The instance to compare to. + /// True if they're equal, false otherwise. + [System.ComponentModel.EditorBrowsable((System.ComponentModel.EditorBrowsableState.Never))] + public bool Equals(PageRange other) + { + if (!Start.Equals(other.Start)) + { + return false; + } + if (!End.Equals(other.End)) + { + return false; + } + + return true; + } + + /// + /// Check if two PageRange instances are equal. + /// + /// The instance to compare to. + /// True if they're equal, false otherwise. + [System.ComponentModel.EditorBrowsable((System.ComponentModel.EditorBrowsableState.Never))] + public override bool Equals(object obj) => obj is PageRange && Equals((PageRange)obj); + + /// + /// Get a hash code for the PageRange. + /// + [System.ComponentModel.EditorBrowsable((System.ComponentModel.EditorBrowsableState.Never))] + public override int GetHashCode() + { + var hashCode = new Azure.Core.HashCodeBuilder(); + hashCode.Add(Start); + hashCode.Add(End); + + return hashCode.ToHashCode(); + } /// /// Deserializes XML into a new PageRange instance. @@ -17754,9 +17938,9 @@ internal PageRange() { } internal static Azure.Storage.Blobs.Models.PageRange FromXml(System.Xml.Linq.XElement element) { System.Diagnostics.Debug.Assert(element != null); - Azure.Storage.Blobs.Models.PageRange _value = new Azure.Storage.Blobs.Models.PageRange(); - _value.Start = long.Parse(element.Element(System.Xml.Linq.XName.Get("Start", "")).Value, System.Globalization.CultureInfo.InvariantCulture); - _value.End = long.Parse(element.Element(System.Xml.Linq.XName.Get("End", "")).Value, System.Globalization.CultureInfo.InvariantCulture); + long start = long.Parse(element.Element(System.Xml.Linq.XName.Get("Start", "")).Value, System.Globalization.CultureInfo.InvariantCulture); + long end = long.Parse(element.Element(System.Xml.Linq.XName.Get("End", "")).Value, System.Globalization.CultureInfo.InvariantCulture); + Azure.Storage.Blobs.Models.PageRange _value = new Azure.Storage.Blobs.Models.PageRange(start, end); CustomizeFromXml(element, _value); return _value; } @@ -17776,15 +17960,11 @@ public static PageRange PageRange( long start, long end) { - return new PageRange() - { - Start = start, - End = end, - }; + return new PageRange(start, end); } } } -#endregion class PageRange +#endregion struct PageRange #region class PageRangesInfo namespace Azure.Storage.Blobs.Models diff --git a/sdk/storage/Azure.Storage.Blobs/swagger/readme.md b/sdk/storage/Azure.Storage.Blobs/swagger/readme.md index f34fe6a27098b..0951a8996ccca 100644 --- a/sdk/storage/Azure.Storage.Blobs/swagger/readme.md +++ b/sdk/storage/Azure.Storage.Blobs/swagger/readme.md @@ -232,6 +232,7 @@ directive: transform: > $.get.description = "Returns the sku name and account kind"; $.get.responses["200"]["x-az-response-name"] = "AccountInfo"; + $.get.responses["200"]["x-az-struct"] = true; - from: swagger-document where: $["x-ms-paths"] transform: > @@ -693,6 +694,7 @@ directive: transform: > $.put.responses["201"]["x-az-response-name"] = "PageInfo"; $.put.responses["201"].description = "The operation completed successfully."; + $.put.responses["201"]["x-az-struct"] = true; $.put.responses["201"].headers["x-ms-blob-sequence-number"].description = "The current sequence number for the page blob. This is only returned for page blobs."; $.put.responses["201"].headers["x-ms-request-server-encrypted"]["x-az-demote-header"] = true; ``` @@ -705,6 +707,7 @@ directive: transform: > $.put.responses["201"]["x-az-response-name"] = "PageInfo"; $.put.responses["201"].description = "The operation completed successfully."; + $.put.responses["201"]["x-az-struct"] = true; $.put.responses["201"].headers["x-ms-blob-sequence-number"].description = "The current sequence number for the page blob. This is only returned for page blobs."; $.put.responses["201"].headers["x-ms-request-server-encrypted"] = { "x-ms-client-name": "IsServerEncrypted", @@ -712,6 +715,11 @@ directive: "x-az-demote-header": true, "description": "The value of this header is set to true if the contents of the request are successfully encrypted using the specified algorithm, and false otherwise." }; + $.put.responses["201"].headers["x-ms-encryption-key-sha256"] = { + "x-ms-client-name": "EncryptionKeySha256", + "type": "string", + "description": "The SHA-256 hash of the encryption key used to encrypt the blob. This header is only returned when the blob was encrypted with a customer-provided key." + }; ``` ### /{containerName}/{blob}?comp=page&update&fromUrl @@ -723,6 +731,7 @@ directive: $.put.operationId = "PageBlob_UploadPagesFromUri"; $.put.responses["201"]["x-az-response-name"] = "PageInfo"; $.put.responses["201"].description = "The operation completed successfully."; + $.put.responses["201"]["x-az-struct"] = true; $.put.responses["201"].headers["x-ms-blob-sequence-number"].description = "The current sequence number for the page blob. This is only returned for page blobs."; $.put.responses["201"].headers["x-ms-request-server-encrypted"]["x-az-demote-header"] = true; $.put.responses["304"] = { @@ -766,6 +775,15 @@ directive: }; ``` +### Define PageRange as struct +``` yaml +directive: +- from: swagger-document + where: $.definitions.PageRange + transform: > + $["x-az-struct"] = true; +``` + ### /{containerName}/{blob}?comp=properties&Resize ``` yaml directive: diff --git a/sdk/storage/Azure.Storage.Blobs/tests/PageInfoTest.cs b/sdk/storage/Azure.Storage.Blobs/tests/PageInfoTest.cs new file mode 100644 index 0000000000000..c14bd21dca45b --- /dev/null +++ b/sdk/storage/Azure.Storage.Blobs/tests/PageInfoTest.cs @@ -0,0 +1,208 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using Azure.Storage.Blobs.Models; +using NUnit.Framework; + +namespace Azure.Storage.Blobs.Tests +{ + /// + /// These tests are related to our generated struct behavior. + /// + public class PageInfoTest + { + [Test] + public void EqualsReturnsTrueForEqualValues() + { + var hash = new byte[] { 1, 2, 3 }; + + var info1 = new PageInfo( + new Core.Http.ETag("A"), + new DateTimeOffset(2019, 9, 25, 1, 1, 1, TimeSpan.Zero), + hash, + hash, + 1, + "key1"); + + var info2 = new PageInfo( + new Core.Http.ETag("A"), + new DateTimeOffset(2019, 9, 25, 1, 1, 1, TimeSpan.Zero), + hash, + hash, + 1, + "key1"); + + Assert.True(info1.Equals(info2)); + Assert.True(info2.Equals(info1)); + + Assert.AreEqual(info1.GetHashCode(), info2.GetHashCode()); + } + + [Test] + public void EqualsReturnsTrueForNullValues() + { + var info1 = new PageInfo( + new Core.Http.ETag("A"), + new DateTimeOffset(2019, 9, 25, 1, 1, 1, TimeSpan.Zero), + null, + null, + 1, + null); + + var info2 = new PageInfo( + new Core.Http.ETag("A"), + new DateTimeOffset(2019, 9, 25, 1, 1, 1, TimeSpan.Zero), + null, + null, + 1, + null); + + Assert.True(info1.Equals(info2)); + Assert.True(info2.Equals(info1)); + + Assert.AreEqual(info1.GetHashCode(), info2.GetHashCode()); + + } + + [Test] + public void EqualsReturnsTrueIfCompareContentHashByValues() + { + var hash1 = new byte[] { 1, 2, 3 }; + var hash2 = new byte[] { 1, 2, 3 }; + + var info1 = new PageInfo( + new Core.Http.ETag("A"), + new DateTimeOffset(2019, 9, 25, 1, 1, 1, TimeSpan.Zero), + hash1, + hash1, + 1, + "key1"); + + var info2 = new PageInfo( + new Core.Http.ETag("A"), + new DateTimeOffset(2019, 9, 25, 1, 1, 1, TimeSpan.Zero), + hash2, + hash1, + 1, + "key1"); + + Assert.True(info1.Equals(info2)); + Assert.True(info2.Equals(info1)); + + Assert.AreEqual(info1.GetHashCode(), info2.GetHashCode()); + } + + [Test] + public void EqualsReturnsTrueIfCompareContentCrc64ByValues() + { + var hash1 = new byte[] { 1, 2, 3 }; + var hash2 = new byte[] { 1, 2, 3 }; + + var info1 = new PageInfo( + new Core.Http.ETag("A"), + new DateTimeOffset(2019, 9, 25, 1, 1, 1, TimeSpan.Zero), + hash1, + hash1, + 1, + "key1"); + + var info2 = new PageInfo( + new Core.Http.ETag("A"), + new DateTimeOffset(2019, 9, 25, 1, 1, 1, TimeSpan.Zero), + hash1, + hash2, + 1, + "key1"); + + Assert.True(info1.Equals(info2)); + Assert.True(info2.Equals(info1)); + + Assert.AreEqual(info1.GetHashCode(), info2.GetHashCode()); + } + + [Test] + public void EqualsReturnsFalseIfCompareContentHashWithNull() + { + var hash = new byte[] { 1, 2, 3 }; + + var info1 = new PageInfo( + new Core.Http.ETag("A"), + new DateTimeOffset(2019, 9, 25, 1, 1, 1, TimeSpan.Zero), + null, + hash, + 1, + "key1"); + + var info2 = new PageInfo( + new Core.Http.ETag("A"), + new DateTimeOffset(2019, 9, 25, 1, 1, 1, TimeSpan.Zero), + hash, + hash, + 1, + "key1"); + + Assert.True(!info1.Equals(info2)); + Assert.True(!info2.Equals(info1)); + + Assert.AreNotEqual(info1.GetHashCode(), info2.GetHashCode()); + + } + + [Test] + public void EqualsReturnsFalseIfCompareContentCrc64WithNull() + { + var hash = new byte[] { 1, 2, 3 }; + + var info1 = new PageInfo( + new Core.Http.ETag("A"), + new DateTimeOffset(2019, 9, 25, 1, 1, 1, TimeSpan.Zero), + hash, + null, + 1, + "key1"); + + var info2 = new PageInfo( + new Core.Http.ETag("A"), + new DateTimeOffset(2019, 9, 25, 1, 1, 1, TimeSpan.Zero), + hash, + hash, + 1, + "key1"); + + Assert.True(!info1.Equals(info2)); + Assert.True(!info2.Equals(info1)); + + Assert.AreNotEqual(info1.GetHashCode(), info2.GetHashCode()); + + } + + + [Test] + public void EqualsReturnFalseIfCompareDifferentValues() + { + var hash = new byte[] { 1, 2, 3 }; + + var info1 = new PageInfo( + new Core.Http.ETag("B"), + new DateTimeOffset(2019, 9, 25, 1, 1, 1, TimeSpan.Zero), + hash, + hash, + 1, + "key1"); + + var info2 = new PageInfo( + new Core.Http.ETag("A"), + new DateTimeOffset(2019, 11, 25, 1, 1, 1, TimeSpan.Zero), + hash, + hash, + 2, + "key2"); + + Assert.True(!info1.Equals(info2)); + Assert.True(!info2.Equals(info1)); + + Assert.AreNotEqual(info1.GetHashCode(), info2.GetHashCode()); + } + } +} diff --git a/sdk/storage/Azure.Storage.Common/src/Azure.Storage.Common.csproj b/sdk/storage/Azure.Storage.Common/src/Azure.Storage.Common.csproj index 8c9e0d4e430f8..6f029aa958135 100644 --- a/sdk/storage/Azure.Storage.Common/src/Azure.Storage.Common.csproj +++ b/sdk/storage/Azure.Storage.Common/src/Azure.Storage.Common.csproj @@ -24,5 +24,6 @@ --> + diff --git a/sdk/storage/Azure.Storage.Common/swagger/Generator/readme.md b/sdk/storage/Azure.Storage.Common/swagger/Generator/readme.md index 40f78e7ac7f4d..6ce5a997be068 100644 --- a/sdk/storage/Azure.Storage.Common/swagger/Generator/readme.md +++ b/sdk/storage/Azure.Storage.Common/swagger/Generator/readme.md @@ -13,6 +13,7 @@ We support a number of extensions including using the vendor prefix `x-az-`: - `x-az-skip-path-components`: Whether to skip any path components and always assume a fully formed URL to the resource (this currently must be set to `true`). - `x-az-include-sync-methods`: Whether to generate support for sync methods. The default value is `false` (this flag should go away soon and always be `true`). - `x-az-stream`: Whether to generate a non buffered request that takes owhership of the response stream. The default value is `false`. +- `x-az-struct`: Indicates whether a model is struct or not. The default value is `false`. - `x-az-nullable-array`: Allows list to be null. The default value is `false`. ### Autorest plugin configuration diff --git a/sdk/storage/Azure.Storage.Common/swagger/Generator/src/generator.ts b/sdk/storage/Azure.Storage.Common/swagger/Generator/src/generator.ts index fd991cb2c83a5..8f5edc845782f 100644 --- a/sdk/storage/Azure.Storage.Common/swagger/Generator/src/generator.ts +++ b/sdk/storage/Azure.Storage.Common/swagger/Generator/src/generator.ts @@ -496,7 +496,7 @@ function generateOperation(w: IndentWriter, serviceModel: IServiceModel, group: const model = response.model; // Deserialize - w.line(`// Create the result`); + if (!response.struct) w.line(`// Create the result`); if (response.body) { const responseType = response.body; if (responseType.type === `string`) { @@ -570,7 +570,7 @@ function generateOperation(w: IndentWriter, serviceModel: IServiceModel, group: } else { throw `Serialization format ${operation.produces} not supported (in ${name})`; } - } else { + } else if (!response.struct) { w.line(`${types.getName(model)} ${valueName} = new ${types.getName(model)}();`); } w.line(); @@ -579,6 +579,11 @@ function generateOperation(w: IndentWriter, serviceModel: IServiceModel, group: if (headers.length > 0) { w.line(`// Get response headers`); w.line(`string ${headerName};`); + if (response.struct) { + for (const header of headers) { + w.line(`${types.getDeclarationType(header.model, true, false, true)} ${naming.parameter(header.clientName)} = default;`); + } + } for (const header of headers) { if (isPrimitiveType(header.model) && header.model.type === 'dictionary') { const prefix = header.model.dictionaryPrefix || `x-ms-meta-`; @@ -593,7 +598,11 @@ function generateOperation(w: IndentWriter, serviceModel: IServiceModel, group: } else { w.line(`if (${responseName}.Headers.TryGetValue("${header.name}", out ${headerName}))`); w.scope('{', '}', () => { - w.write(`${valueName}.${naming.pascalCase(header.clientName)} = `); + if (response.struct) { + w.write(`${naming.parameter(header.clientName)} = `); + } else { + w.write(`${valueName}.${naming.pascalCase(header.clientName)} = `); + } if (isPrimitiveType(header.model) && header.model.collectionFormat === `csv`) { if (!header.model.itemType || header.model.itemType.type !== `string`) { throw `collectionFormat csv is only supported for strings, at the moment`; @@ -606,6 +615,17 @@ function generateOperation(w: IndentWriter, serviceModel: IServiceModel, group: }); } } + if (response.struct) { + w.line(); + const separator = IndentWriter.createFenceposter(); + w.write(`${types.getName(model)} ${valueName} = new ${types.getName(model)}(`); + for (const header of headers) { + if (separator()) { w.write(`, `); } + w.write(`${naming.parameter(header.clientName)}`); + } + w.write(`);`); + w.line(); + } w.line(); } } @@ -822,7 +842,7 @@ function generateEnumStrings(w: IndentWriter, model: IServiceModel, type: IEnumT function generateObject(w: IndentWriter, model: IServiceModel, type: IObjectType) { const service = model.service; - const regionName = `class ${naming.type(type.name)}`; + const regionName = type.struct ? `struct ${naming.type(type.name)}` : `class ${naming.type(type.name)}`; w.line(`#region ${regionName}`); w.line(`namespace ${naming.namespace(type.namespace)}`); w.scope('{', '}', () => { @@ -830,7 +850,8 @@ function generateObject(w: IndentWriter, model: IServiceModel, type: IObjectType w.line(`/// ${type.description || type.name}`); w.line(`/// `); if (type.disableWarnings) { w.line(`#pragma warning disable ${type.disableWarnings}`); } - w.line(`${type.public ? 'public' : 'internal'} partial class ${naming.type(type.name)}`); + if (type.struct) { w.line(`${type.public ? 'public' : 'internal'} readonly partial struct ${naming.type(type.name)}: System.IEquatable<${naming.type(type.name)}>`); } + else { w.line(`${type.public ? 'public' : 'internal'} partial class ${naming.type(type.name)}`); } if (type.disableWarnings) { w.line(`#pragma warning restore ${type.disableWarnings}`); } const separator = IndentWriter.createFenceposter(); w.scope('{', '}', () => { @@ -843,10 +864,14 @@ function generateObject(w: IndentWriter, model: IServiceModel, type: IObjectType w.line(`#pragma warning disable CA1819 // Properties should not return arrays`); } w.write(`public ${types.getDeclarationType(property.model, property.required, property.readonly)} ${naming.property(property.clientName)} { get; `); - if (!property.isNullable && (property.readonly || property.model.type === `array`)) { - w.write(`internal `); + if (!type.struct) { + if (!property.isNullable && (property.readonly || property.model.type === `array`)) { + w.write(`internal `); + } + w.write(`set; `); } - w.line(`set; }`); + w.write(`}`); + w.line(); if (property.model.type === `byte`) { w.line(`#pragma warning restore CA1819 // Properties should not return arrays`); } @@ -902,7 +927,81 @@ function generateObject(w: IndentWriter, model: IServiceModel, type: IObjectType w.line(`/// Prevent direct instantiation of ${naming.type(type.name)} instances.`); w.line(`/// You can use ${factoryName}.${naming.type(type.name)} instead.`); w.line(`/// `); - w.line(`internal ${naming.type(type.name)}() { }`); + if (type.struct) { + const properties = Object.values(type.properties); + w.write(`internal ${naming.type(type.name)}(`); + w.scope(() => { + const separator = IndentWriter.createFenceposter(); + for (const property of properties) { + if (separator()) { w.line(`,`); } + w.write(`${types.getDeclarationType(property.model, property.required, property.readonly)} ${naming.parameter(property.clientName)}`); + } + w.line(`)`); + w.scope('{', '}', () => { + for (const property of properties) { + w.line(`${naming.property(property.clientName)} = ${naming.parameter(property.clientName)};`); + } + }); + }); + w.line(); + w.line(`/// `) + w.line(`/// Check if two ${naming.type(type.name)} instances are equal.`); + w.line(`/// `); + w.line(`/// The instance to compare to.`); + w.line(`/// True if they're equal, false otherwise.`); + w.line(`[System.ComponentModel.EditorBrowsable((System.ComponentModel.EditorBrowsableState.Never))]`); + w.line(`public bool Equals(${naming.type(type.name)} other)`); + w.scope('{', '}', () => { + for (const property of properties) { + const a = naming.property(property.clientName); + const b = `other.${naming.property(property.clientName)}`; + if (types.getDeclarationType(property.model, property.required, property.readonly) === "string") { + w.line(`if (!System.StringComparer.Ordinal.Equals(${a}, ${b}))`); + } else if (types.getDeclarationType(property.model, property.required, property.readonly).includes("[]")) { + w.line(`if (!System.Collections.StructuralComparisons.StructuralEqualityComparer.Equals(${a}, ${b}))`); + } else { + w.line(`if (!${a}.Equals(${b}))`); + } + w.scope('{', '}', () => { + w.line(`return false;`); + }); + } + w.line(); + w.line(`return true;`); + }); + w.line(); + w.line(`/// `); + w.line(`/// Check if two ${naming.type(type.name)} instances are equal.`); + w.line(`/// `); + w.line(`/// The instance to compare to.`); + w.line(`/// True if they're equal, false otherwise.`); + w.line(`[System.ComponentModel.EditorBrowsable((System.ComponentModel.EditorBrowsableState.Never))]`); + w.line(`public override bool Equals(object obj) => obj is ${naming.type(type.name)} && Equals((${naming.type(type.name)})obj);`); + w.line(); + w.line(`/// `) + w.line(`/// Get a hash code for the ${naming.type(type.name)}.`); + w.line(`/// `); + w.line(`[System.ComponentModel.EditorBrowsable((System.ComponentModel.EditorBrowsableState.Never))]`); + w.line(`public override int GetHashCode()`); + w.scope('{', '}', () => { + w.line(`var hashCode = new Azure.Core.HashCodeBuilder();`); + for (const property of properties) { + if (types.getDeclarationType(property.model, property.required, property.readonly) === "string") { + w.line(`if (${naming.property(property.clientName)} != null)`) + w.scope('{', '}', () => { + w.line(`hashCode.Add(${naming.property(property.clientName)}, System.StringComparer.Ordinal);`); + }); + } else if (types.getDeclarationType(property.model, property.required, property.readonly).includes("[]")) { + w.line(`hashCode.Add(System.Collections.StructuralComparisons.StructuralEqualityComparer.GetHashCode(${naming.property(property.clientName)}));`); + } else { + w.line(`hashCode.Add(${naming.property(property.clientName)});`); + } + } + w.line(); + w.line(`return hashCode.ToHashCode();`); + }); + } + else { w.line(`internal ${naming.type(type.name)}() { }`); } } // Create serializers if necessary @@ -956,15 +1055,28 @@ function generateObject(w: IndentWriter, model: IServiceModel, type: IObjectType } w.write(`)`); }); - w.scope('{', '}', () => { - w.line(`return new ${typeName}()`); - - w.scope('{', '};', () => { + const separator = IndentWriter.createFenceposter(); + if (type.struct) { + w.scope('{', '}', () => { + w.write(`return new ${typeName}(`); for (const property of props) { - w.line(`${naming.property(property.clientName)} = ${naming.parameter(property.clientName)},`); + if (separator()) { w.write(`, `); } + w.write(`${naming.parameter(property.clientName)}`); + if (!property.required) { w.write(` = default`); } } + w.write(`);`); }); - }); + } else { + w.scope('{', '}', () => { + w.line(`return new ${typeName}()`); + + w.scope('{', '};', () => { + for (const property of props) { + w.line(`${naming.property(property.clientName)} = ${naming.parameter(property.clientName)},`); + } + }); + }); + } }); } }); @@ -1085,7 +1197,7 @@ function generateDeserialize(w: IndentWriter, service: IService, type: IObjectTy // Create the model const valueName = '_value'; const skipInit = (Object.values(type.properties)).some(p => isObjectType(p.model) || (isPrimitiveType(p.model) && (!!p.model.itemType || p.model.type === `dictionary`))); - w.line(`${types.getName(type)} ${valueName} = new ${types.getName(type)}(${skipInit ? 'true' : ''});`); + if (!type.struct) { w.line(`${types.getName(type)} ${valueName} = new ${types.getName(type)}(${skipInit ? 'true' : ''});`); } // Deserialize each of its properties for (const property of properties) { @@ -1181,7 +1293,8 @@ function generateDeserialize(w: IndentWriter, service: IService, type: IObjectTy } } else { // Assign the value - const assignment = `${valueName}.${naming.property(property.clientName)} = ${parse(target, property.model)};`; + const assignment = type.struct ? `${types.getDeclarationType(property.model, true, false, true)} ${naming.parameter(property.clientName)} = ${parse(target, property.model)};` + : `${valueName}.${naming.property(property.clientName)} = ${parse(target, property.model)};`; if (property.required) { // If a property is required, the XML element will always be there so we can just use it w.line(assignment); @@ -1197,6 +1310,16 @@ function generateDeserialize(w: IndentWriter, service: IService, type: IObjectTy } } } + if (type.struct) { + const separator = IndentWriter.createFenceposter(); + w.write(`${types.getName(type)} ${valueName} = new ${types.getName(type)}(`); + for (const property of properties) { + if (separator()) { w.write(`, `); } + w.write(`${naming.parameter(property.clientName)}`); + } + w.write(`);`); + w.line(); + } w.line(`Customize${fromName}(${rootName}, ${valueName});`); w.line(`return ${valueName};`); }); diff --git a/sdk/storage/Azure.Storage.Common/swagger/Generator/src/models.ts b/sdk/storage/Azure.Storage.Common/swagger/Generator/src/models.ts index 94120815c41e9..6c1246d9c2089 100644 --- a/sdk/storage/Azure.Storage.Common/swagger/Generator/src/models.ts +++ b/sdk/storage/Azure.Storage.Common/swagger/Generator/src/models.ts @@ -133,7 +133,8 @@ export interface IObjectType extends IModelType { serialize: boolean, deserialize: boolean, disableWarnings?: string, - public: boolean + public: boolean, + struct: boolean } export function isObjectType(model: IModelType): model is IObjectType { @@ -238,7 +239,8 @@ export interface IResponse { model?: IModelType, exception?: boolean, public: boolean, - returnStream?: boolean + returnStream?: boolean, + struct: boolean } export interface IResponseGroup { diff --git a/sdk/storage/Azure.Storage.Common/swagger/Generator/src/serviceModel.ts b/sdk/storage/Azure.Storage.Common/swagger/Generator/src/serviceModel.ts index 7d811c492fd02..75b139563df03 100644 --- a/sdk/storage/Azure.Storage.Common/swagger/Generator/src/serviceModel.ts +++ b/sdk/storage/Azure.Storage.Common/swagger/Generator/src/serviceModel.ts @@ -434,6 +434,11 @@ function createObjectType(project: IProject, name: string, swagger: any, locatio isPublic = true; } + let isStruct: boolean|undefined = swagger[`x-az-struct`]; + if (isStruct === undefined) { + isStruct = false; + } + const info = required(() => project.cache.info); return { type: `object`, @@ -447,7 +452,8 @@ function createObjectType(project: IProject, name: string, swagger: any, locatio deserialize: false, disableWarnings: swagger[`x-az-disable-warnings`], public: isPublic, - extendedHeaders: [] + extendedHeaders: [], + struct: isStruct }; } @@ -608,6 +614,11 @@ function createResponse(project: IProject, code: string, name: string, swagger: isPublic = true; } + let isStruct: boolean|undefined = swagger[`x-az-struct`]; + if (isStruct === undefined) { + isStruct = false; + } + return { code, description: swagger.description, @@ -617,7 +628,8 @@ function createResponse(project: IProject, code: string, name: string, swagger: headers, exception: optional(() => swagger[`x-az-create-exception`]), public: isPublic, - returnStream: optional(() => swagger[`x-az-stream`]) + returnStream: optional(() => swagger[`x-az-stream`]), + struct: isStruct }; } @@ -953,7 +965,8 @@ function getOperationResponse(project: IProject, responses: IResponses, defaultN properties: { }, serialize: false, deserialize: false, - extendedHeaders: ignoredHeaders + extendedHeaders: ignoredHeaders, + struct: response.struct }; registerCustomType(project, model); @@ -1002,7 +1015,8 @@ function getOperationResponse(project: IProject, responses: IResponses, defaultN body: a.body || b.body, bodyClientName: a.bodyClientName || b.bodyClientName, headers: { ...b.headers, ...a.headers }, - public: a.public && b.public + public: a.public && b.public, + struct: a.struct && b.struct }; } }