Skip to content

Commit

Permalink
mpfd apis proposal
Browse files Browse the repository at this point in the history
  • Loading branch information
jorgerangel-msft committed Mar 7, 2025
1 parent eab347f commit 5e0f976
Show file tree
Hide file tree
Showing 16 changed files with 902 additions and 14 deletions.
57 changes: 57 additions & 0 deletions sdk/core/System.ClientModel/src/Convenience/MultiPartFile.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.ClientModel.Internal;
using System.IO;

namespace System.ClientModel.Primitives;

/// <summary>
/// A file to be uploaded as part of a multipart request.
/// </summary>
public class MultiPartFile
{
/// <summary>
/// Creates a new instance of <see cref="MultiPartFile"/>.
/// </summary>
public MultiPartFile(Stream contents, string? filename = default, string? contentType = default)
{
Argument.AssertNotNull(contents, nameof(contents));

File = contents;
Filename = filename;
ContentType = contentType ?? "application/octet-stream";
}

/// <summary>
/// Creates a new instance of <see cref="MultiPartFile"/>.
/// </summary>
public MultiPartFile(BinaryData contents, string? filename = default, string? contentType = default)
{
Argument.AssertNotNull(contents, nameof(contents));

Contents = contents;
Filename = filename;
ContentType = contentType ?? "application/octet-stream";
}

/// <summary>
/// The file stream to be uploaded as part of a multipart request.
/// </summary>
public Stream? File { get; }

/// <summary>
/// The file contents to be uploaded as part of a multipart request.
/// </summary>
public BinaryData? Contents { get; }

/// <summary>
/// The name of the file to be uploaded as part of a multipart request.
/// </summary>
public string? Filename { get; }

/// <summary>
/// The content type of the file to be uploaded as part of a multipart request.
/// </summary>
public string ContentType { get; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT License.

using System.Collections;
using System.IO;

namespace System.ClientModel.Primitives;

Expand Down Expand Up @@ -72,4 +73,5 @@ private static object GetFirstObject(IEnumerable enumerable)
}

internal abstract BinaryData Write(IEnumerable enumerable, ModelReaderWriterOptions options);
internal abstract void WriteTo(IEnumerable enumerable, Stream stream, ModelReaderWriterOptions options);
}
22 changes: 22 additions & 0 deletions sdk/core/System.ClientModel/src/ModelReaderWriter/IStreamModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.IO;

namespace System.ClientModel.Primitives;

