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

OpenAPI: Add type hints on 'id' parameters and properties #1595

Merged
merged 1 commit into from
Jul 14, 2024
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

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ internal sealed class DataSchemaGenerator
private readonly SchemaGenerator _defaultSchemaGenerator;
private readonly GenerationCacheSchemaGenerator _generationCacheSchemaGenerator;
private readonly ResourceTypeSchemaGenerator _resourceTypeSchemaGenerator;
private readonly ResourceIdSchemaGenerator _resourceIdSchemaGenerator;
private readonly ResourceIdentifierSchemaGenerator _resourceIdentifierSchemaGenerator;
private readonly LinksVisibilitySchemaGenerator _linksVisibilitySchemaGenerator;
private readonly IResourceGraph _resourceGraph;
Expand All @@ -34,14 +35,15 @@ internal sealed class DataSchemaGenerator
private readonly ResourceDocumentationReader _resourceDocumentationReader;

public DataSchemaGenerator(SchemaGenerator defaultSchemaGenerator, GenerationCacheSchemaGenerator generationCacheSchemaGenerator,
ResourceTypeSchemaGenerator resourceTypeSchemaGenerator, ResourceIdentifierSchemaGenerator resourceIdentifierSchemaGenerator,
LinksVisibilitySchemaGenerator linksVisibilitySchemaGenerator, IResourceGraph resourceGraph, IJsonApiOptions options,
ResourceFieldValidationMetadataProvider resourceFieldValidationMetadataProvider, RelationshipTypeFactory relationshipTypeFactory,
ResourceDocumentationReader resourceDocumentationReader)
ResourceTypeSchemaGenerator resourceTypeSchemaGenerator, ResourceIdSchemaGenerator resourceIdSchemaGenerator,
ResourceIdentifierSchemaGenerator resourceIdentifierSchemaGenerator, LinksVisibilitySchemaGenerator linksVisibilitySchemaGenerator,
IResourceGraph resourceGraph, IJsonApiOptions options, ResourceFieldValidationMetadataProvider resourceFieldValidationMetadataProvider,
RelationshipTypeFactory relationshipTypeFactory, ResourceDocumentationReader resourceDocumentationReader)
{
ArgumentGuard.NotNull(defaultSchemaGenerator);
ArgumentGuard.NotNull(generationCacheSchemaGenerator);
ArgumentGuard.NotNull(resourceTypeSchemaGenerator);
ArgumentGuard.NotNull(resourceIdSchemaGenerator);
ArgumentGuard.NotNull(resourceIdentifierSchemaGenerator);
ArgumentGuard.NotNull(linksVisibilitySchemaGenerator);
ArgumentGuard.NotNull(resourceGraph);
Expand All @@ -53,6 +55,7 @@ public DataSchemaGenerator(SchemaGenerator defaultSchemaGenerator, GenerationCac
_defaultSchemaGenerator = defaultSchemaGenerator;
_generationCacheSchemaGenerator = generationCacheSchemaGenerator;
_resourceTypeSchemaGenerator = resourceTypeSchemaGenerator;
_resourceIdSchemaGenerator = resourceIdSchemaGenerator;
_resourceIdentifierSchemaGenerator = resourceIdentifierSchemaGenerator;
_linksVisibilitySchemaGenerator = linksVisibilitySchemaGenerator;
_resourceGraph = resourceGraph;
Expand Down Expand Up @@ -88,6 +91,8 @@ public OpenApiSchema GenerateSchema(Type resourceDataConstructedType, SchemaRepo
SetResourceType(fullSchemaForResourceData, resourceTypeInfo.ResourceType, schemaRepository);
}

SetResourceId(fullSchemaForDerivedType, resourceTypeInfo.ResourceType, schemaRepository);

fullSchemaForResourceData.Description = _resourceDocumentationReader.GetDocumentationForType(resourceTypeInfo.ResourceType);

var fieldSchemaBuilder = new ResourceFieldSchemaBuilder(_defaultSchemaGenerator, _resourceIdentifierSchemaGenerator, _linksVisibilitySchemaGenerator,
Expand Down Expand Up @@ -162,6 +167,15 @@ private void SetResourceType(OpenApiSchema fullSchemaForResourceData, ResourceTy
fullSchemaForResourceData.Properties[JsonApiPropertyName.Type] = referenceSchema.WrapInExtendedSchema();
}

private void SetResourceId(OpenApiSchema fullSchemaForResourceData, ResourceType resourceType, SchemaRepository schemaRepository)
{
if (fullSchemaForResourceData.Properties.ContainsKey(JsonApiPropertyName.Id))
{
OpenApiSchema idSchema = _resourceIdSchemaGenerator.GenerateSchema(resourceType, schemaRepository);
fullSchemaForResourceData.Properties[JsonApiPropertyName.Id] = idSchema;
}
}

private void SetResourceAttributes(OpenApiSchema fullSchemaForResourceData, bool forRequestSchema, ResourceFieldSchemaBuilder builder,
SchemaRepository schemaRepository)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.OpenApi.JsonApiObjects.ResourceObjects;
using JsonApiDotNetCore.Resources.Annotations;
using Microsoft.OpenApi.Models;
Expand All @@ -19,19 +20,23 @@ internal sealed class RelationshipIdentifierSchemaGenerator

private readonly SchemaGenerator _defaultSchemaGenerator;
private readonly ResourceTypeSchemaGenerator _resourceTypeSchemaGenerator;
private readonly ResourceIdSchemaGenerator _resourceIdSchemaGenerator;
private readonly RelationshipNameSchemaGenerator _relationshipNameSchemaGenerator;
private readonly JsonApiSchemaIdSelector _jsonApiSchemaIdSelector;

public RelationshipIdentifierSchemaGenerator(SchemaGenerator defaultSchemaGenerator, ResourceTypeSchemaGenerator resourceTypeSchemaGenerator,
RelationshipNameSchemaGenerator relationshipNameSchemaGenerator, JsonApiSchemaIdSelector jsonApiSchemaIdSelector)
ResourceIdSchemaGenerator resourceIdSchemaGenerator, RelationshipNameSchemaGenerator relationshipNameSchemaGenerator,
JsonApiSchemaIdSelector jsonApiSchemaIdSelector)
{
ArgumentGuard.NotNull(defaultSchemaGenerator);
ArgumentGuard.NotNull(resourceTypeSchemaGenerator);
ArgumentGuard.NotNull(resourceIdSchemaGenerator);
ArgumentGuard.NotNull(relationshipNameSchemaGenerator);
ArgumentGuard.NotNull(jsonApiSchemaIdSelector);

_defaultSchemaGenerator = defaultSchemaGenerator;
_resourceTypeSchemaGenerator = resourceTypeSchemaGenerator;
_resourceIdSchemaGenerator = resourceIdSchemaGenerator;
_relationshipNameSchemaGenerator = relationshipNameSchemaGenerator;
_jsonApiSchemaIdSelector = jsonApiSchemaIdSelector;
}
Expand Down Expand Up @@ -65,11 +70,9 @@ public OpenApiSchema GenerateSchema(RelationshipAttribute relationship, SchemaRe
OpenApiSchema referenceSchemaForIdentifier = _defaultSchemaGenerator.GenerateSchema(relationshipIdentifierConstructedType, schemaRepository);
OpenApiSchema fullSchemaForIdentifier = schemaRepository.Schemas[referenceSchemaForIdentifier.Reference.Id];

OpenApiSchema referenceSchemaForResourceType = _resourceTypeSchemaGenerator.GenerateSchema(relationship.LeftType, schemaRepository);
fullSchemaForIdentifier.Properties[JsonApiPropertyName.Type] = referenceSchemaForResourceType.WrapInExtendedSchema();

OpenApiSchema referenceSchemaForRelationshipName = _relationshipNameSchemaGenerator.GenerateSchema(relationship, schemaRepository);
fullSchemaForIdentifier.Properties[JsonApiPropertyName.Relationship] = referenceSchemaForRelationshipName.WrapInExtendedSchema();
SetResourceType(fullSchemaForIdentifier, relationship.LeftType, schemaRepository);
SetResourceId(fullSchemaForIdentifier, relationship.LeftType, schemaRepository);
SetRelationship(fullSchemaForIdentifier, relationship, schemaRepository);

#if NET6_0
fullSchemaForIdentifier.ReorderProperties(RelationshipIdentifierPropertyNamesInOrder);
Expand All @@ -80,4 +83,22 @@ public OpenApiSchema GenerateSchema(RelationshipAttribute relationship, SchemaRe

return referenceSchemaForIdentifier;
}

private void SetResourceType(OpenApiSchema fullSchemaForIdentifier, ResourceType resourceType, SchemaRepository schemaRepository)
{
OpenApiSchema referenceSchema = _resourceTypeSchemaGenerator.GenerateSchema(resourceType, schemaRepository);
fullSchemaForIdentifier.Properties[JsonApiPropertyName.Type] = referenceSchema.WrapInExtendedSchema();
}

private void SetResourceId(OpenApiSchema fullSchemaForResourceData, ResourceType resourceType, SchemaRepository schemaRepository)
{
OpenApiSchema idSchema = _resourceIdSchemaGenerator.GenerateSchema(resourceType, schemaRepository);
fullSchemaForResourceData.Properties[JsonApiPropertyName.Id] = idSchema;
}

private void SetRelationship(OpenApiSchema fullSchemaForIdentifier, RelationshipAttribute relationship, SchemaRepository schemaRepository)
{
OpenApiSchema referenceSchema = _relationshipNameSchemaGenerator.GenerateSchema(relationship, schemaRepository);
fullSchemaForIdentifier.Properties[JsonApiPropertyName.Relationship] = referenceSchema.WrapInExtendedSchema();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using JsonApiDotNetCore.Configuration;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;

namespace JsonApiDotNetCore.OpenApi.SchemaGenerators.Components;

internal sealed class ResourceIdSchemaGenerator
{
private readonly SchemaGenerator _defaultSchemaGenerator;

public ResourceIdSchemaGenerator(SchemaGenerator defaultSchemaGenerator)
{
ArgumentGuard.NotNull(defaultSchemaGenerator);

_defaultSchemaGenerator = defaultSchemaGenerator;
}

public OpenApiSchema GenerateSchema(ResourceType resourceType, SchemaRepository schemaRepository)
{
return GenerateSchema(resourceType.IdentityClrType, schemaRepository);
}

public OpenApiSchema GenerateSchema(Type resourceIdClrType, SchemaRepository schemaRepository)
{
ArgumentGuard.NotNull(resourceIdClrType);
ArgumentGuard.NotNull(schemaRepository);

OpenApiSchema idSchema = _defaultSchemaGenerator.GenerateSchema(resourceIdClrType, schemaRepository);
idSchema.Type = "string";

if (resourceIdClrType != typeof(string))
{
// When using string IDs, it's discouraged (but possible) to use an empty string as primary key value, because
// some things won't work: get-by-id, update and delete resource are impossible, and rendered links are unusable.
// For other ID types, provide the length constraint as a fallback in case the type hint isn't recognized.
idSchema.MinLength = 1;
}

return idSchema;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,20 @@ internal sealed class ResourceIdentifierSchemaGenerator
private readonly SchemaGenerator _defaultSchemaGenerator;
private readonly GenerationCacheSchemaGenerator _generationCacheSchemaGenerator;
private readonly ResourceTypeSchemaGenerator _resourceTypeSchemaGenerator;
private readonly ResourceIdSchemaGenerator _resourceIdSchemaGenerator;

public ResourceIdentifierSchemaGenerator(SchemaGenerator defaultSchemaGenerator, GenerationCacheSchemaGenerator generationCacheSchemaGenerator,
ResourceTypeSchemaGenerator resourceTypeSchemaGenerator)
ResourceTypeSchemaGenerator resourceTypeSchemaGenerator, ResourceIdSchemaGenerator resourceIdSchemaGenerator)
{
ArgumentGuard.NotNull(defaultSchemaGenerator);
ArgumentGuard.NotNull(generationCacheSchemaGenerator);
ArgumentGuard.NotNull(resourceTypeSchemaGenerator);
ArgumentGuard.NotNull(resourceIdSchemaGenerator);

_defaultSchemaGenerator = defaultSchemaGenerator;
_generationCacheSchemaGenerator = generationCacheSchemaGenerator;
_resourceTypeSchemaGenerator = resourceTypeSchemaGenerator;
_resourceIdSchemaGenerator = resourceIdSchemaGenerator;
}

public OpenApiSchema GenerateSchema(ResourceType resourceType, bool forRequestSchema, SchemaRepository schemaRepository)
Expand All @@ -42,10 +45,22 @@ public OpenApiSchema GenerateSchema(ResourceType resourceType, bool forRequestSc
fullSchemaForIdentifier.Required.Add(JsonApiPropertyName.Id);
}

OpenApiSchema referenceSchemaForResourceType = _resourceTypeSchemaGenerator.GenerateSchema(resourceType, schemaRepository);
fullSchemaForIdentifier.Properties[JsonApiPropertyName.Type] = referenceSchemaForResourceType.WrapInExtendedSchema();
SetResourceType(fullSchemaForIdentifier, resourceType, schemaRepository);
SetResourceId(fullSchemaForIdentifier, resourceType, schemaRepository);
}

return referenceSchemaForIdentifier;
}

private void SetResourceType(OpenApiSchema fullSchemaForIdentifier, ResourceType resourceType, SchemaRepository schemaRepository)
{
OpenApiSchema referenceSchema = _resourceTypeSchemaGenerator.GenerateSchema(resourceType, schemaRepository);
fullSchemaForIdentifier.Properties[JsonApiPropertyName.Type] = referenceSchema.WrapInExtendedSchema();
}

private void SetResourceId(OpenApiSchema fullSchemaForResourceData, ResourceType resourceType, SchemaRepository schemaRepository)
{
OpenApiSchema idSchema = _resourceIdSchemaGenerator.GenerateSchema(resourceType, schemaRepository);
fullSchemaForResourceData.Properties[JsonApiPropertyName.Id] = idSchema;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Reflection;
using JsonApiDotNetCore.Controllers;
using JsonApiDotNetCore.OpenApi.SchemaGenerators.Bodies;
using JsonApiDotNetCore.OpenApi.SchemaGenerators.Components;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
Expand All @@ -9,17 +10,15 @@ namespace JsonApiDotNetCore.OpenApi.SchemaGenerators;

internal sealed class JsonApiSchemaGenerator : ISchemaGenerator
{
private static readonly OpenApiSchema IdTypeSchema = new()
{
Type = "string"
};

private readonly ResourceIdSchemaGenerator _resourceIdSchemaGenerator;
private readonly ICollection<BodySchemaGenerator> _bodySchemaGenerators;

public JsonApiSchemaGenerator(IEnumerable<BodySchemaGenerator> bodySchemaGenerators)
public JsonApiSchemaGenerator(ResourceIdSchemaGenerator resourceIdSchemaGenerator, IEnumerable<BodySchemaGenerator> bodySchemaGenerators)
{
ArgumentGuard.NotNull(resourceIdSchemaGenerator);
ArgumentGuard.NotNull(bodySchemaGenerators);

_resourceIdSchemaGenerator = resourceIdSchemaGenerator;
_bodySchemaGenerators = bodySchemaGenerators.ToArray();
}

Expand All @@ -31,7 +30,7 @@ public OpenApiSchema GenerateSchema(Type modelType, SchemaRepository schemaRepos

if (parameterInfo is { Name: "id" } && IsJsonApiParameter(parameterInfo))
{
return IdTypeSchema;
return _resourceIdSchemaGenerator.GenerateSchema(modelType, schemaRepository);
}

BodySchemaGenerator schemaGenerator = GetBodySchemaGenerator(modelType);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ private static void AddSchemaGenerators(IServiceCollection services)

services.TryAddSingleton<AtomicOperationCodeSchemaGenerator>();
services.TryAddSingleton<ResourceTypeSchemaGenerator>();
services.TryAddSingleton<ResourceIdSchemaGenerator>();
services.TryAddSingleton<MetaSchemaGenerator>();
services.TryAddSingleton<ResourceIdentifierSchemaGenerator>();
services.TryAddSingleton<RelationshipIdentifierSchemaGenerator>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext =>
Data = new CourseIdentifierInRequest
{
Type = CourseResourceType.Courses,
Id = existingCourse.StringId!
Id = existingCourse.Id
}
},
Student = new ToOneStudentInRequest
Expand Down Expand Up @@ -183,7 +183,7 @@ public async Task Can_create_resource_with_client_generated_ID()
Data = new DataInCreateCourseRequest
{
Type = CourseResourceType.Courses,
Id = newCourse.StringId!,
Id = newCourse.Id,
Attributes = new AttributesInCreateCourseRequest
{
Subject = newCourse.Subject
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ public async Task Can_use_local_IDs()
Data = new DataInCreateCourseRequest
{
Type = CourseResourceType.Courses,
Id = newCourse.StringId!,
Id = newCourse.Id,
Attributes = new AttributesInCreateCourseRequest
{
Subject = newCourse.Subject,
Expand All @@ -94,7 +94,7 @@ public async Task Can_use_local_IDs()
new CourseIdentifierInRequest
{
Type = CourseResourceType.Courses,
Id = newCourse.StringId!
Id = newCourse.Id
}
]
},
Expand Down Expand Up @@ -130,7 +130,7 @@ public async Task Can_use_local_IDs()
Data = new CourseIdentifierInRequest
{
Type = CourseResourceType.Courses,
Id = newCourse.StringId!
Id = newCourse.Id
}
},
Student = new ToOneStudentInRequest
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,12 +123,12 @@ await _testContext.RunOnDatabaseAsync(async dbContext =>
new CourseIdentifierInRequest
{
Type = CourseResourceType.Courses,
Id = existingCourses.ElementAt(0).StringId!
Id = existingCourses.ElementAt(0).Id
},
new CourseIdentifierInRequest
{
Type = CourseResourceType.Courses,
Id = existingCourses.ElementAt(1).StringId!
Id = existingCourses.ElementAt(1).Id
}
]
}
Expand Down Expand Up @@ -187,12 +187,12 @@ await _testContext.RunOnDatabaseAsync(async dbContext =>
new CourseIdentifierInRequest
{
Type = CourseResourceType.Courses,
Id = existingCourses.ElementAt(0).StringId!
Id = existingCourses.ElementAt(0).Id
},
new CourseIdentifierInRequest
{
Type = CourseResourceType.Courses,
Id = existingCourses.ElementAt(1).StringId!
Id = existingCourses.ElementAt(1).Id
}
]
}
Expand Down Expand Up @@ -250,12 +250,12 @@ await _testContext.RunOnDatabaseAsync(async dbContext =>
new CourseIdentifierInRequest
{
Type = CourseResourceType.Courses,
Id = existingTeacher.Teaches.ElementAt(0).StringId!
Id = existingTeacher.Teaches.ElementAt(0).Id
},
new CourseIdentifierInRequest
{
Type = CourseResourceType.Courses,
Id = existingTeacher.Teaches.ElementAt(2).StringId!
Id = existingTeacher.Teaches.ElementAt(2).Id
}
]
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext =>
Data = new CourseIdentifierInRequest
{
Type = CourseResourceType.Courses,
Id = existingCourse.StringId!
Id = existingCourse.Id
}
},
Student = new ToOneStudentInRequest
Expand Down
Loading
Loading