Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
40 changes: 40 additions & 0 deletions templates-v7/csharp/AssemblyInfo.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("{{packageTitle}}")]
[assembly: AssemblyDescription("{{packageDescription}}")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("{{packageCompany}}")]
[assembly: AssemblyProduct("{{packageProductName}}")]
[assembly: AssemblyCopyright("{{packageCopyright}}")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]

// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("{{packageVersion}}")]
[assembly: AssemblyFileVersion("{{packageVersion}}")]
{{^supportsAsync}}
// Settings which don't support asynchronous operations rely on non-public constructors
// This is due to how RestSharp requires the type constraint `where T : new()` in places it probably shouldn't.
[assembly: InternalsVisibleTo("RestSharp")]
[assembly: InternalsVisibleTo("NewtonSoft.Json")]
[assembly: InternalsVisibleTo("JsonSubTypes")]
{{/supportsAsync}}
1 change: 1 addition & 0 deletions templates-v7/csharp/NullConditionalParameter.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{{#isNullable}}{{nrt?}}{{^nrt}}{{#vendorExtensions.x-is-value-type}}?{{/vendorExtensions.x-is-value-type}}{{/nrt}}{{/isNullable}}
1 change: 1 addition & 0 deletions templates-v7/csharp/NullConditionalProperty.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{{#lambda.first}}{{#isNullable}}{{nrt?}}{{^nrt}}{{#vendorExtensions.x-is-value-type}}?{{/vendorExtensions.x-is-value-type}}{{/nrt}} {{/isNullable}}{{^required}}{{nrt?}}{{^nrt}}{{#vendorExtensions.x-is-value-type}}?{{/vendorExtensions.x-is-value-type}}{{/nrt}} {{/required}}{{/lambda.first}}
21 changes: 21 additions & 0 deletions templates-v7/csharp/OpenAPIDateConverter.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{{>partial_header}}
using Newtonsoft.Json.Converters;

namespace {{packageName}}.Client
{
/// <summary>
/// Formatter for 'date' openapi formats ss defined by full-date - RFC3339
/// see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#data-types
/// </summary>
public class OpenAPIDateConverter : IsoDateTimeConverter
{
/// <summary>
/// Initializes a new instance of the <see cref="OpenAPIDateConverter" /> class.
/// </summary>
public OpenAPIDateConverter()
{
// full-date = date-fullyear "-" date-month "-" date-mday
DateTimeFormat = "yyyy-MM-dd";
}
}
}
6 changes: 6 additions & 0 deletions templates-v7/csharp/ValidateRegex.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// {{{name}}} ({{{dataType}}}) pattern.
Regex regex{{{name}}} = new Regex(@"{{{vendorExtensions.x-regex}}}"{{#vendorExtensions.x-modifiers}}{{#-first}}, {{/-first}}RegexOptions.{{{.}}}{{^-last}} | {{/-last}}{{/vendorExtensions.x-modifiers}});
if (!regex{{{name}}}.Match(this.{{{name}}}{{#isUuid}}.ToString(){{/isUuid}}).Success)
{
yield return new System.ComponentModel.DataAnnotations.ValidationResult("Invalid value for {{{name}}}, must match a pattern of " + regex{{{name}}}, new [] { "{{{name}}}" });
}
136 changes: 136 additions & 0 deletions templates-v7/csharp/auth/OAuthAuthenticator.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
{{>partial_header}}

using System;
using System.Threading.Tasks;
using Newtonsoft.Json;
using RestSharp;
using RestSharp.Authenticators;

namespace {{packageName}}.{{clientPackage}}.Auth
{
/// <summary>
/// An authenticator for OAuth2 authentication flows
/// </summary>
public class OAuthAuthenticator : IAuthenticator
{
private TokenResponse{{nrt?}} _token;

/// <summary>
/// Returns the current authentication token. Can return null if there is no authentication token, or it has expired.
/// </summary>
public string{{nrt?}} Token
{
get
{
if (_token == null) return null;
if (_token.ExpiresIn == null) return _token.AccessToken;
if (_token.ExpiresAt < DateTime.Now) return null;

return _token.AccessToken;
}
}

readonly string _tokenUrl;
readonly string _clientId;
readonly string _clientSecret;
readonly string{{nrt?}} _scope;
readonly string _grantType;
readonly JsonSerializerSettings _serializerSettings;
readonly IReadableConfiguration _configuration;

/// <summary>
/// Initialize the OAuth2 Authenticator
/// </summary>
public OAuthAuthenticator(
string tokenUrl,
string clientId,
string clientSecret,
string{{nrt?}} scope,
OAuthFlow? flow,
JsonSerializerSettings serializerSettings,
IReadableConfiguration configuration)
{
_tokenUrl = tokenUrl;
_clientId = clientId;
_clientSecret = clientSecret;
_scope = scope;
_serializerSettings = serializerSettings;
_configuration = configuration;

switch (flow)
{
/*case OAuthFlow.ACCESS_CODE:
_grantType = "authorization_code";
break;
case OAuthFlow.IMPLICIT:
_grantType = "implicit";
break;
case OAuthFlow.PASSWORD:
_grantType = "password";
break;*/
case OAuthFlow.APPLICATION:
_grantType = "client_credentials";
break;
default:
break;
Comment on lines +74 to +75

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The default case in this switch statement does nothing, which means if an unsupported OAuthFlow is provided, the _grantType field will remain uninitialized. This will likely cause a null value to be sent in the token request later. It's better to throw an exception for unsupported flows to fail early and clearly.

                default:
                    throw new NotSupportedException($"The OAuth flow '{flow}' is not supported.");

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This mustache template is unused

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gcatanese Q: Do we leave this in? I've only included it for completeness as part of the v7 generator upgrade

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO I'd keep only if it already existed in the project and you are just moving things. If this is coming in from the upgrade, then I'd remove.

}
}

/// <summary>
/// Creates an authentication parameter from an access token.
/// </summary>
/// <returns>An authentication parameter.</returns>
protected async ValueTask<Parameter> GetAuthenticationParameter()
{
var token = string.IsNullOrEmpty(Token) ? await GetToken().ConfigureAwait(false) : Token;
return new HeaderParameter(KnownHeaders.Authorization, token);
}

/// <summary>
/// Gets the token from the OAuth2 server.
/// </summary>
/// <returns>An authentication token.</returns>
async Task<string> GetToken()
{
var client = new RestClient(_tokenUrl, configureSerialization: serializerConfig => serializerConfig.UseSerializer(() => new CustomJsonCodec(_serializerSettings, _configuration)));

var request = new RestRequest();
if (!string.IsNullOrWhiteSpace(_token?.RefreshToken))
{
request.AddParameter("grant_type", "refresh_token")
.AddParameter("refresh_token", _token.RefreshToken);
}
else
{
request
.AddParameter("grant_type", _grantType)
.AddParameter("client_id", _clientId)
.AddParameter("client_secret", _clientSecret);
}
if (!string.IsNullOrEmpty(_scope))
{
request.AddParameter("scope", _scope);
}
_token = await client.PostAsync<TokenResponse>(request).ConfigureAwait(false);
// RFC6749 - token_type is case insensitive.
// RFC6750 - In Authorization header Bearer should be capitalized.
// Fix the capitalization irrespective of token_type casing.
switch (_token?.TokenType?.ToLower())
{
case "bearer":
return $"Bearer {_token.AccessToken}";
default:
return $"{_token?.TokenType} {_token?.AccessToken}";
}
}

/// <summary>
/// Retrieves the authentication token (creating a new one if necessary) and adds it to the current request
/// </summary>
/// <param name="client"></param>
/// <param name="request"></param>
/// <returns></returns>
public async ValueTask Authenticate(IRestClient client, RestRequest request)
=> request.AddOrUpdateParameter(await GetAuthenticationParameter().ConfigureAwait(false));
}
}
19 changes: 19 additions & 0 deletions templates-v7/csharp/auth/OAuthFlow.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{{>partial_header}}

namespace {{packageName}}.{{clientPackage}}.Auth
{
/// <summary>
/// Available flows for OAuth2 authentication
/// </summary>
public enum OAuthFlow
{
/// <summary>Authorization code flow</summary>
ACCESS_CODE,
/// <summary>Implicit flow</summary>
IMPLICIT,
/// <summary>Password flow</summary>
PASSWORD,
/// <summary>Client credentials flow</summary>
APPLICATION
}
}
24 changes: 24 additions & 0 deletions templates-v7/csharp/auth/TokenResponse.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{{>partial_header}}

using System;
using Newtonsoft.Json;

namespace {{packageName}}.{{clientPackage}}.Auth
{
class TokenResponse
{
[JsonProperty("token_type")]
public string TokenType { get; set; }
[JsonProperty("access_token")]
public string AccessToken { get; set; }
[JsonProperty("expires_in")]
public int? ExpiresIn { get; set; }
[JsonProperty("created")]
public DateTime? Created { get; set; }

[JsonProperty("refresh_token")]
public string{{nrt?}} RefreshToken { get; set; }

public DateTime? ExpiresAt => ExpiresIn == null ? null : Created?.AddSeconds(ExpiresIn.Value);
}
}
47 changes: 47 additions & 0 deletions templates-v7/csharp/libraries/generichost/ApiException.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// <auto-generated>
{{>partial_header}}

{{#nrt}}
#nullable enable

{{/nrt}}
using System;

namespace {{packageName}}.{{corePackageName}}.{{clientPackage}}
{
/// <summary>
/// API Exception
/// </summary>
{{>visibility}} class ApiException : Exception
{
/// <summary>
/// The reason the api request failed
/// </summary>
public string{{nrt?}} ReasonPhrase { get; }

/// <summary>
/// The HttpStatusCode
/// </summary>
public System.Net.HttpStatusCode StatusCode { get; }

/// <summary>
/// The raw data returned by the api
/// </summary>
public string RawContent { get; }

/// <summary>
/// Construct the ApiException from parts of the response
/// </summary>
/// <param name="reasonPhrase"></param>
/// <param name="statusCode"></param>
/// <param name="rawContent"></param>
public ApiException(string{{nrt?}} reasonPhrase, System.Net.HttpStatusCode statusCode, string rawContent) : base(reasonPhrase ?? rawContent)
{
ReasonPhrase = reasonPhrase;

StatusCode = statusCode;

RawContent = rawContent;
}
}
}
48 changes: 48 additions & 0 deletions templates-v7/csharp/libraries/generichost/ApiFactory.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using System;
using Microsoft.Extensions.DependencyInjection;

namespace {{packageName}}.{{corePackageName}}.{{clientPackage}}
{
/// <summary>
/// The factory interface for creating the services that can communicate with the {{packageName}} APIs.
/// </summary>
{{>visibility}} interface {{interfacePrefix}}ApiFactory
{
/// <summary>
/// A method to create an IApi of type IResult
/// </summary>
/// <typeparam name="IResult"></typeparam>
/// <returns></returns>
IResult Create<IResult>() where IResult : {{interfacePrefix}}{{packageName}}ApiService;
}

/// <summary>
/// The implementation of <see cref="{{interfacePrefix}}ApiFactory"/>.
/// </summary>
{{>visibility}} class ApiFactory : {{interfacePrefix}}ApiFactory
{
/// <summary>
/// The service provider
/// </summary>
public IServiceProvider Services { get; }

/// <summary>
/// Initializes a new instance of the <see cref="ApiFactory"/> class.
/// </summary>
/// <param name="services"></param>
public ApiFactory(IServiceProvider services)
{
Services = services;
}

/// <summary>
/// A method to create an IApi of type IResult
/// </summary>
/// <typeparam name="IResult"></typeparam>
/// <returns></returns>
public IResult Create<IResult>() where IResult : {{interfacePrefix}}{{packageName}}ApiService
{
return Services.GetRequiredService<IResult>();
}
}
}
Loading