/// <summary>
/// Allows an object to control its own writing to a <see cref="Stream"/>.
/// The format is determined by the implementer.
/// </summary>
/// <typeparam name="T">The type the model can be converted into.</typeparam>
public interface IStreamModel<out T> : IPersistableModel<T>
{
/// <summary>
/// Writes the model into the provided <see cref="Stream"/>.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to write the model into.</param>
/// <param name="options">The <see cref="ModelReaderWriterOptions"/> to use.</param>
/// <exception cref="FormatException">If the model does not support the requested <see cref="ModelReaderWriterOptions.Format"/>.</exception>
void Write(Stream stream, ModelReaderWriterOptions options);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System.ClientModel.Internal;
using System.Collections;
using System.IO;
using System.Text.Json;

namespace System.ClientModel.Primitives;
Expand All @@ -18,6 +19,13 @@ internal override BinaryData Write(IEnumerable enumerable, ModelReaderWriterOpti
return sequenceWriter.ExtractReader().ToBinaryData();
}

internal override void WriteTo(IEnumerable enumerable, Stream stream, ModelReaderWriterOptions options)
{
using var writer = new Utf8JsonWriter(stream);
WriteEnumerable(enumerable, writer, options);
writer.Flush();
}

private static void WriteJson(object model, Utf8JsonWriter writer, ModelReaderWriterOptions options)
{
if (model is IJsonModel<object> jsonModel)
Expand Down
210 changes: 202 additions & 8 deletions sdk/core/System.ClientModel/src/ModelReaderWriter/ModelReaderWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.ClientModel.Internal;
using System.Collections;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Runtime.CompilerServices;

namespace System.ClientModel.Primitives;
Expand Down Expand Up @@ -67,6 +68,65 @@ public static BinaryData Write(object model, ModelReaderWriterOptions? options =
}
}

/// <summary>
/// Writes the model into the provided <see cref="Stream"/>.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="model"></param>
/// <param name="stream"></param>
/// <param name="options"></param>
/// <exception cref="ArgumentNullException"></exception>
public static void Write<T>(T model, Stream stream, ModelReaderWriterOptions? options = default)
where T : IStreamModel<T>
{
if (model is null)
{
throw new ArgumentNullException(nameof(model));
}

if (stream is null)
{
throw new ArgumentNullException(nameof(stream));
}

options ??= ModelReaderWriterOptions.Json;

WriteStreamModel(model, stream, options);
}

/// <summary>
/// Writes the model into the provided <see cref="Stream"/>.
/// </summary>
/// <param name="model"></param>
/// <param name="stream"></param>
/// <param name="options"></param>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="InvalidOperationException"></exception>
public static void Write(object model, Stream stream, ModelReaderWriterOptions? options = default)
{
if (model is null)
{
throw new ArgumentNullException(nameof(model));
}
if (stream is null)
{
throw new ArgumentNullException(nameof(stream));
}

options ??= ModelReaderWriterOptions.Json;

//temp blocking this for symetry of functionality on read/write with no context.
//will be allowed after https://github.com/Azure/azure-sdk-for-net/issues/48294
if (model is IStreamModel<object> iModel)
{
WriteStreamModel(iModel, stream, options);
}
else
{
throw new InvalidOperationException($"{model.GetType().Name} does not implement IStreamModel");
}
}

/// <summary>
/// Converts the value of a model into a <see cref="BinaryData"/>.
/// </summary>
Expand Down Expand Up @@ -111,6 +171,56 @@ public static BinaryData Write(object model, ModelReaderWriterContext context, M
return WritePersistableOrEnumerable(model, context, options);
}

/// <summary>
/// Writes the model into the provided <see cref="Stream"/>.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="model"></param>
/// <param name="stream"></param>
/// <param name="context"></param>
/// <param name="options"></param>
public static void Write<T>(T model, Stream stream, ModelReaderWriterContext context, ModelReaderWriterOptions? options = default)
{
if (model is null)
{
throw new ArgumentNullException(nameof(model));
}

if (stream is null)
{
throw new ArgumentNullException(nameof(stream));
}

options ??= ModelReaderWriterOptions.Json;

WriteStreamModelOrEnumerable(model, stream, context, options);
}

/// <summary>
/// Writes the model into the provided <see cref="Stream"/>.
/// </summary>
/// <param name="model"></param>
/// <param name="stream"></param>
/// <param name="context"></param>
/// <param name="options"></param>
/// <exception cref="ArgumentNullException"></exception>
public static void Write(object model, Stream stream, ModelReaderWriterContext context, ModelReaderWriterOptions? options = default)
{
if (model is null)
{
throw new ArgumentNullException(nameof(model));
}

if (stream is null)
{
throw new ArgumentNullException(nameof(stream));
}

options ??= ModelReaderWriterOptions.Json;

WriteStreamModelOrEnumerable(model, stream, context, options);
}

private static BinaryData WritePersistableOrEnumerable<T>(T model, ModelReaderWriterContext context, ModelReaderWriterOptions options)
{
if (model is IPersistableModel<T> iModel)
Expand All @@ -119,11 +229,9 @@ private static BinaryData WritePersistableOrEnumerable<T>(T model, ModelReaderWr
}
else
{
var enumerable = model as IEnumerable ?? context.GetModelInfoInternal(model!.GetType()).GetEnumerable(model);
if (enumerable is not null)
if (TryWriteEnumerable(model, context, options, out BinaryData? data) && data != null)
{
var collectionWriter = CollectionWriter.GetCollectionWriter(enumerable, options);
return collectionWriter.Write(enumerable, options);
return data;
}
else
{
Expand All @@ -134,17 +242,103 @@ private static BinaryData WritePersistableOrEnumerable<T>(T model, ModelReaderWr

private static BinaryData WritePersistable<T>(IPersistableModel<T> model, ModelReaderWriterOptions options)
{
if (ShouldWriteAsJson(model, options, out IJsonModel<T>? jsonModel))
if (TryWriteJson(model, options, out BinaryData? data) && data != null)
{
return data;
}
else
{
using (UnsafeBufferSequence.Reader reader = new ModelWriter<T>(jsonModel, options).ExtractReader())
return model.Write(options);
}
}

private static void WriteStreamModelOrEnumerable<T>(
T model,
Stream stream,
ModelReaderWriterContext context,
ModelReaderWriterOptions options)
{
if (model is IStreamModel<T> iModel)
{
WriteStreamModel(iModel, stream, options);
return;
}
else
{
if (TryWriteEnumerable(model, context, options, out _, stream))
{
return reader.ToBinaryData();
return;
}
else
{
throw new InvalidOperationException($"{model!.GetType().Name} must implement IEnumerable or IStreamModel");
}
}
}

private static void WriteStreamModel<T>(IStreamModel<T> model, Stream stream, ModelReaderWriterOptions options)
{
if (TryWriteJson(model, options, out _, stream))
{
return;
}
else
{
return model.Write(options);
model.Write(stream, options);
}

return;
}

private static bool TryWriteJson<T>(
IPersistableModel<T> model,
ModelReaderWriterOptions options,
out BinaryData? data,
Stream? stream = default)
{
data = null;

if (ShouldWriteAsJson(model, options, out IJsonModel<T>? jsonModel))
{
var writer = new ModelWriter<T>(jsonModel, options);
if (stream != null)
{
writer.WriteTo(stream);
return true;
}

using var reader = writer.ExtractReader();
data = reader.ToBinaryData();
return true;
}

return false;
}

private static bool TryWriteEnumerable<T>(
T model,
ModelReaderWriterContext context,
ModelReaderWriterOptions options,
out BinaryData? data,
Stream? stream = default)
{
data = null;

var enumerable = model as IEnumerable ?? context.GetModelInfoInternal(model!.GetType()).GetEnumerable(model);
if (enumerable != null)
{
var collectionWriter = CollectionWriter.GetCollectionWriter(enumerable, options);
if (stream != null)
{
collectionWriter.WriteTo(enumerable, stream, options);
return true;
}

data = collectionWriter.Write(enumerable, options);
return true;
}

return false;
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT License.

using System.ClientModel.Primitives;
using System.IO;
using System.Text.Json;

namespace System.ClientModel.Internal;
Expand Down Expand Up @@ -30,8 +31,19 @@ public UnsafeBufferSequence.Reader ExtractReader()
{
using UnsafeBufferSequence sequenceWriter = new UnsafeBufferSequence();
using var jsonWriter = new Utf8JsonWriter(sequenceWriter);
WriteToInternal(jsonWriter);
return sequenceWriter.ExtractReader();
}

public void WriteTo(Stream stream)
{
using var jsonWriter = new Utf8JsonWriter(stream);
WriteToInternal(jsonWriter);
}

private void WriteToInternal(Utf8JsonWriter jsonWriter)
{
_model.Write(jsonWriter, _options);
jsonWriter.Flush();
return sequenceWriter.ExtractReader();
}
}
Loading

0 comments on commit 5e0f976

Please sign in to comment.