| | 1 | | // Copyright (c) Microsoft Corporation. All rights reserved. |
| | 2 | | // Licensed under the MIT License. |
| | 3 | |
|
| | 4 | | using System; |
| | 5 | | using System.ComponentModel; |
| | 6 | | using System.IO; |
| | 7 | | using System.Runtime.InteropServices; |
| | 8 | | using System.Text; |
| | 9 | | using System.Threading; |
| | 10 | | using System.Threading.Tasks; |
| | 11 | | using Azure.Core.Pipeline; |
| | 12 | | using Azure.Core.Serialization; |
| | 13 | |
|
| | 14 | | namespace Azure.Core |
| | 15 | | { |
| | 16 | | /// <summary> |
| | 17 | | /// A lightweight abstraction for a payload of bytes. This type integrates with <see cref="ObjectSerializer"/> |
| | 18 | | /// to allow for serializing and deserializing payloads. |
| | 19 | | /// |
| | 20 | | /// The ownership model of the underlying bytes varies depending on how the instance is constructed: |
| | 21 | | /// |
| | 22 | | /// If created using the static factory method, <see cref="FromMemory(ReadOnlyMemory{byte})"/>, |
| | 23 | | /// the passed in bytes will be wrapped, rather than copied. This is useful in scenarios where performance |
| | 24 | | /// is critical and/or ownership of the bytes is controlled completely by the consumer, thereby allowing the |
| | 25 | | /// enforcement of whatever ownership model is needed. |
| | 26 | | /// |
| | 27 | | /// If created using the <see cref="BinaryData(ReadOnlySpan{byte})"/> constructor, <see cref="BinaryData"/> will |
| | 28 | | /// maintain its own copy of the underlying bytes. This usage is geared more towards scenarios where the ownership |
| | 29 | | /// of the bytes might be ambiguous to users of the consuming code. By making a copy of the bytes, the payload is |
| | 30 | | /// guaranteed to be immutable. |
| | 31 | | /// |
| | 32 | | /// For all other constructors and static factory methods, BinaryData will assume ownership of the underlying bytes. |
| | 33 | | /// </summary> |
| | 34 | | public readonly struct BinaryData |
| | 35 | | { |
| | 36 | | private const int CopyToBufferSize = 81920; |
| | 37 | |
|
| 2 | 38 | | private static readonly UTF8Encoding s_encoding = new UTF8Encoding(false); |
| | 39 | |
|
| | 40 | | /// <summary> |
| | 41 | | /// The backing store for the <see cref="BinaryData"/> instance. |
| | 42 | | /// </summary> |
| 158 | 43 | | public ReadOnlyMemory<byte> Bytes { get; } |
| | 44 | |
|
| | 45 | | /// <summary> |
| | 46 | | /// Creates a binary data instance by making a copy |
| | 47 | | /// of the passed in bytes. |
| | 48 | | /// </summary> |
| | 49 | | /// <param name="data">Byte data.</param> |
| | 50 | | public BinaryData(ReadOnlySpan<byte> data) |
| | 51 | | { |
| 2 | 52 | | Bytes = data.ToArray(); |
| 2 | 53 | | } |
| | 54 | |
|
| | 55 | | /// <summary> |
| | 56 | | /// Creates a binary data instance by wrapping the |
| | 57 | | /// passed in bytes. |
| | 58 | | /// </summary> |
| | 59 | | /// <param name="data">Byte data.</param> |
| | 60 | | private BinaryData(ReadOnlyMemory<byte> data) |
| | 61 | | { |
| 38 | 62 | | Bytes = data; |
| 38 | 63 | | } |
| | 64 | | /// <summary> |
| | 65 | | /// Creates a binary data instance from a string by converting |
| | 66 | | /// the string to bytes using UTF-8 encoding. |
| | 67 | | /// </summary> |
| | 68 | | /// <param name="data">The string data.</param> |
| | 69 | | /// <returns>A <see cref="BinaryData"/> instance.</returns> |
| | 70 | | /// <remarks>The byte order mark is not included as part of the encoding process.</remarks> |
| | 71 | | public BinaryData(string data) |
| | 72 | | { |
| 2 | 73 | | Bytes = s_encoding.GetBytes(data); |
| 2 | 74 | | } |
| | 75 | |
|
| | 76 | | /// <summary> |
| | 77 | | /// Creates a binary data instance by wrapping the passed in |
| | 78 | | /// <see cref="ReadOnlyMemory{Byte}"/>. |
| | 79 | | /// </summary> |
| | 80 | | /// <param name="data"></param> |
| | 81 | | /// <returns>A <see cref="BinaryData"/> instance.</returns> |
| | 82 | | public static BinaryData FromMemory(ReadOnlyMemory<byte> data) => |
| 18 | 83 | | new BinaryData(data); |
| | 84 | |
|
| | 85 | | /// <summary> |
| | 86 | | /// Creates a binary data instance from the specified stream. |
| | 87 | | /// </summary> |
| | 88 | | /// <param name="stream">Stream containing the data.</param> |
| | 89 | | /// <returns>A <see cref="BinaryData"/> instance.</returns> |
| | 90 | | public static BinaryData FromStream(Stream stream) => |
| 8 | 91 | | FromStreamAsync(stream, false).EnsureCompleted(); |
| | 92 | |
|
| | 93 | | /// <summary> |
| | 94 | | /// Creates a binary data instance from the specified stream. |
| | 95 | | /// </summary> |
| | 96 | | /// <param name="stream">Stream containing the data.</param> |
| | 97 | | /// <param name="cancellationToken">An optional<see cref="CancellationToken"/> instance to signal |
| | 98 | | /// the request to cancel the operation.</param> |
| | 99 | | /// <returns>A <see cref="BinaryData"/> instance.</returns> |
| | 100 | | public static async Task<BinaryData> FromStreamAsync( |
| | 101 | | Stream stream, |
| | 102 | | CancellationToken cancellationToken = default) => |
| 4 | 103 | | await FromStreamAsync(stream, true, cancellationToken).ConfigureAwait(false); |
| | 104 | |
|
| | 105 | | private static async Task<BinaryData> FromStreamAsync( |
| | 106 | | Stream stream, |
| | 107 | | bool async, |
| | 108 | | CancellationToken cancellationToken = default) |
| | 109 | | { |
| 12 | 110 | | Argument.AssertNotNull(stream, nameof(stream)); |
| 8 | 111 | | if (stream.CanSeek && stream.Length > int.MaxValue) |
| | 112 | | { |
| 2 | 113 | | throw new ArgumentOutOfRangeException( |
| 2 | 114 | | nameof(stream), |
| 2 | 115 | | "Stream length must be less than Int32.MaxValue"); |
| | 116 | | } |
| 6 | 117 | | using (var memoryStream = new MemoryStream()) |
| | 118 | | { |
| 6 | 119 | | if (async) |
| | 120 | | { |
| 2 | 121 | | await stream.CopyToAsync(memoryStream, CopyToBufferSize, cancellationToken).ConfigureAwait(false); |
| | 122 | | } |
| | 123 | | else |
| | 124 | | { |
| 4 | 125 | | stream.CopyTo(memoryStream); |
| | 126 | | } |
| 6 | 127 | | return new BinaryData((ReadOnlyMemory<byte>) memoryStream.ToArray()); |
| | 128 | | } |
| 6 | 129 | | } |
| | 130 | |
|
| | 131 | | /// <summary> |
| | 132 | | /// Creates a BinaryData instance from the specified data using |
| | 133 | | /// the <see cref="JsonObjectSerializer"/>. |
| | 134 | | /// </summary> |
| | 135 | | /// <typeparam name="T">The type of the data.</typeparam> |
| | 136 | | /// <param name="data">The data to use.</param> |
| | 137 | | /// <param name="cancellationToken">The <see cref="CancellationToken"/> to use during serialization.</param> |
| | 138 | | /// <returns>A <see cref="BinaryData"/> instance.</returns> |
| | 139 | | public static BinaryData Serialize<T>( |
| | 140 | | T data, |
| | 141 | | CancellationToken cancellationToken = default) => |
| 4 | 142 | | Serialize<T>(data, new JsonObjectSerializer(), cancellationToken); |
| | 143 | |
|
| | 144 | | /// <summary> |
| | 145 | | /// Creates a BinaryData instance from the specified data |
| | 146 | | /// using the <see cref="JsonObjectSerializer"/>. |
| | 147 | | /// </summary> |
| | 148 | | /// <typeparam name="T">The type of the data.</typeparam> |
| | 149 | | /// <param name="data">The data to use.</param> |
| | 150 | | /// <param name="cancellationToken">The <see cref="CancellationToken"/> to use during serialization.</param> |
| | 151 | | /// <returns>A <see cref="BinaryData"/> instance.</returns> |
| | 152 | | public static async Task<BinaryData> SerializeAsync<T>( |
| | 153 | | T data, |
| | 154 | | CancellationToken cancellationToken = default) => |
| 4 | 155 | | await SerializeInternalAsync<T>(data, new JsonObjectSerializer(), true, cancellationToken).ConfigureAwait(fa |
| | 156 | |
|
| | 157 | | /// <summary> |
| | 158 | | /// Creates a BinaryData instance from the specified data using |
| | 159 | | /// the provided <see cref="ObjectSerializer"/>. |
| | 160 | | /// </summary> |
| | 161 | | /// <typeparam name="T">The type of the data.</typeparam> |
| | 162 | | /// <param name="data">The data to use.</param> |
| | 163 | | /// <param name="serializer">The serializer to serialize |
| | 164 | | /// the data.</param> |
| | 165 | | /// <param name="cancellationToken">The <see cref="CancellationToken"/> to use during serialization.</param> |
| | 166 | | /// <returns>A <see cref="BinaryData"/> instance.</returns> |
| | 167 | | public static BinaryData Serialize<T>( |
| | 168 | | T data, |
| | 169 | | ObjectSerializer serializer, |
| | 170 | | CancellationToken cancellationToken = default) => |
| 10 | 171 | | SerializeInternalAsync<T>(data, serializer, false, cancellationToken).EnsureCompleted(); |
| | 172 | |
|
| | 173 | | /// <summary> |
| | 174 | | /// Creates a BinaryData instance from the specified data using |
| | 175 | | /// the provided <see cref="ObjectSerializer"/>. |
| | 176 | | /// </summary> |
| | 177 | | /// <typeparam name="T">The type of the data.</typeparam> |
| | 178 | | /// <param name="data">The data to use.</param> |
| | 179 | | /// <param name="serializer">The serializer to serialize |
| | 180 | | /// the data.</param> |
| | 181 | | /// <param name="cancellationToken">The <see cref="CancellationToken"/> to use during serialization.</param> |
| | 182 | | /// <returns>A <see cref="BinaryData"/> instance.</returns> |
| | 183 | | public static async Task<BinaryData> SerializeAsync<T>( |
| | 184 | | T data, |
| | 185 | | ObjectSerializer serializer, |
| | 186 | | CancellationToken cancellationToken = default) => |
| 4 | 187 | | await SerializeInternalAsync<T>(data, serializer, true, cancellationToken).ConfigureAwait(false); |
| | 188 | |
|
| | 189 | | private static async Task<BinaryData> SerializeInternalAsync<T>( |
| | 190 | | T data, |
| | 191 | | ObjectSerializer serializer, |
| | 192 | | bool async, |
| | 193 | | CancellationToken cancellationToken) |
| | 194 | | { |
| 18 | 195 | | Argument.AssertNotNull(serializer, nameof(serializer)); |
| 14 | 196 | | using var memoryStream = new MemoryStream(); |
| 14 | 197 | | if (async) |
| | 198 | | { |
| 6 | 199 | | await serializer.SerializeAsync(memoryStream, data, typeof(T), cancellationToken).ConfigureAwait(false); |
| | 200 | | } |
| | 201 | | else |
| | 202 | | { |
| 8 | 203 | | serializer.Serialize(memoryStream, data, typeof(T), cancellationToken); |
| | 204 | | } |
| 14 | 205 | | return new BinaryData((ReadOnlyMemory<byte>) memoryStream.ToArray()); |
| 14 | 206 | | } |
| | 207 | |
|
| | 208 | | /// <summary> |
| | 209 | | /// Converts the BinaryData to a string using UTF-8. |
| | 210 | | /// </summary> |
| | 211 | | /// <returns>The string representation of the data.</returns> |
| | 212 | | public override string ToString() |
| | 213 | | { |
| 4 | 214 | | if (MemoryMarshal.TryGetArray( |
| 4 | 215 | | Bytes, |
| 4 | 216 | | out ArraySegment<byte> data)) |
| | 217 | | { |
| 4 | 218 | | return s_encoding.GetString(data.Array, data.Offset, data.Count); |
| | 219 | | } |
| 0 | 220 | | return s_encoding.GetString(Bytes.ToArray()); |
| | 221 | | } |
| | 222 | |
|
| | 223 | | /// <summary> |
| | 224 | | /// Converts the BinaryData to a stream. |
| | 225 | | /// </summary> |
| | 226 | | /// <returns>A stream representing the data.</returns> |
| | 227 | | public Stream ToStream() => |
| 112 | 228 | | new ReadOnlyMemoryStream(Bytes); |
| | 229 | |
|
| | 230 | | /// <summary> |
| | 231 | | /// Converts the BinaryData to the specified type using |
| | 232 | | /// the provided <see cref="ObjectSerializer"/>. |
| | 233 | | /// </summary> |
| | 234 | | /// <typeparam name="T">The type that the data should be |
| | 235 | | /// converted to.</typeparam> |
| | 236 | | /// <param name="serializer">The serializer to use |
| | 237 | | /// when deserializing the data.</param> |
| | 238 | | /// <param name="cancellationToken">The <see cref="CancellationToken"/> to use during deserialization.</param> |
| | 239 | | ///<returns>The data converted to the specified type.</returns> |
| | 240 | | public T Deserialize<T>(ObjectSerializer serializer, CancellationToken cancellationToken = default) => |
| 26 | 241 | | DeserializeInternalAsync<T>(serializer, false, cancellationToken).EnsureCompleted(); |
| | 242 | |
|
| | 243 | | /// <summary> |
| | 244 | | /// Converts the BinaryData to the specified type using the |
| | 245 | | /// <see cref="JsonObjectSerializer"/>. |
| | 246 | | /// </summary> |
| | 247 | | /// <typeparam name="T">The type that the data should be |
| | 248 | | /// converted to.</typeparam> |
| | 249 | | /// <param name="cancellationToken">The <see cref="CancellationToken"/> to use during deserialization.</param> |
| | 250 | | ///<returns>The data cast to the specified type. If the cast cannot |
| | 251 | | ///be performed, an <see cref="InvalidCastException"/> will be |
| | 252 | | ///thrown.</returns> |
| | 253 | | public async ValueTask<T> DeserializeAsync<T>(CancellationToken cancellationToken = default) => |
| 28 | 254 | | await DeserializeInternalAsync<T>(new JsonObjectSerializer(), true, cancellationToken).ConfigureAwait(false) |
| | 255 | |
|
| | 256 | | /// <summary> |
| | 257 | | /// Converts the BinaryData to the specified type using the |
| | 258 | | /// <see cref="JsonObjectSerializer"/>. |
| | 259 | | /// </summary> |
| | 260 | | /// <typeparam name="T">The type that the data should be |
| | 261 | | /// converted to.</typeparam> |
| | 262 | | /// <param name="cancellationToken">The <see cref="CancellationToken"/> to use during deserialization.</param> |
| | 263 | | ///<returns>The data converted to the specified type.</returns> |
| | 264 | | public T Deserialize<T>(CancellationToken cancellationToken = default) => |
| 28 | 265 | | DeserializeInternalAsync<T>(new JsonObjectSerializer(), false, cancellationToken).EnsureCompleted(); |
| | 266 | |
|
| | 267 | | /// <summary> |
| | 268 | | /// Converts the BinaryData to the specified type using the |
| | 269 | | /// provided <see cref="ObjectSerializer"/>. |
| | 270 | | /// </summary> |
| | 271 | | /// <typeparam name="T">The type that the data should be |
| | 272 | | /// converted to.</typeparam> |
| | 273 | | /// <param name="serializer">The serializer to use |
| | 274 | | /// when deserializing the data.</param> |
| | 275 | | /// <param name="cancellationToken">The <see cref="CancellationToken"/> to use during deserialization.</param> |
| | 276 | | ///<returns>The data cast to the specified type. If the cast cannot |
| | 277 | | ///be performed, an <see cref="InvalidCastException"/> will be |
| | 278 | | ///thrown.</returns> |
| | 279 | | public async ValueTask<T> DeserializeAsync<T>(ObjectSerializer serializer, CancellationToken cancellationToken = |
| 26 | 280 | | await DeserializeInternalAsync<T>(serializer, true, cancellationToken).ConfigureAwait(false); |
| | 281 | |
|
| | 282 | | private async ValueTask<T> DeserializeInternalAsync<T>( |
| | 283 | | ObjectSerializer serializer, |
| | 284 | | bool async, |
| | 285 | | CancellationToken cancellationToken) |
| | 286 | | { |
| 108 | 287 | | Argument.AssertNotNull(serializer, nameof(serializer)); |
| 104 | 288 | | if (async) |
| | 289 | | { |
| 52 | 290 | | return (T)await serializer.DeserializeAsync( |
| 52 | 291 | | ToStream(), |
| 52 | 292 | | typeof(T), |
| 52 | 293 | | cancellationToken) |
| 52 | 294 | | .ConfigureAwait(false); |
| | 295 | | } |
| | 296 | | else |
| | 297 | | { |
| 52 | 298 | | return (T)serializer.Deserialize(ToStream(), typeof(T), cancellationToken); |
| | 299 | | } |
| 96 | 300 | | } |
| | 301 | |
|
| | 302 | | /// <summary> |
| | 303 | | /// Implicit conversion to bytes. |
| | 304 | | /// </summary> |
| | 305 | | /// <param name="data"></param> |
| | 306 | | public static implicit operator ReadOnlyMemory<byte>( |
| | 307 | | BinaryData data) => |
| 4 | 308 | | data.Bytes; |
| | 309 | |
|
| | 310 | | /// <summary> |
| | 311 | | /// Two BinaryData objects are equal if the memory regions point to the same array and have the same length. |
| | 312 | | /// The method does not check to see if the contents are equal. |
| | 313 | | /// </summary> |
| | 314 | | /// <param name="obj">The BinaryData to compare.</param> |
| | 315 | | /// <returns>true if the current instance and other are equal; otherwise, false.</returns> |
| | 316 | | [EditorBrowsable(EditorBrowsableState.Never)] |
| | 317 | | public override bool Equals(object? obj) |
| | 318 | | { |
| 10 | 319 | | if (obj is BinaryData data) |
| | 320 | | { |
| 8 | 321 | | return data.Bytes.Equals(Bytes); |
| | 322 | | } |
| 2 | 323 | | return false; |
| | 324 | | } |
| | 325 | | /// <inheritdoc /> |
| | 326 | | [EditorBrowsable(EditorBrowsableState.Never)] |
| | 327 | | public override int GetHashCode() => |
| 10 | 328 | | Bytes.GetHashCode(); |
| | 329 | | } |
| | 330 | | } |