Skip to content
Closed
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
415 changes: 63 additions & 352 deletions src/Refitter.Core/CSharpClientGeneratorFactory.cs

Large diffs are not rendered by default.

25 changes: 25 additions & 0 deletions src/Refitter.Core/CustomIntegerTypeMutator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using NJsonSchema;
using NSwag;

namespace Refitter.Core;

internal sealed class CustomIntegerTypeMutator(IntegerType customIntegerType)
: IOpenApiDocumentMutator
{
public void Mutate(OpenApiDocument document)
{
if (customIntegerType == IntegerType.Int32)
return;

SchemaWalker.TraverseDocumentSchemas(document, FixSchemaIntegerFormat);
}

private static void FixSchemaIntegerFormat(JsonSchema schema)
{
if (schema.Type == JsonObjectType.Integer &&
string.IsNullOrEmpty(schema.Format))
{
schema.Format = "int64";
}
}
}
34 changes: 34 additions & 0 deletions src/Refitter.Core/CustomTemplateFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using NJsonSchema;
using NJsonSchema.CodeGeneration;
using NJsonSchema.CodeGeneration.CSharp;
using NSwag;
using NSwag.CodeGeneration.CSharp;

namespace Refitter.Core;

/// <summary>
/// Custom template factory solely for the purpose of tweaking the JsonPolymorphic attribute
/// with UnknownDerivedTypeHandling = FallBackToBaseType and IgnoreUnrecognizedTypeDiscriminators = true.
/// This class should be removed if NSwag eventually supports setting UnknownDerivedTypeHandling
/// and IgnoreUnrecognizedTypeDiscriminators.
/// </summary>
internal class CustomTemplateFactory : NSwag.CodeGeneration.DefaultTemplateFactory
{
public CustomTemplateFactory(CodeGeneratorSettingsBase settings)
: base(settings, [typeof(CSharpGenerator).Assembly, typeof(CSharpGeneratorBaseSettings).Assembly, typeof(NJsonSchema.CodeGeneration.CSharp.CSharpGeneratorSettings).Assembly])
{
}

protected override string GetEmbeddedLiquidTemplate(string language, string template)
{
var templateText = base.GetEmbeddedLiquidTemplate(language, template);
return template switch
{
"Class" => templateText
.Replace(
"[System.Text.Json.Serialization.JsonPolymorphic(TypeDiscriminatorPropertyName = \"{{ Discriminator }}\")]",
"[System.Text.Json.Serialization.JsonPolymorphic(TypeDiscriminatorPropertyName = \"{{ Discriminator }}\", UnknownDerivedTypeHandling = System.Text.Json.Serialization.JsonUnknownDerivedTypeHandling.FallBackToBaseType, IgnoreUnrecognizedTypeDiscriminators = true)]"),
_ => templateText,
};
}
}
21 changes: 21 additions & 0 deletions src/Refitter.Core/DisableAdditionalPropertiesMutator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using NSwag;

namespace Refitter.Core;

internal sealed class DisableAdditionalPropertiesMutator(bool generateDefaultAdditionalProperties)
: IOpenApiDocumentMutator
{
public void Mutate(OpenApiDocument document)
{
if (generateDefaultAdditionalProperties)
return;

if (document.Components?.Schemas == null)
return;

foreach (var kvp in document.Components.Schemas)
{
kvp.Value.ActualSchema.AllowAdditionalProperties = false;
}
}
}
28 changes: 28 additions & 0 deletions src/Refitter.Core/FixMissingIntegerTypesMutator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using NJsonSchema;
using NSwag;

namespace Refitter.Core;

