diff --git a/src/OpenApiWeaver/ClientGenerator.SupportTypesEmitter.Cookie.cs b/src/OpenApiWeaver/ClientGenerator.SupportTypesEmitter.Cookie.cs new file mode 100644 index 0000000..8ec7db0 --- /dev/null +++ b/src/OpenApiWeaver/ClientGenerator.SupportTypesEmitter.Cookie.cs @@ -0,0 +1,30 @@ +namespace OpenApiWeaver; + +public sealed partial class ClientGenerator +{ + private static partial class SupportTypesEmitter + { + private static void EmitCookieHelpers(IndentedStringBuilder writer) + { + writer.AppendLine("internal static void AppendCookieParameter(StringBuilder builder, string name, string value)"); + writer.AppendLine("{"); + using (writer.PushIndent()) + { + writer.AppendLine("if (builder.Length > 0)"); + writer.AppendLine("{"); + using (writer.PushIndent()) + { + writer.AppendLine("builder.Append(\"; \");"); + } + + writer.AppendLine("}"); + writer.AppendLine(); + writer.AppendLine("builder.Append(name);"); + writer.AppendLine("builder.Append('=');"); + writer.AppendLine("builder.Append(Uri.EscapeDataString(value));"); + } + + writer.AppendLine("}"); + } + } +} diff --git a/src/OpenApiWeaver/ClientGenerator.SupportTypesEmitter.Exceptions.cs b/src/OpenApiWeaver/ClientGenerator.SupportTypesEmitter.Exceptions.cs new file mode 100644 index 0000000..cb60356 --- /dev/null +++ b/src/OpenApiWeaver/ClientGenerator.SupportTypesEmitter.Exceptions.cs @@ -0,0 +1,71 @@ +namespace OpenApiWeaver; + +public sealed partial class ClientGenerator +{ + private static partial class SupportTypesEmitter + { + private static void EmitExceptionTypes(IndentedStringBuilder writer) + { + writer.Append("public class ").Append(SupportTypeNames.Exception).AppendLine(" : Exception"); + writer.AppendLine("{"); + using (writer.PushIndent()) + { + writer.Append("public ").Append(SupportTypeNames.Exception).AppendLine("(int statusCode, string? reasonPhrase, string? contentType, string? responseContent, Exception? innerException = null)"); + writer.AppendLine(": base(CreateMessage(statusCode, reasonPhrase), innerException)"); + writer.AppendLine("{"); + using (writer.PushIndent()) + { + writer.AppendLine("StatusCode = statusCode;"); + writer.AppendLine("ReasonPhrase = reasonPhrase;"); + writer.AppendLine("ContentType = contentType;"); + writer.AppendLine("ResponseContent = responseContent;"); + } + + writer.AppendLine("}"); + writer.AppendLine(); + writer.AppendLine("public int StatusCode { get; }"); + writer.AppendLine(); + writer.AppendLine("public string? ReasonPhrase { get; }"); + writer.AppendLine(); + writer.AppendLine("public string? ContentType { get; }"); + writer.AppendLine(); + writer.AppendLine("public string? ResponseContent { get; }"); + writer.AppendLine(); + writer.AppendLine("private static string CreateMessage(int statusCode, string? reasonPhrase)"); + writer.AppendLine("{"); + using (writer.PushIndent()) + { + writer.AppendLine("return string.IsNullOrWhiteSpace(reasonPhrase)"); + using (writer.PushIndent()) + { + writer.AppendLine("? $\"The HTTP request failed with status code {statusCode}.\""); + writer.AppendLine(": $\"The HTTP request failed with status code {statusCode} ({reasonPhrase}).\";"); + } + } + + writer.AppendLine("}"); + } + + writer.AppendLine("}"); + writer.AppendLine(); + writer.Append("public class ").Append(SupportTypeNames.Exception).Append(" : ").Append(SupportTypeNames.Exception).AppendLine(); + writer.AppendLine("{"); + using (writer.PushIndent()) + { + writer.Append("public ").Append(SupportTypeNames.Exception).AppendLine("(int statusCode, string? reasonPhrase, string? contentType, string? responseContent, TError? error, Exception? innerException = null)"); + writer.AppendLine(": base(statusCode, reasonPhrase, contentType, responseContent, innerException)"); + writer.AppendLine("{"); + using (writer.PushIndent()) + { + writer.AppendLine("Error = error;"); + } + + writer.AppendLine("}"); + writer.AppendLine(); + writer.AppendLine("public TError? Error { get; }"); + } + + writer.AppendLine("}"); + } + } +} diff --git a/src/OpenApiWeaver/ClientGenerator.SupportTypesEmitter.Helpers.cs b/src/OpenApiWeaver/ClientGenerator.SupportTypesEmitter.Helpers.cs new file mode 100644 index 0000000..d85a10e --- /dev/null +++ b/src/OpenApiWeaver/ClientGenerator.SupportTypesEmitter.Helpers.cs @@ -0,0 +1,28 @@ +namespace OpenApiWeaver; + +public sealed partial class ClientGenerator +{ + private static partial class SupportTypesEmitter + { + private static void EmitHelperClass(IndentedStringBuilder writer) + { + writer.Append("internal static class ").Append(SupportTypeNames.ClientHelpers).AppendLine(); + writer.AppendLine("{"); + using (writer.PushIndent()) + { + writer.Append("internal static readonly JsonSerializerOptions ").Append(SupportTypeNames.SerializerOptions).AppendLine(" = new(JsonSerializerDefaults.Web);"); + writer.AppendLine(); + + EmitParameterFormattingHelpers(writer); + writer.AppendLine(); + EmitQueryHelpers(writer); + writer.AppendLine(); + EmitCookieHelpers(writer); + writer.AppendLine(); + EmitResponseHelpers(writer); + } + + writer.AppendLine("}"); + } + } +} diff --git a/src/OpenApiWeaver/ClientGenerator.SupportTypesEmitter.ParameterFormatting.cs b/src/OpenApiWeaver/ClientGenerator.SupportTypesEmitter.ParameterFormatting.cs new file mode 100644 index 0000000..fc5028a --- /dev/null +++ b/src/OpenApiWeaver/ClientGenerator.SupportTypesEmitter.ParameterFormatting.cs @@ -0,0 +1,79 @@ +namespace OpenApiWeaver; + +public sealed partial class ClientGenerator +{ + private static partial class SupportTypesEmitter + { + private static void EmitParameterFormattingHelpers(IndentedStringBuilder writer) + { + writer.AppendLine("internal static string FormatParameter(string? value) => value ?? string.Empty;"); + writer.AppendLine(); + writer.AppendLine("internal static string FormatParameter(JsonElement value) => value.ToString();"); + writer.AppendLine(); + writer.AppendLine("internal static string FormatParameter(bool value) => value ? \"true\" : \"false\";"); + writer.AppendLine(); + writer.AppendLine("internal static string FormatParameter(bool? value) => value is bool actual ? (actual ? \"true\" : \"false\") : string.Empty;"); + writer.AppendLine(); + writer.AppendLine("internal static string FormatParameter(T value) where T : struct, IFormattable"); + writer.AppendLine("{"); + using (writer.PushIndent()) + { + writer.AppendLine("return FormatFormattable(value);"); + } + + writer.AppendLine("}"); + writer.AppendLine(); + writer.AppendLine("internal static string FormatParameter(T? value) where T : struct, IFormattable"); + writer.AppendLine("{"); + using (writer.PushIndent()) + { + writer.AppendLine("return value is T actual ? FormatFormattable(actual) : string.Empty;"); + } + + writer.AppendLine("}"); + writer.AppendLine(); + writer.AppendLine("internal static string FormatParameter(object? value)"); + writer.AppendLine("{"); + using (writer.PushIndent()) + { + writer.AppendLine("return value switch"); + writer.AppendLine("{"); + using (writer.PushIndent()) + { + writer.AppendLine("null => string.Empty,"); + writer.AppendLine("bool boolean => boolean ? \"true\" : \"false\","); + writer.AppendLine("DateTimeOffset dateTimeOffset => dateTimeOffset.ToString(\"o\", CultureInfo.InvariantCulture),"); + writer.AppendLine("DateTime dateTime => dateTime.ToString(\"o\", CultureInfo.InvariantCulture),"); + writer.AppendLine("DateOnly dateOnly => dateOnly.ToString(\"yyyy-MM-dd\", CultureInfo.InvariantCulture),"); + writer.AppendLine("TimeOnly timeOnly => timeOnly.ToString(\"HH:mm:ss.FFFFFFF\", CultureInfo.InvariantCulture),"); + writer.AppendLine("IFormattable formattable => formattable.ToString(null, CultureInfo.InvariantCulture),"); + writer.AppendLine("_ => value.ToString() ?? string.Empty"); + } + + writer.AppendLine("};"); + } + + writer.AppendLine("}"); + writer.AppendLine(); + writer.AppendLine("private static string FormatFormattable(T value) where T : struct, IFormattable"); + writer.AppendLine("{"); + using (writer.PushIndent()) + { + writer.AppendLine("return value switch"); + writer.AppendLine("{"); + using (writer.PushIndent()) + { + writer.AppendLine("DateTimeOffset dateTimeOffset => dateTimeOffset.ToString(\"o\", CultureInfo.InvariantCulture),"); + writer.AppendLine("DateTime dateTime => dateTime.ToString(\"o\", CultureInfo.InvariantCulture),"); + writer.AppendLine("DateOnly dateOnly => dateOnly.ToString(\"yyyy-MM-dd\", CultureInfo.InvariantCulture),"); + writer.AppendLine("TimeOnly timeOnly => timeOnly.ToString(\"HH:mm:ss.FFFFFFF\", CultureInfo.InvariantCulture),"); + writer.AppendLine("_ => value.ToString(null, CultureInfo.InvariantCulture)"); + } + + writer.AppendLine("};"); + } + + writer.AppendLine("}"); + } + } +} diff --git a/src/OpenApiWeaver/ClientGenerator.SupportTypesEmitter.Query.cs b/src/OpenApiWeaver/ClientGenerator.SupportTypesEmitter.Query.cs new file mode 100644 index 0000000..3225a4c --- /dev/null +++ b/src/OpenApiWeaver/ClientGenerator.SupportTypesEmitter.Query.cs @@ -0,0 +1,76 @@ +namespace OpenApiWeaver; + +public sealed partial class ClientGenerator +{ + private static partial class SupportTypesEmitter + { + private static void EmitQueryHelpers(IndentedStringBuilder writer) + { + writer.AppendLine("internal static void AppendQueryParameter(StringBuilder builder, ref bool hasQuery, string name, string value)"); + writer.AppendLine("{"); + using (writer.PushIndent()) + { + writer.AppendLine("builder.Append(hasQuery ? '&' : '?');"); + writer.AppendLine("hasQuery = true;"); + writer.AppendLine("builder.Append(name);"); + writer.AppendLine("builder.Append('=');"); + writer.AppendLine("builder.Append(Uri.EscapeDataString(value));"); + } + + writer.AppendLine("}"); + writer.AppendLine(); + writer.AppendLine("internal static void AppendQueryParameters(StringBuilder builder, ref bool hasQuery, string name, IEnumerable values)"); + writer.AppendLine("{"); + using (writer.PushIndent()) + { + writer.AppendLine("foreach (var value in values)"); + writer.AppendLine("{"); + using (writer.PushIndent()) + { + writer.AppendLine("AppendQueryParameter(builder, ref hasQuery, name, FormatParameter(value));"); + } + + writer.AppendLine("}"); + } + + writer.AppendLine("}"); + writer.AppendLine(); + writer.AppendLine("internal static string FormatCollectionParameter(IEnumerable? values)"); + writer.AppendLine("{"); + using (writer.PushIndent()) + { + writer.AppendLine("if (values is null)"); + writer.AppendLine("{"); + using (writer.PushIndent()) + { + writer.AppendLine("return string.Empty;"); + } + + writer.AppendLine("}"); + writer.AppendLine(); + writer.AppendLine("var builder = new StringBuilder();"); + writer.AppendLine("foreach (var value in values)"); + writer.AppendLine("{"); + using (writer.PushIndent()) + { + writer.AppendLine("if (builder.Length > 0)"); + writer.AppendLine("{"); + using (writer.PushIndent()) + { + writer.AppendLine("builder.Append(',');"); + } + + writer.AppendLine("}"); + writer.AppendLine(); + writer.AppendLine("builder.Append(FormatParameter(value));"); + } + + writer.AppendLine("}"); + writer.AppendLine(); + writer.AppendLine("return builder.ToString();"); + } + + writer.AppendLine("}"); + } + } +} diff --git a/src/OpenApiWeaver/ClientGenerator.SupportTypesEmitter.Response.cs b/src/OpenApiWeaver/ClientGenerator.SupportTypesEmitter.Response.cs new file mode 100644 index 0000000..a013e79 --- /dev/null +++ b/src/OpenApiWeaver/ClientGenerator.SupportTypesEmitter.Response.cs @@ -0,0 +1,77 @@ +namespace OpenApiWeaver; + +public sealed partial class ClientGenerator +{ + private static partial class SupportTypesEmitter + { + private static void EmitResponseHelpers(IndentedStringBuilder writer) + { + writer.AppendLine("internal static bool HasJsonContentType(string? contentType)"); + writer.AppendLine("{"); + using (writer.PushIndent()) + { + writer.AppendLine("return !string.IsNullOrWhiteSpace(contentType) && contentType.Contains(\"json\", StringComparison.OrdinalIgnoreCase);"); + } + + writer.AppendLine("}"); + writer.AppendLine(); + writer.AppendLine("internal static bool ResponseMatchesStatusCode(int statusCode, string statusCodePattern)"); + writer.AppendLine("{"); + using (writer.PushIndent()) + { + writer.AppendLine("if (string.Equals(statusCodePattern, \"default\", StringComparison.OrdinalIgnoreCase))"); + writer.AppendLine("{"); + using (writer.PushIndent()) + { + writer.AppendLine("return true;"); + } + + writer.AppendLine("}"); + writer.AppendLine(); + writer.AppendLine("if (statusCodePattern.Length == 3"); + using (writer.PushIndent()) + { + writer.AppendLine("&& char.IsDigit(statusCodePattern[0])"); + writer.AppendLine("&& statusCodePattern[1] is 'X' or 'x'"); + writer.AppendLine("&& statusCodePattern[2] is 'X' or 'x')"); + } + + writer.AppendLine("{"); + using (writer.PushIndent()) + { + writer.AppendLine("var prefix = statusCodePattern[0] - '0';"); + writer.AppendLine("return statusCode >= prefix * 100 && statusCode < (prefix + 1) * 100;"); + } + + writer.AppendLine("}"); + writer.AppendLine(); + writer.AppendLine("return int.TryParse(statusCodePattern, out var expectedStatusCode) && statusCode == expectedStatusCode;"); + } + + writer.AppendLine("}"); + writer.AppendLine(); + writer.AppendLine("internal static T? DeserializeResponseContent(string? responseContent)"); + writer.AppendLine("{"); + using (writer.PushIndent()) + { + writer.Append("return DeserializeResponseContent(responseContent, ").Append(SupportTypeNames.SerializerOptions).AppendLine(");"); + } + + writer.AppendLine("}"); + writer.AppendLine(); + writer.AppendLine("internal static T? DeserializeResponseContent(string? responseContent, JsonSerializerOptions options)"); + writer.AppendLine("{"); + using (writer.PushIndent()) + { + writer.AppendLine("return string.IsNullOrWhiteSpace(responseContent)"); + using (writer.PushIndent()) + { + writer.AppendLine("? default"); + writer.AppendLine(": JsonSerializer.Deserialize(responseContent, options);"); + } + } + + writer.AppendLine("}"); + } + } +} diff --git a/src/OpenApiWeaver/ClientGenerator.SupportTypesEmitter.cs b/src/OpenApiWeaver/ClientGenerator.SupportTypesEmitter.cs index 43a80db..b175c21 100644 --- a/src/OpenApiWeaver/ClientGenerator.SupportTypesEmitter.cs +++ b/src/OpenApiWeaver/ClientGenerator.SupportTypesEmitter.cs @@ -4,7 +4,7 @@ namespace OpenApiWeaver; public sealed partial class ClientGenerator { - private static class SupportTypesEmitter + private static partial class SupportTypesEmitter { public static string Emit(string rootNamespace) { @@ -30,303 +30,5 @@ public static string Emit(string rootNamespace) EmitHelperClass(writer); return builder.ToString(); } - - private static void EmitExceptionTypes(IndentedStringBuilder writer) - { - writer.AppendLine("public class OpenApiException : Exception"); - writer.AppendLine("{"); - using (writer.PushIndent()) - { - writer.AppendLine("public OpenApiException(int statusCode, string? reasonPhrase, string? contentType, string? responseContent, Exception? innerException = null)"); - writer.AppendLine(": base(CreateMessage(statusCode, reasonPhrase), innerException)"); - writer.AppendLine("{"); - using (writer.PushIndent()) - { - writer.AppendLine("StatusCode = statusCode;"); - writer.AppendLine("ReasonPhrase = reasonPhrase;"); - writer.AppendLine("ContentType = contentType;"); - writer.AppendLine("ResponseContent = responseContent;"); - } - - writer.AppendLine("}"); - writer.AppendLine(); - writer.AppendLine("public int StatusCode { get; }"); - writer.AppendLine(); - writer.AppendLine("public string? ReasonPhrase { get; }"); - writer.AppendLine(); - writer.AppendLine("public string? ContentType { get; }"); - writer.AppendLine(); - writer.AppendLine("public string? ResponseContent { get; }"); - writer.AppendLine(); - writer.AppendLine("private static string CreateMessage(int statusCode, string? reasonPhrase)"); - writer.AppendLine("{"); - using (writer.PushIndent()) - { - writer.AppendLine("return string.IsNullOrWhiteSpace(reasonPhrase)"); - using (writer.PushIndent()) - { - writer.AppendLine("? $\"The HTTP request failed with status code {statusCode}.\""); - writer.AppendLine(": $\"The HTTP request failed with status code {statusCode} ({reasonPhrase}).\";"); - } - } - - writer.AppendLine("}"); - } - - writer.AppendLine("}"); - writer.AppendLine(); - writer.AppendLine("public class OpenApiException : OpenApiException"); - writer.AppendLine("{"); - using (writer.PushIndent()) - { - writer.AppendLine("public OpenApiException(int statusCode, string? reasonPhrase, string? contentType, string? responseContent, TError? error, Exception? innerException = null)"); - writer.AppendLine(": base(statusCode, reasonPhrase, contentType, responseContent, innerException)"); - writer.AppendLine("{"); - using (writer.PushIndent()) - { - writer.AppendLine("Error = error;"); - } - - writer.AppendLine("}"); - writer.AppendLine(); - writer.AppendLine("public TError? Error { get; }"); - } - - writer.AppendLine("}"); - } - - private static void EmitHelperClass(IndentedStringBuilder writer) - { - writer.AppendLine("internal static class OpenApiClientHelpers"); - writer.AppendLine("{"); - using (writer.PushIndent()) - { - writer.AppendLine("internal static readonly JsonSerializerOptions SerializerOptions = new(JsonSerializerDefaults.Web);"); - writer.AppendLine(); - writer.AppendLine("internal static string FormatParameter(string? value) => value ?? string.Empty;"); - writer.AppendLine(); - writer.AppendLine("internal static string FormatParameter(JsonElement value) => value.ToString();"); - writer.AppendLine(); - writer.AppendLine("internal static string FormatParameter(bool value) => value ? \"true\" : \"false\";"); - writer.AppendLine(); - writer.AppendLine("internal static string FormatParameter(bool? value) => value is bool actual ? (actual ? \"true\" : \"false\") : string.Empty;"); - writer.AppendLine(); - writer.AppendLine("internal static string FormatParameter(T value) where T : struct, IFormattable"); - writer.AppendLine("{"); - using (writer.PushIndent()) - { - writer.AppendLine("return FormatFormattable(value);"); - } - - writer.AppendLine("}"); - writer.AppendLine(); - writer.AppendLine("internal static string FormatParameter(T? value) where T : struct, IFormattable"); - writer.AppendLine("{"); - using (writer.PushIndent()) - { - writer.AppendLine("return value is T actual ? FormatFormattable(actual) : string.Empty;"); - } - - writer.AppendLine("}"); - writer.AppendLine(); - writer.AppendLine("internal static string FormatParameter(object? value)"); - writer.AppendLine("{"); - using (writer.PushIndent()) - { - writer.AppendLine("return value switch"); - writer.AppendLine("{"); - using (writer.PushIndent()) - { - writer.AppendLine("null => string.Empty,"); - writer.AppendLine("bool boolean => boolean ? \"true\" : \"false\","); - writer.AppendLine("DateTimeOffset dateTimeOffset => dateTimeOffset.ToString(\"o\", CultureInfo.InvariantCulture),"); - writer.AppendLine("DateTime dateTime => dateTime.ToString(\"o\", CultureInfo.InvariantCulture),"); - writer.AppendLine("DateOnly dateOnly => dateOnly.ToString(\"yyyy-MM-dd\", CultureInfo.InvariantCulture),"); - writer.AppendLine("TimeOnly timeOnly => timeOnly.ToString(\"HH:mm:ss.FFFFFFF\", CultureInfo.InvariantCulture),"); - writer.AppendLine("IFormattable formattable => formattable.ToString(null, CultureInfo.InvariantCulture),"); - writer.AppendLine("_ => value.ToString() ?? string.Empty"); - } - - writer.AppendLine("};"); - } - - writer.AppendLine("}"); - writer.AppendLine(); - writer.AppendLine("private static string FormatFormattable(T value) where T : struct, IFormattable"); - writer.AppendLine("{"); - using (writer.PushIndent()) - { - writer.AppendLine("return value switch"); - writer.AppendLine("{"); - using (writer.PushIndent()) - { - writer.AppendLine("DateTimeOffset dateTimeOffset => dateTimeOffset.ToString(\"o\", CultureInfo.InvariantCulture),"); - writer.AppendLine("DateTime dateTime => dateTime.ToString(\"o\", CultureInfo.InvariantCulture),"); - writer.AppendLine("DateOnly dateOnly => dateOnly.ToString(\"yyyy-MM-dd\", CultureInfo.InvariantCulture),"); - writer.AppendLine("TimeOnly timeOnly => timeOnly.ToString(\"HH:mm:ss.FFFFFFF\", CultureInfo.InvariantCulture),"); - writer.AppendLine("_ => value.ToString(null, CultureInfo.InvariantCulture)"); - } - - writer.AppendLine("};"); - } - - writer.AppendLine("}"); - writer.AppendLine(); - writer.AppendLine("internal static void AppendQueryParameter(StringBuilder builder, ref bool hasQuery, string name, string value)"); - writer.AppendLine("{"); - using (writer.PushIndent()) - { - writer.AppendLine("builder.Append(hasQuery ? '&' : '?');"); - writer.AppendLine("hasQuery = true;"); - writer.AppendLine("builder.Append(name);"); - writer.AppendLine("builder.Append('=');"); - writer.AppendLine("builder.Append(Uri.EscapeDataString(value));"); - } - - writer.AppendLine("}"); - writer.AppendLine(); - writer.AppendLine("internal static void AppendQueryParameters(StringBuilder builder, ref bool hasQuery, string name, IEnumerable values)"); - writer.AppendLine("{"); - using (writer.PushIndent()) - { - writer.AppendLine("foreach (var value in values)"); - writer.AppendLine("{"); - using (writer.PushIndent()) - { - writer.AppendLine("AppendQueryParameter(builder, ref hasQuery, name, FormatParameter(value));"); - } - - writer.AppendLine("}"); - } - - writer.AppendLine("}"); - writer.AppendLine(); - writer.AppendLine("internal static string FormatCollectionParameter(IEnumerable? values)"); - writer.AppendLine("{"); - using (writer.PushIndent()) - { - writer.AppendLine("if (values is null)"); - writer.AppendLine("{"); - using (writer.PushIndent()) - { - writer.AppendLine("return string.Empty;"); - } - - writer.AppendLine("}"); - writer.AppendLine(); - writer.AppendLine("var builder = new StringBuilder();"); - writer.AppendLine("foreach (var value in values)"); - writer.AppendLine("{"); - using (writer.PushIndent()) - { - writer.AppendLine("if (builder.Length > 0)"); - writer.AppendLine("{"); - using (writer.PushIndent()) - { - writer.AppendLine("builder.Append(',');"); - } - - writer.AppendLine("}"); - writer.AppendLine(); - writer.AppendLine("builder.Append(FormatParameter(value));"); - } - - writer.AppendLine("}"); - writer.AppendLine(); - writer.AppendLine("return builder.ToString();"); - } - - writer.AppendLine("}"); - writer.AppendLine(); - writer.AppendLine("internal static void AppendCookieParameter(StringBuilder builder, string name, string value)"); - writer.AppendLine("{"); - using (writer.PushIndent()) - { - writer.AppendLine("if (builder.Length > 0)"); - writer.AppendLine("{"); - using (writer.PushIndent()) - { - writer.AppendLine("builder.Append(\"; \");"); - } - - writer.AppendLine("}"); - writer.AppendLine(); - writer.AppendLine("builder.Append(name);"); - writer.AppendLine("builder.Append('=');"); - writer.AppendLine("builder.Append(Uri.EscapeDataString(value));"); - } - - writer.AppendLine("}"); - writer.AppendLine(); - writer.AppendLine("internal static bool HasJsonContentType(string? contentType)"); - writer.AppendLine("{"); - using (writer.PushIndent()) - { - writer.AppendLine("return !string.IsNullOrWhiteSpace(contentType) && contentType.Contains(\"json\", StringComparison.OrdinalIgnoreCase);"); - } - - writer.AppendLine("}"); - writer.AppendLine(); - writer.AppendLine("internal static bool ResponseMatchesStatusCode(int statusCode, string statusCodePattern)"); - writer.AppendLine("{"); - using (writer.PushIndent()) - { - writer.AppendLine("if (string.Equals(statusCodePattern, \"default\", StringComparison.OrdinalIgnoreCase))"); - writer.AppendLine("{"); - using (writer.PushIndent()) - { - writer.AppendLine("return true;"); - } - - writer.AppendLine("}"); - writer.AppendLine(); - writer.AppendLine("if (statusCodePattern.Length == 3"); - using (writer.PushIndent()) - { - writer.AppendLine("&& char.IsDigit(statusCodePattern[0])"); - writer.AppendLine("&& statusCodePattern[1] is 'X' or 'x'"); - writer.AppendLine("&& statusCodePattern[2] is 'X' or 'x')"); - } - - writer.AppendLine("{"); - using (writer.PushIndent()) - { - writer.AppendLine("var prefix = statusCodePattern[0] - '0';"); - writer.AppendLine("return statusCode >= prefix * 100 && statusCode < (prefix + 1) * 100;"); - } - - writer.AppendLine("}"); - writer.AppendLine(); - writer.AppendLine("return int.TryParse(statusCodePattern, out var expectedStatusCode) && statusCode == expectedStatusCode;"); - } - - writer.AppendLine("}"); - writer.AppendLine(); - writer.AppendLine("internal static T? DeserializeResponseContent(string? responseContent)"); - writer.AppendLine("{"); - using (writer.PushIndent()) - { - writer.AppendLine("return DeserializeResponseContent(responseContent, SerializerOptions);"); - } - - writer.AppendLine("}"); - writer.AppendLine(); - writer.AppendLine("internal static T? DeserializeResponseContent(string? responseContent, JsonSerializerOptions options)"); - writer.AppendLine("{"); - using (writer.PushIndent()) - { - writer.AppendLine("return string.IsNullOrWhiteSpace(responseContent)"); - using (writer.PushIndent()) - { - writer.AppendLine("? default"); - writer.AppendLine(": JsonSerializer.Deserialize(responseContent, options);"); - } - } - - writer.AppendLine("}"); - } - - writer.AppendLine("}"); - } } } diff --git a/src/OpenApiWeaver/ClientGenerator.Transformer.Schemas.cs b/src/OpenApiWeaver/ClientGenerator.Transformer.Schemas.cs index 1d98cb5..f7096c3 100644 --- a/src/OpenApiWeaver/ClientGenerator.Transformer.Schemas.cs +++ b/src/OpenApiWeaver/ClientGenerator.Transformer.Schemas.cs @@ -9,8 +9,7 @@ private sealed partial class Transformer private void RegisterSchemaNames() { _schemaCatalog.ReserveTypeName(_clientName); - _schemaCatalog.ReserveTypeName("OpenApiClientHelpers"); - _schemaCatalog.ReserveTypeName("OpenApiException"); + SupportTypeNames.ReserveTypeNames(_schemaCatalog); if (_document.Components?.Schemas is null) { diff --git a/src/OpenApiWeaver/ClientGenerator.Transformer.Tags.cs b/src/OpenApiWeaver/ClientGenerator.Transformer.Tags.cs index 67a69bb..852cd3b 100644 --- a/src/OpenApiWeaver/ClientGenerator.Transformer.Tags.cs +++ b/src/OpenApiWeaver/ClientGenerator.Transformer.Tags.cs @@ -48,8 +48,7 @@ private List BuildTagGroups(IReadOnlyList secur var reservedClassNames = _schemaCatalog.ComponentTypeNames .Concat(_schemaCatalog.InlineSchemas.Where(static schema => schema.ParentTypeName is null).Select(static schema => schema.DeclaredTypeName)) .Append(_clientName) - .Append("OpenApiClientHelpers") - .Append("OpenApiException"); + .Concat(SupportTypeNames.ReservedTypeNames); if (!string.IsNullOrWhiteSpace(serializerOptionsTypeName)) { reservedClassNames = reservedClassNames.Append(serializerOptionsTypeName); diff --git a/src/OpenApiWeaver/ClientGenerator.Transformer.cs b/src/OpenApiWeaver/ClientGenerator.Transformer.cs index d35c18c..1663dce 100644 --- a/src/OpenApiWeaver/ClientGenerator.Transformer.cs +++ b/src/OpenApiWeaver/ClientGenerator.Transformer.cs @@ -37,7 +37,7 @@ public ClientModel Transform() var securitySchemes = BuildSecuritySchemes(); var schemas = BuildSchemaDefinitions(); var serializerOptionsTypeName = schemas.Any(static schema => schema.Properties.Any(static property => property.ReadOnly || property.WriteOnly)) - ? _schemaCatalog.AllocateTypeName(parentTypeName: null, _clientName + "JsonSerializerOptions") + ? _schemaCatalog.AllocateTypeName(parentTypeName: null, SupportTypeNames.CreateSerializerOptionsTypeName(_clientName)) : string.Empty; var tagGroups = BuildTagGroups(securitySchemes, serializerOptionsTypeName); diff --git a/src/OpenApiWeaver/JsonSerializerOptionsEmitter.cs b/src/OpenApiWeaver/JsonSerializerOptionsEmitter.cs index a172dc9..1be5c83 100644 --- a/src/OpenApiWeaver/JsonSerializerOptionsEmitter.cs +++ b/src/OpenApiWeaver/JsonSerializerOptionsEmitter.cs @@ -8,7 +8,7 @@ public static string GetOptionsExpression(ClientModel model, JsonSerializerDirec { if (!model.HasDirectionalSchemaProperties || direction == JsonSerializerDirection.Neutral) { - return "OpenApiClientHelpers.SerializerOptions"; + return SupportTypeNames.DefaultSerializerOptionsExpression; } return direction == JsonSerializerDirection.Request diff --git a/src/OpenApiWeaver/OperationEmitter.ErrorResponses.cs b/src/OpenApiWeaver/OperationEmitter.ErrorResponses.cs new file mode 100644 index 0000000..ac12bec --- /dev/null +++ b/src/OpenApiWeaver/OperationEmitter.ErrorResponses.cs @@ -0,0 +1,81 @@ +using static OpenApiWeaver.CSharpCodeEmissionUtilities; + +namespace OpenApiWeaver; + +internal sealed partial class OperationEmitter +{ + private void EmitErrorResponseHandling(IndentedStringBuilder writer, OperationGroupItem operation) + { + writer.AppendLine("var statusCode = (int)response.StatusCode;"); + writer.AppendLine("var contentType = response.Content?.Headers?.ContentType?.MediaType;"); + writer.AppendLine("var responseContent = response.Content is null"); + using (writer.PushIndent()) + { + writer.AppendLine("? null"); + writer.AppendLine(": await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);"); + } + + foreach (var errorResponse in operation.ErrorResponses) + { + writer.Append("if (OpenApiClientHelpers.ResponseMatchesStatusCode(statusCode, \"").Append(EscapeStringLiteral(errorResponse.StatusCodePattern)).AppendLine("\"))"); + writer.AppendLine("{"); + using (writer.PushIndent()) + { + EmitTypedErrorResponseHandling(writer, errorResponse.Response); + } + + writer.AppendLine("}"); + } + + writer.AppendLine("throw new OpenApiException(statusCode, response.ReasonPhrase, contentType, responseContent);"); + } + + private void EmitTypedErrorResponseHandling(IndentedStringBuilder writer, ResponseInfo response) + { + var errorTypeName = response.Type?.NonNullableCSharpTypeName ?? string.Empty; + switch (response.Kind) + { + case ResponseKind.Json: + writer.AppendLine("if (string.IsNullOrWhiteSpace(contentType) || OpenApiClientHelpers.HasJsonContentType(contentType))"); + writer.AppendLine("{"); + using (writer.PushIndent()) + { + writer.AppendLine("try"); + writer.AppendLine("{"); + using (writer.PushIndent()) + { + var serializerOptions = GetSerializerOptionsExpression(JsonSerializerDirection.Response); + writer.Append("var error = OpenApiClientHelpers.DeserializeResponseContent<").Append(errorTypeName).Append(">(responseContent"); + if (!string.Equals(serializerOptions, SupportTypeNames.DefaultSerializerOptionsExpression, StringComparison.Ordinal)) + { + writer.Append(", ").Append(serializerOptions); + } + + writer.AppendLine(");"); + writer.Append("throw new OpenApiException<").Append(errorTypeName).AppendLine(">(statusCode, response.ReasonPhrase, contentType, responseContent, error);"); + } + + writer.AppendLine("}"); + writer.AppendLine("catch (JsonException exception)"); + writer.AppendLine("{"); + using (writer.PushIndent()) + { + writer.AppendLine("throw new OpenApiException(statusCode, response.ReasonPhrase, contentType, responseContent, exception);"); + } + + writer.AppendLine("}"); + } + + writer.AppendLine("}"); + return; + case ResponseKind.String: + writer.Append("throw new OpenApiException<").Append(errorTypeName).AppendLine(">(statusCode, response.ReasonPhrase, contentType, responseContent, responseContent);"); + return; + case ResponseKind.Binary: + case ResponseKind.None: + // No typed deserialization: the untyped OpenApiException emitted after the + // dispatch loop handles these response shapes. + return; + } + } +} diff --git a/src/OpenApiWeaver/OperationEmitter.Parameters.cs b/src/OpenApiWeaver/OperationEmitter.Parameters.cs new file mode 100644 index 0000000..58b5a74 --- /dev/null +++ b/src/OpenApiWeaver/OperationEmitter.Parameters.cs @@ -0,0 +1,98 @@ +using static OpenApiWeaver.CSharpCodeEmissionUtilities; + +namespace OpenApiWeaver; + +internal sealed partial class OperationEmitter +{ + private void EmitRouteTemplate(IndentedStringBuilder writer, string route, IReadOnlyList pathParameters) + { + var parameterLookup = pathParameters + .Where(static parameter => !string.IsNullOrEmpty(parameter.WireName)) + .ToDictionary(static parameter => parameter.WireName, StringComparer.Ordinal); + + var startIndex = 0; + while (startIndex < route.Length) + { + var openBraceIndex = route.IndexOf('{', startIndex); + if (openBraceIndex < 0) + { + EmitRouteLiteral(writer, route.Substring(startIndex)); + break; + } + + var closeBraceIndex = route.IndexOf('}', openBraceIndex + 1); + if (closeBraceIndex < 0) + { + EmitRouteLiteral(writer, route.Substring(startIndex)); + break; + } + + EmitRouteLiteral(writer, route.Substring(startIndex, openBraceIndex - startIndex)); + + var parameterName = route.Substring(openBraceIndex + 1, closeBraceIndex - openBraceIndex - 1); + if (parameterLookup.TryGetValue(parameterName, out var parameter)) + { + writer.Append("pathBuilder.Append(Uri.EscapeDataString("); + EmitParameterValue(writer, parameter); + writer.AppendLine("));"); + } + else + { + EmitRouteLiteral(writer, route.Substring(openBraceIndex, closeBraceIndex - openBraceIndex + 1)); + } + + startIndex = closeBraceIndex + 1; + } + } + + private static void EmitRouteLiteral(IndentedStringBuilder writer, string segment) + { + if (segment.Length == 0) + { + return; + } + + writer.Append("pathBuilder.Append(\"").Append(EscapeStringLiteral(segment)).AppendLine("\");"); + } + + private static string NormalizeRelativeRoute(string route) + { + return route.TrimStart('/'); + } + + private static void EmitQueryParameterAppend(IndentedStringBuilder writer, ParameterInfo parameter) + { + if (parameter.IsArray) + { + writer.Append("OpenApiClientHelpers.AppendQueryParameters(pathBuilder, ref hasQuery, \"").Append(EscapeStringLiteral(parameter.WireName)).Append("\", ").Append(parameter.ParameterName).AppendLine(");"); + return; + } + + writer.Append("OpenApiClientHelpers.AppendQueryParameter(pathBuilder, ref hasQuery, \"").Append(EscapeStringLiteral(parameter.WireName)).Append("\", OpenApiClientHelpers.FormatParameter(").Append(parameter.ParameterName).AppendLine("));"); + } + + private static void EmitHeaderParameterAppend(IndentedStringBuilder writer, ParameterInfo parameter) + { + writer.Append("request.Headers.TryAddWithoutValidation(\"").Append(EscapeStringLiteral(parameter.WireName)).Append("\", "); + EmitParameterValue(writer, parameter); + writer.AppendLine(");"); + } + + private static void EmitCookieParameterAppend(IndentedStringBuilder writer, ParameterInfo parameter) + { + writer.Append("OpenApiClientHelpers.AppendCookieParameter(cookieBuilder, \"").Append(EscapeStringLiteral(parameter.WireName)).Append("\", "); + EmitParameterValue(writer, parameter); + writer.AppendLine(");"); + } + + private static void EmitParameterValue(IndentedStringBuilder writer, ParameterInfo parameter) + { + if (parameter.IsArray) + { + writer.Append("OpenApiClientHelpers.FormatCollectionParameter(").Append(parameter.ParameterName).Append(')'); + return; + } + + writer.Append("OpenApiClientHelpers.FormatParameter(").Append(parameter.ParameterName).Append(')'); + } +} diff --git a/src/OpenApiWeaver/OperationEmitter.Security.cs b/src/OpenApiWeaver/OperationEmitter.Security.cs new file mode 100644 index 0000000..c18690a --- /dev/null +++ b/src/OpenApiWeaver/OperationEmitter.Security.cs @@ -0,0 +1,111 @@ +namespace OpenApiWeaver; + +internal sealed partial class OperationEmitter +{ + private void EmitSecurityRequirementSelection(IndentedStringBuilder writer, OperationGroupItem operation) + { + if (operation.SecurityRequirements is not { Count: > 0 } securityRequirements + || !securityRequirements.Any(static requirement => requirement.Schemes.Count > 0)) + { + return; + } + + writer.AppendLine("var securityRequirementIndex = 0;"); + for (var i = 0; i < securityRequirements.Count; i++) + { + writer.Append("if (securityRequirementIndex == 0"); + var requirement = securityRequirements[i]; + foreach (var scheme in requirement.Schemes) + { + writer.Append(" && ").Append(scheme.FieldName).Append(" is not null"); + } + + writer.AppendLine(")"); + writer.AppendLine("{"); + using (writer.PushIndent()) + { + writer.Append("securityRequirementIndex = ").Append((i + 1).ToString(System.Globalization.CultureInfo.InvariantCulture)).AppendLine(";"); + } + + writer.AppendLine("}"); + } + } + + private void EmitSecuritySchemeBlock(IndentedStringBuilder writer, OperationGroupItem operation, SecuritySchemeBinding securityScheme, Action emitBody) + { + if (operation.SecurityRequirements is null) + { + writer.Append("if (").Append(securityScheme.FieldName).AppendLine(" is not null)"); + writer.AppendLine("{"); + using (writer.PushIndent()) + { + emitBody(); + } + + writer.AppendLine("}"); + return; + } + + var matchingRequirements = new List(); + for (var i = 0; i < operation.SecurityRequirements.Count; i++) + { + if (operation.SecurityRequirements[i].Schemes.Contains(securityScheme)) + { + matchingRequirements.Add(i + 1); + } + } + + if (matchingRequirements.Count == 0) + { + return; + } + + writer.Append("if ("); + for (var i = 0; i < matchingRequirements.Count; i++) + { + if (i > 0) + { + writer.Append(" || "); + } + + writer.Append("securityRequirementIndex == ").Append(matchingRequirements[i].ToString(System.Globalization.CultureInfo.InvariantCulture)); + } + + writer.AppendLine(")"); + writer.AppendLine("{"); + using (writer.PushIndent()) + { + emitBody(); + } + + writer.AppendLine("}"); + } + + private List GetOperationSecuritySchemes(OperationGroupItem operation, SecuritySchemeLocation location) + => GetOperationSecuritySchemes(operation, static (scheme, state) => scheme.Location == state, location); + + private List GetOperationSecuritySchemesExcept(OperationGroupItem operation, SecuritySchemeLocation location) + => GetOperationSecuritySchemes(operation, static (scheme, state) => scheme.Location != state, location); + + private List GetOperationSecuritySchemes( + OperationGroupItem operation, + Func predicate, + TState state) + { + var schemes = operation.SecurityRequirements is null + ? _model.SecuritySchemes + : operation.SecurityRequirements.SelectMany(static requirement => requirement.Schemes).ToList(); + + var result = new List(); + var usedSchemeKeys = new HashSet(StringComparer.Ordinal); + foreach (var scheme in schemes) + { + if (predicate(scheme, state) && usedSchemeKeys.Add(scheme.SchemeKey)) + { + result.Add(scheme); + } + } + + return result; + } +} diff --git a/src/OpenApiWeaver/OperationEmitter.Serialization.cs b/src/OpenApiWeaver/OperationEmitter.Serialization.cs new file mode 100644 index 0000000..9a189b1 --- /dev/null +++ b/src/OpenApiWeaver/OperationEmitter.Serialization.cs @@ -0,0 +1,7 @@ +namespace OpenApiWeaver; + +internal sealed partial class OperationEmitter +{ + private string GetSerializerOptionsExpression(JsonSerializerDirection direction) + => JsonSerializerOptionsEmitter.GetOptionsExpression(_model, direction); +} diff --git a/src/OpenApiWeaver/OperationEmitter.cs b/src/OpenApiWeaver/OperationEmitter.cs index f5ceac5..5f35ee8 100644 --- a/src/OpenApiWeaver/OperationEmitter.cs +++ b/src/OpenApiWeaver/OperationEmitter.cs @@ -2,8 +2,9 @@ namespace OpenApiWeaver; -internal sealed class OperationEmitter(ClientModel model) +internal sealed partial class OperationEmitter(ClientModel model) { + private readonly ClientModel _model = model; private readonly OperationRequestBodyEmitter _requestBodyEmitter = new(model); public void Emit(IndentedStringBuilder writer, OperationGroupItem operation) @@ -274,281 +275,4 @@ public void Emit(IndentedStringBuilder writer, OperationGroupItem operation) writer.AppendLine("}"); } - - private void EmitErrorResponseHandling(IndentedStringBuilder writer, OperationGroupItem operation) - { - writer.AppendLine("var statusCode = (int)response.StatusCode;"); - writer.AppendLine("var contentType = response.Content?.Headers?.ContentType?.MediaType;"); - writer.AppendLine("var responseContent = response.Content is null"); - using (writer.PushIndent()) - { - writer.AppendLine("? null"); - writer.AppendLine(": await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);"); - } - - foreach (var errorResponse in operation.ErrorResponses) - { - writer.Append("if (OpenApiClientHelpers.ResponseMatchesStatusCode(statusCode, \"").Append(EscapeStringLiteral(errorResponse.StatusCodePattern)).AppendLine("\"))"); - writer.AppendLine("{"); - using (writer.PushIndent()) - { - EmitTypedErrorResponseHandling(writer, errorResponse.Response); - } - - writer.AppendLine("}"); - } - - writer.AppendLine("throw new OpenApiException(statusCode, response.ReasonPhrase, contentType, responseContent);"); - } - - private void EmitTypedErrorResponseHandling(IndentedStringBuilder writer, ResponseInfo response) - { - var errorTypeName = response.Type?.NonNullableCSharpTypeName ?? string.Empty; - switch (response.Kind) - { - case ResponseKind.Json: - writer.AppendLine("if (string.IsNullOrWhiteSpace(contentType) || OpenApiClientHelpers.HasJsonContentType(contentType))"); - writer.AppendLine("{"); - using (writer.PushIndent()) - { - writer.AppendLine("try"); - writer.AppendLine("{"); - using (writer.PushIndent()) - { - var serializerOptions = GetSerializerOptionsExpression(JsonSerializerDirection.Response); - writer.Append("var error = OpenApiClientHelpers.DeserializeResponseContent<").Append(errorTypeName).Append(">(responseContent"); - if (!string.Equals(serializerOptions, "OpenApiClientHelpers.SerializerOptions", StringComparison.Ordinal)) - { - writer.Append(", ").Append(serializerOptions); - } - - writer.AppendLine(");"); - writer.Append("throw new OpenApiException<").Append(errorTypeName).AppendLine(">(statusCode, response.ReasonPhrase, contentType, responseContent, error);"); - } - - writer.AppendLine("}"); - writer.AppendLine("catch (JsonException exception)"); - writer.AppendLine("{"); - using (writer.PushIndent()) - { - writer.AppendLine("throw new OpenApiException(statusCode, response.ReasonPhrase, contentType, responseContent, exception);"); - } - - writer.AppendLine("}"); - } - - writer.AppendLine("}"); - return; - case ResponseKind.String: - writer.Append("throw new OpenApiException<").Append(errorTypeName).AppendLine(">(statusCode, response.ReasonPhrase, contentType, responseContent, responseContent);"); - return; - case ResponseKind.Binary: - case ResponseKind.None: - // No typed deserialization: the untyped OpenApiException emitted after the - // dispatch loop handles these response shapes. - return; - } - } - - private void EmitRouteTemplate(IndentedStringBuilder writer, string route, IReadOnlyList pathParameters) - { - var parameterLookup = pathParameters - .Where(static parameter => !string.IsNullOrEmpty(parameter.WireName)) - .ToDictionary(static parameter => parameter.WireName, StringComparer.Ordinal); - - var startIndex = 0; - while (startIndex < route.Length) - { - var openBraceIndex = route.IndexOf('{', startIndex); - if (openBraceIndex < 0) - { - EmitRouteLiteral(writer, route.Substring(startIndex)); - break; - } - - var closeBraceIndex = route.IndexOf('}', openBraceIndex + 1); - if (closeBraceIndex < 0) - { - EmitRouteLiteral(writer, route.Substring(startIndex)); - break; - } - - EmitRouteLiteral(writer, route.Substring(startIndex, openBraceIndex - startIndex)); - - var parameterName = route.Substring(openBraceIndex + 1, closeBraceIndex - openBraceIndex - 1); - if (parameterLookup.TryGetValue(parameterName, out var parameter)) - { - writer.Append("pathBuilder.Append(Uri.EscapeDataString("); - EmitParameterValue(writer, parameter); - writer.AppendLine("));"); - } - else - { - EmitRouteLiteral(writer, route.Substring(openBraceIndex, closeBraceIndex - openBraceIndex + 1)); - } - - startIndex = closeBraceIndex + 1; - } - } - - private static void EmitRouteLiteral(IndentedStringBuilder writer, string segment) - { - if (segment.Length == 0) - { - return; - } - - writer.Append("pathBuilder.Append(\"").Append(EscapeStringLiteral(segment)).AppendLine("\");"); - } - - private static string NormalizeRelativeRoute(string route) - { - return route.TrimStart('/'); - } - - private static void EmitQueryParameterAppend(IndentedStringBuilder writer, ParameterInfo parameter) - { - if (parameter.IsArray) - { - writer.Append("OpenApiClientHelpers.AppendQueryParameters(pathBuilder, ref hasQuery, \"").Append(EscapeStringLiteral(parameter.WireName)).Append("\", ").Append(parameter.ParameterName).AppendLine(");"); - return; - } - - writer.Append("OpenApiClientHelpers.AppendQueryParameter(pathBuilder, ref hasQuery, \"").Append(EscapeStringLiteral(parameter.WireName)).Append("\", OpenApiClientHelpers.FormatParameter(").Append(parameter.ParameterName).AppendLine("));"); - } - - private static void EmitHeaderParameterAppend(IndentedStringBuilder writer, ParameterInfo parameter) - { - writer.Append("request.Headers.TryAddWithoutValidation(\"").Append(EscapeStringLiteral(parameter.WireName)).Append("\", "); - EmitParameterValue(writer, parameter); - writer.AppendLine(");"); - } - - private static void EmitCookieParameterAppend(IndentedStringBuilder writer, ParameterInfo parameter) - { - writer.Append("OpenApiClientHelpers.AppendCookieParameter(cookieBuilder, \"").Append(EscapeStringLiteral(parameter.WireName)).Append("\", "); - EmitParameterValue(writer, parameter); - writer.AppendLine(");"); - } - - private static void EmitParameterValue(IndentedStringBuilder writer, ParameterInfo parameter) - { - if (parameter.IsArray) - { - writer.Append("OpenApiClientHelpers.FormatCollectionParameter(").Append(parameter.ParameterName).Append(')'); - return; - } - - writer.Append("OpenApiClientHelpers.FormatParameter(").Append(parameter.ParameterName).Append(')'); - } - - private void EmitSecurityRequirementSelection(IndentedStringBuilder writer, OperationGroupItem operation) - { - if (operation.SecurityRequirements is not { Count: > 0 } securityRequirements - || !securityRequirements.Any(static requirement => requirement.Schemes.Count > 0)) - { - return; - } - - writer.AppendLine("var securityRequirementIndex = 0;"); - for (var i = 0; i < securityRequirements.Count; i++) - { - writer.Append("if (securityRequirementIndex == 0"); - var requirement = securityRequirements[i]; - foreach (var scheme in requirement.Schemes) - { - writer.Append(" && ").Append(scheme.FieldName).Append(" is not null"); - } - - writer.AppendLine(")"); - writer.AppendLine("{"); - using (writer.PushIndent()) - { - writer.Append("securityRequirementIndex = ").Append((i + 1).ToString(System.Globalization.CultureInfo.InvariantCulture)).AppendLine(";"); - } - - writer.AppendLine("}"); - } - } - - private void EmitSecuritySchemeBlock(IndentedStringBuilder writer, OperationGroupItem operation, SecuritySchemeBinding securityScheme, Action emitBody) - { - if (operation.SecurityRequirements is null) - { - writer.Append("if (").Append(securityScheme.FieldName).AppendLine(" is not null)"); - writer.AppendLine("{"); - using (writer.PushIndent()) - { - emitBody(); - } - - writer.AppendLine("}"); - return; - } - - var matchingRequirements = new List(); - for (var i = 0; i < operation.SecurityRequirements.Count; i++) - { - if (operation.SecurityRequirements[i].Schemes.Contains(securityScheme)) - { - matchingRequirements.Add(i + 1); - } - } - - if (matchingRequirements.Count == 0) - { - return; - } - - writer.Append("if ("); - for (var i = 0; i < matchingRequirements.Count; i++) - { - if (i > 0) - { - writer.Append(" || "); - } - - writer.Append("securityRequirementIndex == ").Append(matchingRequirements[i].ToString(System.Globalization.CultureInfo.InvariantCulture)); - } - - writer.AppendLine(")"); - writer.AppendLine("{"); - using (writer.PushIndent()) - { - emitBody(); - } - - writer.AppendLine("}"); - } - - private List GetOperationSecuritySchemes(OperationGroupItem operation, SecuritySchemeLocation location) - => GetOperationSecuritySchemes(operation, static (scheme, state) => scheme.Location == state, location); - - private List GetOperationSecuritySchemesExcept(OperationGroupItem operation, SecuritySchemeLocation location) - => GetOperationSecuritySchemes(operation, static (scheme, state) => scheme.Location != state, location); - - private List GetOperationSecuritySchemes( - OperationGroupItem operation, - Func predicate, - TState state) - { - var schemes = operation.SecurityRequirements is null - ? model.SecuritySchemes - : operation.SecurityRequirements.SelectMany(static requirement => requirement.Schemes).ToList(); - - var result = new List(); - var usedSchemeKeys = new HashSet(StringComparer.Ordinal); - foreach (var scheme in schemes) - { - if (predicate(scheme, state) && usedSchemeKeys.Add(scheme.SchemeKey)) - { - result.Add(scheme); - } - } - - return result; - } - - private string GetSerializerOptionsExpression(JsonSerializerDirection direction) - => JsonSerializerOptionsEmitter.GetOptionsExpression(model, direction); } diff --git a/src/OpenApiWeaver/SupportTypeNames.cs b/src/OpenApiWeaver/SupportTypeNames.cs new file mode 100644 index 0000000..0a0304d --- /dev/null +++ b/src/OpenApiWeaver/SupportTypeNames.cs @@ -0,0 +1,26 @@ +namespace OpenApiWeaver; + +internal static class SupportTypeNames +{ + public const string ClientHelpers = "OpenApiClientHelpers"; + public const string Exception = "OpenApiException"; + public const string SerializerOptions = "SerializerOptions"; + public const string DefaultSerializerOptionsExpression = ClientHelpers + "." + SerializerOptions; + + public static IReadOnlyList ReservedTypeNames { get; } = + [ + ClientHelpers, + Exception + ]; + + public static string CreateSerializerOptionsTypeName(string clientName) + => clientName + "JsonSerializerOptions"; + + public static void ReserveTypeNames(SchemaCatalog catalog) + { + foreach (var typeName in ReservedTypeNames) + { + catalog.ReserveTypeName(typeName); + } + } +}