V14/feature/custom generic swagger (#15025)

Co-authored-by: Sven Geusens <sge@umbraco.dk>
This commit is contained in:
Sven Geusens
2023-11-07 16:15:56 +01:00
committed by GitHub
parent 66ea4fe29a
commit 2f4f2ae384
14 changed files with 142 additions and 43 deletions

View File

@@ -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) })
{
}
}

View File

@@ -1,4 +1,5 @@
using System.Text.RegularExpressions;
using Umbraco.Cms.Api.Common.Attributes;
using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Common.OpenApi;
@@ -10,18 +11,9 @@ public class SchemaIdSelector : ISchemaIdSelector
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)
{
@@ -33,4 +25,58 @@ public class SchemaIdSelector : ISchemaIdSelector
// 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;
}
}
}

View File

@@ -17357,7 +17357,7 @@
},
"additionalProperties": false
},
"ContentResponseModelBaseDocumentValueModelDocumentVariantResponseModel": {
"ContentForDocumentResponseModel": {
"type": "object",
"properties": {
"values": {
@@ -17462,7 +17462,7 @@
"type": "integer",
"format": "int32"
},
"ContentTypeResponseModelBaseDocumentTypePropertyTypeResponseModelDocumentTypePropertyTypeContainerResponseModel": {
"ContentTypeForDocumentTypeResponseModel": {
"type": "object",
"properties": {
"alias": {
@@ -17537,7 +17537,7 @@
},
"additionalProperties": false
},
"ContentTypeResponseModelBaseMediaTypePropertyTypeResponseModelMediaTypePropertyTypeContainerResponseModel": {
"ContentTypeForMediaTypeResponseModel": {
"type": "object",
"properties": {
"alias": {
@@ -17667,7 +17667,7 @@
},
"additionalProperties": false
},
"CreateContentRequestModelBaseDocumentValueModelDocumentVariantRequestModel": {
"CreateContentForDocumentRequestModel": {
"type": "object",
"properties": {
"values": {
@@ -17703,7 +17703,7 @@
},
"additionalProperties": false
},
"CreateContentRequestModelBaseMediaValueModelMediaVariantRequestModel": {
"CreateContentForMediaRequestModel": {
"type": "object",
"properties": {
"values": {
@@ -17739,7 +17739,7 @@
},
"additionalProperties": false
},
"CreateContentTypeRequestModelBaseCreateDocumentTypePropertyTypeRequestModelCreateDocumentTypePropertyTypeContainerRequestModel": {
"CreateContentTypeForDocumentTypeRequestModel": {
"type": "object",
"properties": {
"alias": {
@@ -17820,7 +17820,7 @@
},
"additionalProperties": false
},
"CreateContentTypeRequestModelBaseCreateMediaTypePropertyTypeRequestModelCreateMediaTypePropertyTypeContainerRequestModel": {
"CreateContentTypeForMediaTypeRequestModel": {
"type": "object",
"properties": {
"alias": {
@@ -17942,7 +17942,7 @@
"type": "object",
"allOf": [
{
"$ref": "#/components/schemas/CreateContentRequestModelBaseDocumentValueModelDocumentVariantRequestModel"
"$ref": "#/components/schemas/CreateContentForDocumentRequestModel"
}
],
"properties": {
@@ -17980,7 +17980,7 @@
"type": "object",
"allOf": [
{
"$ref": "#/components/schemas/CreateContentTypeRequestModelBaseCreateDocumentTypePropertyTypeRequestModelCreateDocumentTypePropertyTypeContainerRequestModel"
"$ref": "#/components/schemas/CreateContentTypeForDocumentTypeRequestModel"
}
],
"properties": {
@@ -18059,7 +18059,7 @@
"type": "object",
"allOf": [
{
"$ref": "#/components/schemas/CreateContentRequestModelBaseMediaValueModelMediaVariantRequestModel"
"$ref": "#/components/schemas/CreateContentForMediaRequestModel"
}
],
"properties": {
@@ -18092,7 +18092,7 @@
"type": "object",
"allOf": [
{
"$ref": "#/components/schemas/CreateContentTypeRequestModelBaseCreateMediaTypePropertyTypeRequestModelCreateMediaTypePropertyTypeContainerRequestModel"
"$ref": "#/components/schemas/CreateContentTypeForMediaTypeRequestModel"
}
],
"additionalProperties": false
@@ -18677,6 +18677,9 @@
"contentTypeId": {
"type": "string",
"format": "uuid"
},
"isTrashed": {
"type": "boolean"
}
},
"additionalProperties": false
@@ -18697,7 +18700,7 @@
"type": "object",
"allOf": [
{
"$ref": "#/components/schemas/ContentResponseModelBaseDocumentValueModelDocumentVariantResponseModel"
"$ref": "#/components/schemas/ContentForDocumentResponseModel"
}
],
"properties": {
@@ -18715,6 +18718,9 @@
"type": "string",
"format": "uuid",
"nullable": true
},
"isTrashed": {
"type": "boolean"
}
},
"additionalProperties": false
@@ -18796,7 +18802,7 @@
"type": "object",
"allOf": [
{
"$ref": "#/components/schemas/ContentTypeResponseModelBaseDocumentTypePropertyTypeResponseModelDocumentTypePropertyTypeContainerResponseModel"
"$ref": "#/components/schemas/ContentTypeForDocumentTypeResponseModel"
}
],
"properties": {
@@ -19633,6 +19639,9 @@
"icon": {
"type": "string",
"nullable": true
},
"isTrashed": {
"type": "boolean"
}
},
"additionalProperties": false
@@ -19688,7 +19697,7 @@
"type": "object",
"allOf": [
{
"$ref": "#/components/schemas/ContentTypeResponseModelBaseMediaTypePropertyTypeResponseModelMediaTypePropertyTypeContainerResponseModel"
"$ref": "#/components/schemas/ContentTypeForMediaTypeResponseModel"
}
],
"additionalProperties": false
@@ -22148,7 +22157,7 @@
},
"additionalProperties": false
},
"UpdateContentRequestModelBaseDocumentValueModelDocumentVariantRequestModel": {
"UpdateContentForDocumentRequestModel": {
"type": "object",
"properties": {
"values": {
@@ -22174,7 +22183,7 @@
},
"additionalProperties": false
},
"UpdateContentRequestModelBaseMediaValueModelMediaVariantRequestModel": {
"UpdateContentForMediaRequestModel": {
"type": "object",
"properties": {
"values": {
@@ -22200,7 +22209,7 @@
},
"additionalProperties": false
},
"UpdateContentTypeRequestModelBaseUpdateDocumentTypePropertyTypeRequestModelUpdateDocumentTypePropertyTypeContainerRequestModel": {
"UpdateContentTypeForDocumentTypeRequestModel": {
"type": "object",
"properties": {
"alias": {
@@ -22271,7 +22280,7 @@
},
"additionalProperties": false
},
"UpdateContentTypeRequestModelBaseUpdateMediaTypePropertyTypeRequestModelUpdateMediaTypePropertyTypeContainerRequestModel": {
"UpdateContentTypeForMediaTypeRequestModel": {
"type": "object",
"properties": {
"alias": {
@@ -22376,7 +22385,7 @@
"type": "object",
"allOf": [
{
"$ref": "#/components/schemas/UpdateContentRequestModelBaseDocumentValueModelDocumentVariantRequestModel"
"$ref": "#/components/schemas/UpdateContentForDocumentRequestModel"
}
],
"properties": {
@@ -22410,7 +22419,7 @@
"type": "object",
"allOf": [
{
"$ref": "#/components/schemas/UpdateContentTypeRequestModelBaseUpdateDocumentTypePropertyTypeRequestModelUpdateDocumentTypePropertyTypeContainerRequestModel"
"$ref": "#/components/schemas/UpdateContentTypeForDocumentTypeRequestModel"
}
],
"properties": {
@@ -22467,7 +22476,7 @@
"type": "object",
"allOf": [
{
"$ref": "#/components/schemas/UpdateContentRequestModelBaseMediaValueModelMediaVariantRequestModel"
"$ref": "#/components/schemas/UpdateContentForMediaRequestModel"
}
],
"additionalProperties": false
@@ -22494,7 +22503,7 @@
"type": "object",
"allOf": [
{
"$ref": "#/components/schemas/UpdateContentTypeRequestModelBaseUpdateMediaTypePropertyTypeRequestModelUpdateMediaTypePropertyTypeContainerRequestModel"
"$ref": "#/components/schemas/UpdateContentTypeForMediaTypeRequestModel"
}
],
"additionalProperties": false

View File

@@ -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; }

View File

@@ -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>();

View File

@@ -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; }

View File

@@ -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>
{

View File

@@ -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>();

View File

@@ -1,7 +1,9 @@
using Umbraco.Cms.Api.Common.Attributes;
using Umbraco.Cms.Api.Management.ViewModels.ContentType;
namespace Umbraco.Cms.Api.Management.ViewModels.DocumentType;
[ShortGenericSchemaName<UpdateDocumentTypePropertyTypeRequestModel, UpdateDocumentTypePropertyTypeContainerRequestModel>("UpdateContentTypeForDocumentTypeRequestModel")]
public class UpdateDocumentTypeRequestModel
: UpdateContentTypeRequestModelBase<UpdateDocumentTypePropertyTypeRequestModel, UpdateDocumentTypePropertyTypeContainerRequestModel>
{

View File

@@ -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.Media;
[ShortGenericSchemaName<MediaValueModel, MediaVariantRequestModel>("CreateContentForMediaRequestModel")]
public class CreateMediaRequestModel : CreateContentRequestModelBase<MediaValueModel, MediaVariantRequestModel>
{
public Guid ContentTypeId { get; set; }

View File

@@ -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.Media;
[ShortGenericSchemaName<MediaValueModel, MediaVariantRequestModel>("UpdateContentForMediaRequestModel")]
public class UpdateMediaRequestModel : UpdateContentRequestModelBase<MediaValueModel, MediaVariantRequestModel>
{
}

View File

@@ -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.MediaType;
[ShortGenericSchemaName<CreateMediaTypePropertyTypeRequestModel, CreateMediaTypePropertyTypeContainerRequestModel>("CreateContentTypeForMediaTypeRequestModel")]
public class CreateMediaTypeRequestModel
: CreateContentTypeRequestModelBase<CreateMediaTypePropertyTypeRequestModel, CreateMediaTypePropertyTypeContainerRequestModel>
{

View File

@@ -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.MediaType;
[ShortGenericSchemaName<MediaTypePropertyTypeResponseModel, MediaTypePropertyTypeContainerResponseModel>("ContentTypeForMediaTypeResponseModel")]
public class MediaTypeResponseModel : ContentTypeResponseModelBase<MediaTypePropertyTypeResponseModel, MediaTypePropertyTypeContainerResponseModel>
{
}

View File

@@ -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.MediaType;
[ShortGenericSchemaName<UpdateMediaTypePropertyTypeRequestModel, UpdateMediaTypePropertyTypeContainerRequestModel>("UpdateContentTypeForMediaTypeRequestModel")]
public class UpdateMediaTypeRequestModel
: UpdateContentTypeRequestModelBase<UpdateMediaTypePropertyTypeRequestModel, UpdateMediaTypePropertyTypeContainerRequestModel>
{