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

V14/feature/custom generic swagger #15025

Merged
merged 5 commits into from
Nov 7, 2023
Merged
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace Umbraco.Cms.Api.Common.Attributes;

public abstract class ShortGenericSchemaNameAttribute : Attribute
{
public Type[] GenericTypes { get; set; }

public string SchemaName { get; set; }

public ShortGenericSchemaNameAttribute(string schemaName, Type[] genericTypes)
{
GenericTypes = genericTypes;
SchemaName = schemaName;
}
}

public class ShortGenericSchemaNameAttribute<T1, T2> : ShortGenericSchemaNameAttribute
{
public ShortGenericSchemaNameAttribute(string schemaName)
: base(schemaName, new[] { typeof(T1), typeof(T2) })
{
}
}
68 changes: 57 additions & 11 deletions src/Umbraco.Cms.Api.Common/OpenApi/SchemaIdSelector.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Text.RegularExpressions;
using Umbraco.Cms.Api.Common.Attributes;
using Umbraco.Extensions;

namespace Umbraco.Cms.Api.Common.OpenApi;
Expand All @@ -10,18 +11,9 @@ public virtual string SchemaId(Type type)

protected string UmbracoSchemaId(Type type)
{
string SanitizedTypeName(Type t) => t.Name
// first grab the "non generic" part of any generic type name (i.e. "PagedViewModel`1" becomes "PagedViewModel")
.Split('`').First()
// then remove the "ViewModel" postfix from type names
.TrimEnd("ViewModel");

var name = SanitizedTypeName(type);
if (type.IsGenericType)
{
// append the generic type names, ultimately turning i.e. "PagedViewModel<RelationItemViewModel>" into "PagedRelationItem"
name = $"{name}{string.Join(string.Empty, type.GenericTypeArguments.Select(SanitizedTypeName))}";
}

name = HandleGenerics(name, type);

if (name.EndsWith("Model") == false)
{
Expand All @@ -33,4 +25,58 @@ string SanitizedTypeName(Type t) => t.Name
// make absolutely sure we don't pass any invalid named by removing all non-word chars
return Regex.Replace(name, @"[^\w]", string.Empty);
}

private string SanitizedTypeName(Type t) => t.Name
// first grab the "non generic" part of any generic type name (i.e. "PagedViewModel`1" becomes "PagedViewModel")
.Split('`').First()
// then remove the "ViewModel" postfix from type names
.TrimEnd("ViewModel");

private string HandleGenerics(string name, Type type)
{
if (!type.IsGenericType)
{
return name;
}

// find all types that implement this type and have an matching attribute
var assignableTypesWithAttributeInfo = AppDomain.CurrentDomain.GetAssemblies()
.Where(assembly => assembly.FullName?.StartsWith("Umbraco") == true)
.SelectMany(assembly => assembly.GetTypes())
.Where(t => t.IsAssignableTo(type))
.Select(t =>
{
var attribute = System.Attribute.GetCustomAttributes(t)
.FirstOrDefault(attribute => attribute is ShortGenericSchemaNameAttribute) as
ShortGenericSchemaNameAttribute;
return attribute == null
? new ShortSchemaNameAttributeInfo(t)
: new ShortSchemaNameAttributeInfo(t, attribute.GenericTypes, attribute.SchemaName);
})
.Where(info => info.GenericTypes != null);

var matchingType = assignableTypesWithAttributeInfo
.SingleOrDefault(t => t.GenericTypes!.Length == type.GenericTypeArguments.Length
&& t.GenericTypes.Intersect(type.GenericTypeArguments).Count() ==
type.GenericTypeArguments.Length && t.SchemaName.IsNullOrWhiteSpace() == false);

// use attribute custom name or append the generic type names, ultimately turning i.e. "PagedViewModel<RelationItemViewModel>" into "PagedRelationItem"
return matchingType != null
? matchingType.SchemaName!
: $"{name}{string.Join(string.Empty, type.GenericTypeArguments.Select(SanitizedTypeName))}";
}

private class ShortSchemaNameAttributeInfo
{
public Type Type { get; set; }
public Type[]? GenericTypes { get; set; }
public string? SchemaName { get; set; }

public ShortSchemaNameAttributeInfo(Type type, Type[]? genericTypes = null, string? schemaName = null)
{
Type = type;
GenericTypes = genericTypes;
SchemaName = schemaName;
}
}
}
53 changes: 31 additions & 22 deletions src/Umbraco.Cms.Api.Management/OpenApi.json
Original file line number Diff line number Diff line change
Expand Up @@ -17357,7 +17357,7 @@
},
"additionalProperties": false
},
"ContentResponseModelBaseDocumentValueModelDocumentVariantResponseModel": {
"ContentForDocumentResponseModel": {
"type": "object",
"properties": {
"values": {
Expand Down Expand Up @@ -17462,7 +17462,7 @@
"type": "integer",
"format": "int32"
},
"ContentTypeResponseModelBaseDocumentTypePropertyTypeResponseModelDocumentTypePropertyTypeContainerResponseModel": {
"ContentTypeForDocumentTypeResponseModel": {
"type": "object",
"properties": {
"alias": {
Expand Down Expand Up @@ -17537,7 +17537,7 @@
},
"additionalProperties": false
},
"ContentTypeResponseModelBaseMediaTypePropertyTypeResponseModelMediaTypePropertyTypeContainerResponseModel": {
"ContentTypeForMediaTypeResponseModel": {
"type": "object",
"properties": {
"alias": {
Expand Down Expand Up @@ -17667,7 +17667,7 @@
},
"additionalProperties": false
},
"CreateContentRequestModelBaseDocumentValueModelDocumentVariantRequestModel": {
"CreateContentForDocumentRequestModel": {
"type": "object",
"properties": {
"values": {
Expand Down Expand Up @@ -17703,7 +17703,7 @@
},
"additionalProperties": false
},
"CreateContentRequestModelBaseMediaValueModelMediaVariantRequestModel": {
"CreateContentForMediaRequestModel": {
"type": "object",
"properties": {
"values": {
Expand Down Expand Up @@ -17739,7 +17739,7 @@
},
"additionalProperties": false
},
"CreateContentTypeRequestModelBaseCreateDocumentTypePropertyTypeRequestModelCreateDocumentTypePropertyTypeContainerRequestModel": {
"CreateContentTypeForDocumentTypeRequestModel": {
"type": "object",
"properties": {
"alias": {
Expand Down Expand Up @@ -17820,7 +17820,7 @@
},
"additionalProperties": false
},
"CreateContentTypeRequestModelBaseCreateMediaTypePropertyTypeRequestModelCreateMediaTypePropertyTypeContainerRequestModel": {
"CreateContentTypeForMediaTypeRequestModel": {
"type": "object",
"properties": {
"alias": {
Expand Down Expand Up @@ -17942,7 +17942,7 @@
"type": "object",
"allOf": [
{
"$ref": "#/components/schemas/CreateContentRequestModelBaseDocumentValueModelDocumentVariantRequestModel"
"$ref": "#/components/schemas/CreateContentForDocumentRequestModel"
}
],
"properties": {
Expand Down Expand Up @@ -17980,7 +17980,7 @@
"type": "object",
"allOf": [
{
"$ref": "#/components/schemas/CreateContentTypeRequestModelBaseCreateDocumentTypePropertyTypeRequestModelCreateDocumentTypePropertyTypeContainerRequestModel"
"$ref": "#/components/schemas/CreateContentTypeForDocumentTypeRequestModel"
}
],
"properties": {
Expand Down Expand Up @@ -18059,7 +18059,7 @@
"type": "object",
"allOf": [
{
"$ref": "#/components/schemas/CreateContentRequestModelBaseMediaValueModelMediaVariantRequestModel"
"$ref": "#/components/schemas/CreateContentForMediaRequestModel"
}
],
"properties": {
Expand Down Expand Up @@ -18092,7 +18092,7 @@
"type": "object",
"allOf": [
{
"$ref": "#/components/schemas/CreateContentTypeRequestModelBaseCreateMediaTypePropertyTypeRequestModelCreateMediaTypePropertyTypeContainerRequestModel"
"$ref": "#/components/schemas/CreateContentTypeForMediaTypeRequestModel"
}
],
"additionalProperties": false
Expand Down Expand Up @@ -18677,6 +18677,9 @@
"contentTypeId": {
"type": "string",
"format": "uuid"
},
"isTrashed": {
"type": "boolean"
}
},
"additionalProperties": false
Expand All @@ -18697,7 +18700,7 @@
"type": "object",
"allOf": [
{
"$ref": "#/components/schemas/ContentResponseModelBaseDocumentValueModelDocumentVariantResponseModel"
"$ref": "#/components/schemas/ContentForDocumentResponseModel"
}
],
"properties": {
Expand All @@ -18715,6 +18718,9 @@
"type": "string",
"format": "uuid",
"nullable": true
},
"isTrashed": {
"type": "boolean"
}
},
"additionalProperties": false
Expand Down Expand Up @@ -18796,7 +18802,7 @@
"type": "object",
"allOf": [
{
"$ref": "#/components/schemas/ContentTypeResponseModelBaseDocumentTypePropertyTypeResponseModelDocumentTypePropertyTypeContainerResponseModel"
"$ref": "#/components/schemas/ContentTypeForDocumentTypeResponseModel"
}
],
"properties": {
Expand Down Expand Up @@ -19633,6 +19639,9 @@
"icon": {
"type": "string",
"nullable": true
},
"isTrashed": {
"type": "boolean"
}
},
"additionalProperties": false
Expand Down Expand Up @@ -19688,7 +19697,7 @@
"type": "object",
"allOf": [
{
"$ref": "#/components/schemas/ContentTypeResponseModelBaseMediaTypePropertyTypeResponseModelMediaTypePropertyTypeContainerResponseModel"
"$ref": "#/components/schemas/ContentTypeForMediaTypeResponseModel"
}
],
"additionalProperties": false
Expand Down Expand Up @@ -22148,7 +22157,7 @@
},
"additionalProperties": false
},
"UpdateContentRequestModelBaseDocumentValueModelDocumentVariantRequestModel": {
"UpdateContentForDocumentRequestModel": {
"type": "object",
"properties": {
"values": {
Expand All @@ -22174,7 +22183,7 @@
},
"additionalProperties": false
},
"UpdateContentRequestModelBaseMediaValueModelMediaVariantRequestModel": {
"UpdateContentForMediaRequestModel": {
"type": "object",
"properties": {
"values": {
Expand All @@ -22200,7 +22209,7 @@
},
"additionalProperties": false
},
"UpdateContentTypeRequestModelBaseUpdateDocumentTypePropertyTypeRequestModelUpdateDocumentTypePropertyTypeContainerRequestModel": {
"UpdateContentTypeForDocumentTypeRequestModel": {
"type": "object",
"properties": {
"alias": {
Expand Down Expand Up @@ -22271,7 +22280,7 @@
},
"additionalProperties": false
},
"UpdateContentTypeRequestModelBaseUpdateMediaTypePropertyTypeRequestModelUpdateMediaTypePropertyTypeContainerRequestModel": {
"UpdateContentTypeForMediaTypeRequestModel": {
"type": "object",
"properties": {
"alias": {
Expand Down Expand Up @@ -22376,7 +22385,7 @@
"type": "object",
"allOf": [
{
"$ref": "#/components/schemas/UpdateContentRequestModelBaseDocumentValueModelDocumentVariantRequestModel"
"$ref": "#/components/schemas/UpdateContentForDocumentRequestModel"
}
],
"properties": {
Expand Down Expand Up @@ -22410,7 +22419,7 @@
"type": "object",
"allOf": [
{
"$ref": "#/components/schemas/UpdateContentTypeRequestModelBaseUpdateDocumentTypePropertyTypeRequestModelUpdateDocumentTypePropertyTypeContainerRequestModel"
"$ref": "#/components/schemas/UpdateContentTypeForDocumentTypeRequestModel"
}
],
"properties": {
Expand Down Expand Up @@ -22467,7 +22476,7 @@
"type": "object",
"allOf": [
{
"$ref": "#/components/schemas/UpdateContentRequestModelBaseMediaValueModelMediaVariantRequestModel"
"$ref": "#/components/schemas/UpdateContentForMediaRequestModel"
}
],
"additionalProperties": false
Expand All @@ -22494,7 +22503,7 @@
"type": "object",
"allOf": [
{
"$ref": "#/components/schemas/UpdateContentTypeRequestModelBaseUpdateMediaTypePropertyTypeRequestModelUpdateMediaTypePropertyTypeContainerRequestModel"
"$ref": "#/components/schemas/UpdateContentTypeForMediaTypeRequestModel"
}
],
"additionalProperties": false
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using Umbraco.Cms.Api.Management.ViewModels.Content;
using Umbraco.Cms.Api.Common.Attributes;
using Umbraco.Cms.Api.Management.ViewModels.Content;

namespace Umbraco.Cms.Api.Management.ViewModels.Document;

[ShortGenericSchemaName<DocumentValueModel, DocumentVariantRequestModel>("CreateContentForDocumentRequestModel")]
public class CreateDocumentRequestModel : CreateContentRequestModelBase<DocumentValueModel, DocumentVariantRequestModel>
{
public Guid ContentTypeId { get; set; }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using Umbraco.Cms.Api.Management.ViewModels.Content;
using Umbraco.Cms.Api.Common.Attributes;
using Umbraco.Cms.Api.Management.ViewModels.Content;

namespace Umbraco.Cms.Api.Management.ViewModels.Document;

[ShortGenericSchemaName<DocumentValueModel, DocumentVariantResponseModel>("ContentForDocumentResponseModel")]
public class DocumentResponseModel : ContentResponseModelBase<DocumentValueModel, DocumentVariantResponseModel>
{
public IEnumerable<ContentUrlInfo> Urls { get; set; } = Array.Empty<ContentUrlInfo>();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using Umbraco.Cms.Api.Management.ViewModels.Content;
using Umbraco.Cms.Api.Common.Attributes;
using Umbraco.Cms.Api.Management.ViewModels.Content;

namespace Umbraco.Cms.Api.Management.ViewModels.Document;

[ShortGenericSchemaName<DocumentValueModel, DocumentVariantRequestModel>("UpdateContentForDocumentRequestModel")]
public class UpdateDocumentRequestModel : UpdateContentRequestModelBase<DocumentValueModel, DocumentVariantRequestModel>
{
public Guid? TemplateId { get; set; }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using Umbraco.Cms.Api.Management.ViewModels.ContentType;
using Umbraco.Cms.Api.Common.Attributes;
using Umbraco.Cms.Api.Management.ViewModels.ContentType;

namespace Umbraco.Cms.Api.Management.ViewModels.DocumentType;

[ShortGenericSchemaName<CreateDocumentTypePropertyTypeRequestModel, CreateDocumentTypePropertyTypeContainerRequestModel>("CreateContentTypeForDocumentTypeRequestModel")]
public class CreateDocumentTypeRequestModel
: CreateContentTypeRequestModelBase<CreateDocumentTypePropertyTypeRequestModel, CreateDocumentTypePropertyTypeContainerRequestModel>
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using Umbraco.Cms.Api.Management.ViewModels.ContentType;
using Umbraco.Cms.Api.Common.Attributes;
using Umbraco.Cms.Api.Management.ViewModels.ContentType;

namespace Umbraco.Cms.Api.Management.ViewModels.DocumentType;

[ShortGenericSchemaName<DocumentTypePropertyTypeResponseModel, DocumentTypePropertyTypeContainerResponseModel>("ContentTypeForDocumentTypeResponseModel")]
public class DocumentTypeResponseModel : ContentTypeResponseModelBase<DocumentTypePropertyTypeResponseModel, DocumentTypePropertyTypeContainerResponseModel>
{
public IEnumerable<Guid> AllowedTemplateIds { get; set; } = Array.Empty<Guid>();
Expand Down
Loading
Loading