RicoSuter / NSwag

The Swagger/OpenAPI toolchain for .NET, ASP.NET Core and TypeScript.
http://NSwag.org
MIT License
6.74k stars 1.29k forks source link

Cannot customise schema names #3872

Open davidkeaveny opened 2 years ago

davidkeaveny commented 2 years ago

I have an application where data contracts in C# are defined under two namespaces: MyProject.Contracts.V1 and MyProject.Contracts.V2, and I have set up API versioning so that API version V1 will return the V1 contracts, and V2 will return a mix of V1 and V2 - some contracts have not changed between V1 and V2, and some others haven't.

Using the defaults for NSwag 13.15.5, when it generates the schema for a class that is new/updated in V2, it creates the schema and adds a version suffix to the name. e.g. MyProject.Contracts.V1.Allowance has a schema name of "$ref": "#/components/schemas/Allowance", whereas the updated MyProject.Contracts.V2.Allowance has a schema name of "$ref": "#/components/schemas/Allowance2".

I would like to customise that by namespace, so my V1 contracts would be e.g. #/components/schemas/V1.Allowance and V2 contracts would be #/components/schemas/V2.Allowance. To that end, I implemented a custom schema name generator as follows:

internal class CustomSchemaNameGenerator : ISchemaNameGenerator
{
  public string Generate(Type type)
  {
     return type.Namespace.Contains(".V2.")
        ? $"V2.{type.Name}"
        : $"V1.{type.Name}";
  }
}

and configured it with:

private void ConfigureApiVersion(AspNetCoreOpenApiDocumentGeneratorSettings configure, IServiceProvider provider, string version)
{
  configure.SchemaNameGenerator = new CustomSchemaNameGenerator();
}

If I put a breakpoint in the Generate(Type) method then I can see the code being hit, but when I view the final rendered swagger.json, the changes aren't there, I still have Allowance and Allowance2. Am I missing something obvious?

ldsenow commented 1 year ago

Same here. So it is not being used in the output?

hobwell commented 4 months ago

I am speculating, but I think the issue is the "." (period/dot) in the type name. I wrote a simple SchemaNameGenerator with the express purpose of having the entire namespace included in the type name, but if I don't explicitly replace the periods with underscore (or presumably some other "valid" character), then the namespace is simply removed and I wind up with the "class", "class1", "class2" behaviour. If I had to hazard a guess, somewhere there's a 'lastIndexOf(".")' being applied to shorten the type name.

// A swagger processor which will include namespaces in object type names
public class NamespacedSchemaNameGenerator : ISchemaNameGenerator {
    // Include namespaces in object type names
    public string Generate(Type type) {
        // return type.FullName; <-- results in namespace being stripped
        return type.FullName.Replace(".", "_"); // <-- results in type names like "MyNamespace_SubNamespace_ClassName"
    }
}

I would love to know if there is a simple workaround for this, I much prefer MyNamespace.SubNamespace.ClassName to MyNamespace_SubNamespace_ClassName

Edit: I found this in another issue, and it seems to do the tick (in combination with the above)

public class NamespacedTypeNameGenerator : DefaultTypeNameGenerator {
    public override string Generate(JsonSchema schema, string typeNameHint, IEnumerable<string> reservedTypeNames) {
        var typeName = Generate(schema, typeNameHint).Replace("_", ".");
        return typeName;
    }
}