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

Add Convenience Types to System.ClientModel library #41016

Merged
merged 19 commits into from
Jan 8, 2024
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions sdk/core/System.ClientModel/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

### Features Added

- Initial release of convenience types in the System.ClientModel namespace, including `ClientResult<T>`, `KeyCredential`, and `ClientResultException`.

### Breaking Changes

### Bugs Fixed
Expand Down
18 changes: 16 additions & 2 deletions sdk/core/System.ClientModel/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# System.ClientModel library for .NET

`System.ClientModel` provides shared primitives, abstractions, and helpers for .NET service client libraries.
`System.ClientModel` contains building blocks for communicating with cloud services. It provides shared primitives, abstractions, and helpers for .NET service client libraries.

`System.ClientModel` allows client libraries built from its components to expose common functionality in a consistent fashion, so that once you learn how to use these APIs in one client library, you'll know how to use them in other client libraries as well.

[Source code][source] | [Package (NuGet)][package]

Expand All @@ -21,15 +23,21 @@ dotnet add package System.ClientModel

None needed for `System.ClientModel`.

### Authenticate the client

The `System.ClientModel` preview package provides a `KeyCredential` type for authentication.

## Key concepts

The main shared concepts of `System.ClientModel` include:

- Accessing HTTP response details (`ClientResult`, `ClientResult<T>`).
- Exceptions for reporting errors from service requests in a consistent fashion (`ClientResultException`).
- Providing APIs to read and write models in different formats.

## Examples

### Simple ModelReaderWriter usage
### Read and write persistable models

As a library author you can implement `IPersistableModel<T>` or `IJsonModel<T>` which will give library users the ability to read and write your models.