internal sealed class FixMissingIntegerTypesMutator : IOpenApiDocumentMutator
{
public void Mutate(OpenApiDocument document)
{
SchemaWalker.TraverseDocumentSchemas(document, FixSchemaTypeFromFormat);
}

private static void FixSchemaTypeFromFormat(JsonSchema schema)
{
if ((schema.Type == JsonObjectType.None || schema.Type == JsonObjectType.Null) &&
!string.IsNullOrEmpty(schema.Format))
{
if (schema.Format == "int32" || schema.Format == "int64")
{
schema.Type = JsonObjectType.Integer;
}
else if (schema.Format == "float" || schema.Format == "double")
{
schema.Type = JsonObjectType.Number;
}
}
}
}
8 changes: 8 additions & 0 deletions src/Refitter.Core/IOpenApiDocumentMutator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using NSwag;

namespace Refitter.Core;

internal interface IOpenApiDocumentMutator
{
void Mutate(OpenApiDocument document);
}
15 changes: 15 additions & 0 deletions src/Refitter.Core/JsonSchemaReferenceComparer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System.Runtime.CompilerServices;
using NJsonSchema;

namespace Refitter.Core;

internal sealed class JsonSchemaReferenceComparer : IEqualityComparer<JsonSchema>
{
public static JsonSchemaReferenceComparer Instance { get; } = new();

public bool Equals(JsonSchema? x, JsonSchema? y) =>
ReferenceEquals(x, y);

public int GetHashCode(JsonSchema obj) =>
RuntimeHelpers.GetHashCode(obj);
}
48 changes: 48 additions & 0 deletions src/Refitter.Core/OneOfDiscriminatorToAllOfMutator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using NJsonSchema;
using NSwag;

namespace Refitter.Core;

internal sealed class OneOfDiscriminatorToAllOfMutator : IOpenApiDocumentMutator
{
public void Mutate(OpenApiDocument document)

Check failure on line 8 in src/Refitter.Core/OneOfDiscriminatorToAllOfMutator.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this method to reduce its Cognitive Complexity from 20 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=christianhelle_refitter&issues=AZ7Cz-ixlFS2SonGilJm&open=AZ7Cz-ixlFS2SonGilJm&pullRequest=1135
{
if (document.Components?.Schemas == null)
return;

foreach (var kvp in document.Components.Schemas)
{
var schema = kvp.Value?.ActualSchema;
if (schema == null)
continue;

if (schema.DiscriminatorObject == null)
continue;

var unionSchemas = schema.OneOf.Concat(schema.AnyOf).ToArray();
if (unionSchemas.Length == 0)
continue;

if (schema.Type == JsonObjectType.None || schema.Type == JsonObjectType.Null)
schema.Type = JsonObjectType.Object;

foreach (var subSchemaRef in unionSchemas)
{
var subSchema = subSchemaRef?.ActualSchema;
if (subSchema == null)
continue;

bool alreadyInherits = subSchema.AllOf.Any(
a => a.HasReference && a.ActualSchema == schema);
if (!alreadyInherits)
{
var reference = new JsonSchema { Reference = schema };
subSchema.AllOf.Add(reference);
}
}

schema.OneOf.Clear();
schema.AnyOf.Clear();
}
}
}
39 changes: 39 additions & 0 deletions src/Refitter.Core/SafeSchemaTypeNameGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using NJsonSchema;
using NJsonSchema.CodeGeneration;

namespace Refitter.Core;

internal sealed class SafeSchemaTypeNameGenerator(
HashSet<string> preferredExactTypeNameHints) : ITypeNameGenerator
{
private const string AnonymousTypeName = "Anonymous";
private readonly DefaultTypeNameGenerator inner = new();

public string Generate(
JsonSchema schema,
string? typeNameHint,
IEnumerable<string> reservedTypeNames)
{
var normalizedHint = IdentifierUtils.NormalizeSchemaTypeNameHint(typeNameHint)
?? IdentifierUtils.NormalizeSchemaTypeNameHint(schema.Title)
?? AnonymousTypeName;

var typeNames = reservedTypeNames as string[] ?? reservedTypeNames.ToArray();
if (!string.IsNullOrEmpty(typeNameHint) &&
!string.Equals(typeNameHint, normalizedHint, StringComparison.Ordinal) &&
preferredExactTypeNameHints.Contains(normalizedHint))
{
var reservedHints = typeNames
.Concat(preferredExactTypeNameHints);

var reservedHintSet = new HashSet<string>(reservedHints, StringComparer.Ordinal);

normalizedHint = IdentifierUtils.Counted(reservedHintSet, normalizedHint);
}

var generatedTypeName = inner.Generate(schema, normalizedHint, typeNames);
return string.IsNullOrWhiteSpace(generatedTypeName)
? inner.Generate(schema, AnonymousTypeName, typeNames)
: generatedTypeName;
}
}
161 changes: 161 additions & 0 deletions src/Refitter.Core/SchemaWalker.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
using NJsonSchema;
using NSwag;

namespace Refitter.Core;

internal static class SchemaWalker
{
public static void TraverseDocumentSchemas(
OpenApiDocument document,
Action<JsonSchema> visitor)
{
var visited = new HashSet<JsonSchema>(JsonSchemaReferenceComparer.Instance);
var schemasToProcess = new Stack<JsonSchema>();

foreach (var schema in EnumerateDocumentSchemaRoots(document))
{
TryPush(schema, schemasToProcess);
}

while (schemasToProcess.Count > 0)
{
var actualSchema = schemasToProcess.Pop().ActualSchema;
if (!visited.Add(actualSchema))
{
continue;
}

visitor(actualSchema);

foreach (var childSchema in EnumerateTraversableSchemas(actualSchema))
{
TryPush(childSchema, schemasToProcess);
}
}
}

public static IEnumerable<JsonSchema?> EnumerateDocumentSchemaRoots(

Check failure on line 37 in src/Refitter.Core/SchemaWalker.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this method to reduce its Cognitive Complexity from 44 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=christianhelle_refitter&issues=AZ7Cz-lclFS2SonGilJn&open=AZ7Cz-lclFS2SonGilJn&pullRequest=1135
OpenApiDocument document)
{
if (document.Components?.Schemas != null)
{
foreach (var schema in document.Components.Schemas.Values)
{
yield return schema;
}
}

if (document.Paths == null)
{
yield break;
}

foreach (var pathItem in document.Paths.Values)
{
if (pathItem == null)
{
continue;
}

foreach (var parameter in pathItem.Parameters)
{
yield return parameter;
}

foreach (var operation in pathItem.Values)
{
if (operation == null)
{
continue;
}

foreach (var parameter in operation.ActualParameters)
{
yield return parameter;
}

if (operation.RequestBody?.Content != null)
{
foreach (var content in operation.RequestBody.Content.Values)
{
yield return content.Schema;
}
}

foreach (var response in operation.ActualResponses.Values)
{
if (response.Headers != null)
{
foreach (var header in response.Headers.Values)
{
yield return header;
}
}

if (response.Content == null)
{
continue;
}

foreach (var content in response.Content.Values)
{
yield return content.Schema;
}
}
}
}
}

public static IEnumerable<JsonSchema?> EnumerateTraversableSchemas(JsonSchema schema)
{
yield return schema.AdditionalItemsSchema;
yield return schema.AdditionalPropertiesSchema;
yield return schema.DictionaryKey;
yield return schema.Item;

if (schema.Items.Count != 0)
{
foreach (var item in schema.Items)
{
yield return item;
}
}

yield return schema.Not;

foreach (var property in schema.Properties.Values)
{
yield return property;
}

foreach (var subSchema in schema.AllOf)
{
yield return subSchema;
}

foreach (var subSchema in schema.OneOf)
{
yield return subSchema;
}

foreach (var subSchema in schema.AnyOf)
{
yield return subSchema;
}

foreach (var definition in schema.Definitions.Values)
{
yield return definition;
}
}

private static void TryPush(JsonSchema? schema, Stack<JsonSchema> stack)
{
if (schema == null)
{
return;
}

stack.Push(schema);
}
}
Loading
Loading