Expand All @@ -51,6 +59,12 @@ string json = @"{
OutputModel? model = ModelReaderWriter.Read<OutputModel>(BinaryData.FromString(json));
```

## Troubleshooting

You can troubleshoot `System.ClientModel`-based clients by inspecting the result of any `ClientResultException` thrown from a pipeline's `Send` method.

## Next steps

## Contributing

This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit https://cla.microsoft.com.
Expand Down
63 changes: 63 additions & 0 deletions sdk/core/System.ClientModel/api/System.ClientModel.net6.0.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,42 @@
namespace System.ClientModel
{
public abstract partial class ClientResult
{
protected ClientResult(System.ClientModel.Primitives.PipelineResponse response) { }
public static System.ClientModel.OptionalClientResult<T> FromOptionalValue<T>(T? value, System.ClientModel.Primitives.PipelineResponse response) { throw null; }
public static System.ClientModel.ClientResult FromResponse(System.ClientModel.Primitives.PipelineResponse response) { throw null; }
public static System.ClientModel.ClientResult<T> FromValue<T>(T value, System.ClientModel.Primitives.PipelineResponse response) { throw null; }
public System.ClientModel.Primitives.PipelineResponse GetRawResponse() { throw null; }
}
public partial class ClientResultException : System.Exception, System.Runtime.Serialization.ISerializable
{
public ClientResultException(System.ClientModel.Primitives.PipelineResponse response, string? message = null, System.Exception? innerException = null) { }
protected ClientResultException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { }
public ClientResultException(string message, System.Exception? innerException = null) { }
public int Status { get { throw null; } protected set { } }
public override void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { }
public System.ClientModel.Primitives.PipelineResponse? GetRawResponse() { throw null; }
}
public abstract partial class ClientResult<T> : System.ClientModel.OptionalClientResult<T>
{
protected ClientResult(T value, System.ClientModel.Primitives.PipelineResponse response) : base (default(T), default(System.ClientModel.Primitives.PipelineResponse)) { }
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
public sealed override bool HasValue { get { throw null; } }
public sealed override T Value { get { throw null; } }
}
public partial class KeyCredential
{
public KeyCredential(string key) { }
public string GetValue() { throw null; }
public void Update(string key) { }
}
public abstract partial class OptionalClientResult<T> : System.ClientModel.ClientResult
{
protected OptionalClientResult(T? value, System.ClientModel.Primitives.PipelineResponse response) : base (default(System.ClientModel.Primitives.PipelineResponse)) { }
public virtual bool HasValue { get { throw null; } }
public virtual T? Value { get { throw null; } }
}
}
namespace System.ClientModel.Primitives
{
public partial interface IJsonModel<out T> : System.ClientModel.Primitives.IPersistableModel<T>
Expand All @@ -11,6 +50,17 @@ public partial interface IPersistableModel<out T>
string GetFormatFromOptions(System.ClientModel.Primitives.ModelReaderWriterOptions options);
System.BinaryData Write(System.ClientModel.Primitives.ModelReaderWriterOptions options);
}
public abstract partial class MessageHeaders : System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, string>>, System.Collections.IEnumerable
{
protected MessageHeaders() { }
public abstract void Add(string name, string value);
public abstract System.Collections.Generic.IEnumerator<System.Collections.Generic.KeyValuePair<string, string>> GetEnumerator();
public abstract bool Remove(string name);
public abstract void Set(string name, string value);
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; }
public abstract bool TryGetValue(string name, out string? value);
public abstract bool TryGetValues(string name, out System.Collections.Generic.IEnumerable<string>? values);
}
public static partial class ModelReaderWriter
{
public static object? Read(System.BinaryData data, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicConstructors | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] System.Type returnType, System.ClientModel.Primitives.ModelReaderWriterOptions? options = null) { throw null; }
Expand All @@ -32,4 +82,17 @@ public PersistableModelProxyAttribute([System.Diagnostics.CodeAnalysis.Dynamical
[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicConstructors | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
public System.Type ProxyType { get { throw null; } }
}
public abstract partial class PipelineResponse : System.IDisposable
{
protected PipelineResponse() { }
public System.BinaryData Content { get { throw null; } }
public abstract System.IO.Stream? ContentStream { get; set; }
public System.ClientModel.Primitives.MessageHeaders Headers { get { throw null; } }
public virtual bool IsError { get { throw null; } }
public abstract string ReasonPhrase { get; }
public abstract int Status { get; }
public abstract void Dispose();
protected abstract System.ClientModel.Primitives.MessageHeaders GetHeadersCore();
protected virtual void SetIsErrorCore(bool isError) { }
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,42 @@
namespace System.ClientModel
{
public abstract partial class ClientResult
{
protected ClientResult(System.ClientModel.Primitives.PipelineResponse response) { }
public static System.ClientModel.OptionalClientResult<T> FromOptionalValue<T>(T? value, System.ClientModel.Primitives.PipelineResponse response) { throw null; }
public static System.ClientModel.ClientResult FromResponse(System.ClientModel.Primitives.PipelineResponse response) { throw null; }
public static System.ClientModel.ClientResult<T> FromValue<T>(T value, System.ClientModel.Primitives.PipelineResponse response) { throw null; }
public System.ClientModel.Primitives.PipelineResponse GetRawResponse() { throw null; }
}
public partial class ClientResultException : System.Exception, System.Runtime.Serialization.ISerializable
{
public ClientResultException(System.ClientModel.Primitives.PipelineResponse response, string? message = null, System.Exception? innerException = null) { }
protected ClientResultException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { }
public ClientResultException(string message, System.Exception? innerException = null) { }
public int Status { get { throw null; } protected set { } }
public override void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { }
public System.ClientModel.Primitives.PipelineResponse? GetRawResponse() { throw null; }
}
public abstract partial class ClientResult<T> : System.ClientModel.OptionalClientResult<T>
{
protected ClientResult(T value, System.ClientModel.Primitives.PipelineResponse response) : base (default(T), default(System.ClientModel.Primitives.PipelineResponse)) { }
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
public sealed override bool HasValue { get { throw null; } }
public sealed override T Value { get { throw null; } }
}
public partial class KeyCredential
{
public KeyCredential(string key) { }
public string GetValue() { throw null; }
public void Update(string key) { }
}
public abstract partial class OptionalClientResult<T> : System.ClientModel.ClientResult
{
protected OptionalClientResult(T? value, System.ClientModel.Primitives.PipelineResponse response) : base (default(System.ClientModel.Primitives.PipelineResponse)) { }
public virtual bool HasValue { get { throw null; } }
public virtual T? Value { get { throw null; } }
}
}
namespace System.ClientModel.Primitives
{
public partial interface IJsonModel<out T> : System.ClientModel.Primitives.IPersistableModel<T>
Expand All @@ -11,6 +50,17 @@ public partial interface IPersistableModel<out T>
string GetFormatFromOptions(System.ClientModel.Primitives.ModelReaderWriterOptions options);
System.BinaryData Write(System.ClientModel.Primitives.ModelReaderWriterOptions options);
}
public abstract partial class MessageHeaders : System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, string>>, System.Collections.IEnumerable
{
protected MessageHeaders() { }
public abstract void Add(string name, string value);
public abstract System.Collections.Generic.IEnumerator<System.Collections.Generic.KeyValuePair<string, string>> GetEnumerator();
public abstract bool Remove(string name);
public abstract void Set(string name, string value);
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; }
public abstract bool TryGetValue(string name, out string? value);
public abstract bool TryGetValues(string name, out System.Collections.Generic.IEnumerable<string>? values);
}
public static partial class ModelReaderWriter
{
public static object? Read(System.BinaryData data, System.Type returnType, System.ClientModel.Primitives.ModelReaderWriterOptions? options = null) { throw null; }
Expand All @@ -31,4 +81,17 @@ public sealed partial class PersistableModelProxyAttribute : System.Attribute
public PersistableModelProxyAttribute(System.Type proxyType) { }
public System.Type ProxyType { get { throw null; } }
}
public abstract partial class PipelineResponse : System.IDisposable
{
protected PipelineResponse() { }
public System.BinaryData Content { get { throw null; } }
public abstract System.IO.Stream? ContentStream { get; set; }
public System.ClientModel.Primitives.MessageHeaders Headers { get { throw null; } }
public virtual bool IsError { get { throw null; } }
public abstract string ReasonPhrase { get; }
public abstract int Status { get; }
public abstract void Dispose();
protected abstract System.ClientModel.Primitives.MessageHeaders GetHeadersCore();
protected virtual void SetIsErrorCore(bool isError) { }
}
}
70 changes: 70 additions & 0 deletions sdk/core/System.ClientModel/src/Convenience/ClientResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.ClientModel.Internal;
using System.ClientModel.Primitives;

namespace System.ClientModel;

public abstract class ClientResult
{
private readonly PipelineResponse _response;

protected ClientResult(PipelineResponse response)
{
ClientUtilities.AssertNotNull(response, nameof(response));

_response = response;
}

/// <summary>
/// Returns the HTTP response returned by the service.
/// </summary>
/// <returns>The HTTP response returned by the service.</returns>
public PipelineResponse GetRawResponse() => _response;

#region Factory methods for ClientResult and subtypes

public static ClientResult FromResponse(PipelineResponse response)
=> new ClientModelClientResult(response);

public static ClientResult<T> FromValue<T>(T value, PipelineResponse response)
{
// Null values must use OptionalClientResult<T>
if (value is null)
{
string message = "ClientResult<T> contract guarantees that ClientResult<T>.Value is non-null. " +
"If you need to return an ClientResult where the Value is null, please use OptionalClientResult<T> instead.";

throw new ArgumentNullException(nameof(value), message);
}

return new ClientModelClientResult<T>(value, response);
}

public static OptionalClientResult<T> FromOptionalValue<T>(T? value, PipelineResponse response)
=> new ClientModelOptionalClientResult<T>(value, response);

#endregion

#region Private implementation subtypes of abstract ClientResult types
private class ClientModelClientResult : ClientResult
{
public ClientModelClientResult(PipelineResponse response)
: base(response) { }
}

private class ClientModelOptionalClientResult<T> : OptionalClientResult<T>
{
public ClientModelOptionalClientResult(T? value, PipelineResponse response)
: base(value, response) { }
}

private class ClientModelClientResult<T> : ClientResult<T>
{
public ClientModelClientResult(T value, PipelineResponse response)
: base(value, response) { }
}

#endregion
}
101 changes: 101 additions & 0 deletions sdk/core/System.ClientModel/src/Convenience/ClientResultException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.ClientModel.Internal;
using System.ClientModel.Primitives;
using System.Globalization;
using System.IO;
using System.Runtime.Serialization;
using System.Text;

namespace System.ClientModel;

[Serializable]
public class ClientResultException : Exception, ISerializable
{
private const string DefaultMessage = "Service request failed.";

private readonly PipelineResponse? _response;
private int _status;

/// <summary>
/// Gets the HTTP status code of the response. Returns. <code>0</code> if response was not received.
/// </summary>
public int Status
{
get => _status;
protected set => _status = value;
}

public ClientResultException(PipelineResponse response, string? message = default, Exception? innerException = default)
: base(GetMessage(response, message), innerException)
{
ClientUtilities.AssertNotNull(response, nameof(response));

_response = response;
_status = response.Status;
}

public ClientResultException(string message, Exception? innerException = default)
: base(message, innerException)
{
_status = 0;
}

/// <summary>
/// TBD
/// </summary>
/// <param name="info"></param>
/// <param name="context"></param>
protected ClientResultException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
_status = info.GetInt32(nameof(Status));
}

/// <inheritdoc />
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
ClientUtilities.AssertNotNull(info, nameof(info));

info.AddValue(nameof(Status), Status);

base.GetObjectData(info, context);
}

public PipelineResponse? GetRawResponse() => _response;

// Create message from response if available, and override message, if available.
private static string GetMessage(PipelineResponse response, string? message)
{
// Setting the message will override extracting it from the response.
if (message is not null)
{
return message;
}

response.BufferContent();

StringBuilder messageBuilder = new();

messageBuilder
.AppendLine(DefaultMessage)
.Append("Status: ")
.Append(response.Status.ToString(CultureInfo.InvariantCulture));

if (!string.IsNullOrEmpty(response.ReasonPhrase))
{
messageBuilder.Append(" (")
.Append(response.ReasonPhrase)
.AppendLine(")");
}
else
{
messageBuilder.AppendLine();
}

// Content or headers can be obtained from raw response so are not added here.

return messageBuilder.ToString();
}
}
Loading