1
- using System . Text . Json ;
1
+ using System . Reflection ;
2
2
using JsonApiDotNetCore . OpenApi . JsonApiMetadata ;
3
3
using JsonApiDotNetCore . OpenApi . JsonApiObjects . Relationships ;
4
4
using JsonApiDotNetCore . OpenApi . JsonApiObjects . ResourceObjects ;
@@ -35,14 +35,14 @@ internal sealed class ResourceFieldObjectSchemaBuilder
35
35
private readonly SchemaGenerator _defaultSchemaGenerator ;
36
36
private readonly ResourceTypeSchemaGenerator _resourceTypeSchemaGenerator ;
37
37
private readonly SchemaRepository _resourceSchemaRepository = new ( ) ;
38
- private readonly NullableReferenceSchemaGenerator _nullableReferenceSchemaGenerator ;
39
38
private readonly IDictionary < string , OpenApiSchema > _schemasForResourceFields ;
40
39
private readonly ResourceFieldValidationMetadataProvider _resourceFieldValidationMetadataProvider ;
41
40
private readonly RelationshipTypeFactory _relationshipTypeFactory ;
41
+ private readonly NullabilityInfoContext _nullabilityInfoContext = new ( ) ;
42
42
private readonly ResourceObjectDocumentationReader _resourceObjectDocumentationReader ;
43
43
44
44
public ResourceFieldObjectSchemaBuilder ( ResourceTypeInfo resourceTypeInfo , ISchemaRepositoryAccessor schemaRepositoryAccessor ,
45
- SchemaGenerator defaultSchemaGenerator , ResourceTypeSchemaGenerator resourceTypeSchemaGenerator , JsonNamingPolicy ? namingPolicy ,
45
+ SchemaGenerator defaultSchemaGenerator , ResourceTypeSchemaGenerator resourceTypeSchemaGenerator ,
46
46
ResourceFieldValidationMetadataProvider resourceFieldValidationMetadataProvider )
47
47
{
48
48
ArgumentGuard . NotNull ( resourceTypeInfo ) ;
@@ -57,7 +57,6 @@ public ResourceFieldObjectSchemaBuilder(ResourceTypeInfo resourceTypeInfo, ISche
57
57
_resourceTypeSchemaGenerator = resourceTypeSchemaGenerator ;
58
58
_resourceFieldValidationMetadataProvider = resourceFieldValidationMetadataProvider ;
59
59
60
- _nullableReferenceSchemaGenerator = new NullableReferenceSchemaGenerator ( schemaRepositoryAccessor , namingPolicy ) ;
61
60
_relationshipTypeFactory = new RelationshipTypeFactory ( resourceFieldValidationMetadataProvider ) ;
62
61
_schemasForResourceFields = GetFieldSchemas ( ) ;
63
62
_resourceObjectDocumentationReader = new ResourceObjectDocumentationReader ( ) ;
@@ -86,7 +85,15 @@ public void SetMembersOfAttributesObject(OpenApiSchema fullSchemaForAttributesOb
86
85
87
86
if ( matchingAttribute != null && matchingAttribute . Capabilities . HasFlag ( requiredCapability ) )
88
87
{
89
- AddAttributeSchemaToResourceObject ( matchingAttribute , fullSchemaForAttributesObject , resourceFieldSchema ) ;
88
+ bool isPrimitiveOpenApiType = resourceFieldSchema . AllOf . IsNullOrEmpty ( ) ;
89
+
90
+ // Types like enum and complex attributes are not primitive and handled as reference schemas.
91
+ if ( ! isPrimitiveOpenApiType )
92
+ {
93
+ EnsureAttributeSchemaIsExposed ( resourceFieldSchema , matchingAttribute ) ;
94
+ }
95
+
96
+ fullSchemaForAttributesObject . Properties [ matchingAttribute . PublicName ] = resourceFieldSchema ;
90
97
91
98
resourceFieldSchema . Nullable = _resourceFieldValidationMetadataProvider . IsNullable ( matchingAttribute ) ;
92
99
@@ -107,21 +114,33 @@ private static AttrCapabilities GetRequiredCapabilityForAttributes(Type resource
107
114
resourceObjectOpenType == typeof ( ResourceObjectInPatchRequest < > ) ? AttrCapabilities . AllowChange : throw new UnreachableCodeException ( ) ;
108
115
}
109
116
110
- private void AddAttributeSchemaToResourceObject ( AttrAttribute attribute , OpenApiSchema attributesObjectSchema , OpenApiSchema resourceAttributeSchema )
117
+ private void EnsureAttributeSchemaIsExposed ( OpenApiSchema attributeReferenceSchema , AttrAttribute attribute )
111
118
{
112
- if ( resourceAttributeSchema . Reference != null && ! _schemaRepositoryAccessor . Current . TryLookupByType ( attribute . Property . PropertyType , out _ ) )
119
+ Type nonNullableTypeInPropertyType = GetRepresentedTypeForAttributeSchema ( attribute ) ;
120
+
121
+ if ( _schemaRepositoryAccessor . Current . TryLookupByType ( nonNullableTypeInPropertyType , out _ ) )
113
122
{
114
- ExposeSchema ( resourceAttributeSchema . Reference , attribute . Property . PropertyType ) ;
123
+ return ;
115
124
}
116
125
117
- attributesObjectSchema . Properties . Add ( attribute . PublicName , resourceAttributeSchema ) ;
126
+ string schemaId = attributeReferenceSchema . UnwrapExtendedReferenceSchema ( ) . Reference . Id ;
127
+
128
+ OpenApiSchema fullSchema = _resourceSchemaRepository . Schemas [ schemaId ] ;
129
+ _schemaRepositoryAccessor . Current . AddDefinition ( schemaId , fullSchema ) ;
130
+ _schemaRepositoryAccessor . Current . RegisterType ( nonNullableTypeInPropertyType , schemaId ) ;
118
131
}
119
132
120
- private void ExposeSchema ( OpenApiReference openApiReference , Type typeRepresentedBySchema )
133
+ private Type GetRepresentedTypeForAttributeSchema ( AttrAttribute attribute )
121
134
{
122
- OpenApiSchema fullSchema = _resourceSchemaRepository . Schemas [ openApiReference . Id ] ;
123
- _schemaRepositoryAccessor . Current . AddDefinition ( openApiReference . Id , fullSchema ) ;
124
- _schemaRepositoryAccessor . Current . RegisterType ( typeRepresentedBySchema , openApiReference . Id ) ;
135
+ NullabilityInfo attributeNullabilityInfo = _nullabilityInfoContext . Create ( attribute . Property ) ;
136
+
137
+ bool isNullable = attributeNullabilityInfo is { ReadState : NullabilityState . Nullable , WriteState : NullabilityState . Nullable } ;
138
+
139
+ Type nonNullableTypeInPropertyType = isNullable
140
+ ? Nullable . GetUnderlyingType ( attribute . Property . PropertyType ) ?? attribute . Property . PropertyType
141
+ : attribute . Property . PropertyType ;
142
+
143
+ return nonNullableTypeInPropertyType ;
125
144
}
126
145
127
146
private bool IsFieldRequired ( ResourceFieldAttribute field )
@@ -135,18 +154,14 @@ public void SetMembersOfRelationshipsObject(OpenApiSchema fullSchemaForRelations
135
154
{
136
155
ArgumentGuard . NotNull ( fullSchemaForRelationshipsObject ) ;
137
156
138
- foreach ( ( string fieldName , OpenApiSchema resourceFieldSchema ) in _schemasForResourceFields )
157
+ foreach ( string fieldName in _schemasForResourceFields . Keys )
139
158
{
140
159
RelationshipAttribute ? matchingRelationship = _resourceTypeInfo . ResourceType . FindRelationshipByPublicName ( fieldName ) ;
141
160
142
161
if ( matchingRelationship != null )
143
162
{
144
163
EnsureResourceIdentifierObjectSchemaExists ( matchingRelationship ) ;
145
164
AddRelationshipSchemaToResourceObject ( matchingRelationship , fullSchemaForRelationshipsObject ) ;
146
-
147
- // This currently has no effect because $ref cannot be combined with other elements in OAS 3.0.
148
- // This can be worked around by using the allOf operator. See https://github.com/OAI/OpenAPI-Specification/issues/1514.
149
- resourceFieldSchema . Description = _resourceObjectDocumentationReader . GetDocumentationForRelationship ( matchingRelationship ) ;
150
165
}
151
166
}
152
167
}
@@ -182,9 +197,19 @@ private void AddRelationshipSchemaToResourceObject(RelationshipAttribute relatio
182
197
{
183
198
Type relationshipSchemaType = GetRelationshipSchemaType ( relationship , _resourceTypeInfo . ResourceObjectOpenType ) ;
184
199
185
- OpenApiSchema relationshipSchema = GetReferenceSchemaForRelationship ( relationshipSchemaType ) ?? CreateRelationshipSchema ( relationshipSchemaType ) ;
200
+ OpenApiSchema referenceSchemaForRelationship =
201
+ GetReferenceSchemaForRelationship ( relationshipSchemaType ) ?? CreateRelationshipReferenceSchema ( relationshipSchemaType ) ;
202
+
203
+ var extendedReferenceSchemaForRelationship = new OpenApiSchema
204
+ {
205
+ AllOf = new List < OpenApiSchema >
206
+ {
207
+ referenceSchemaForRelationship
208
+ } ,
209
+ Description = _resourceObjectDocumentationReader . GetDocumentationForRelationship ( relationship )
210
+ } ;
186
211
187
- fullSchemaForRelationshipsObject . Properties . Add ( relationship . PublicName , relationshipSchema ) ;
212
+ fullSchemaForRelationshipsObject . Properties . Add ( relationship . PublicName , extendedReferenceSchemaForRelationship ) ;
188
213
189
214
if ( IsFieldRequired ( relationship ) )
190
215
{
@@ -205,15 +230,15 @@ private Type GetRelationshipSchemaType(RelationshipAttribute relationship, Type
205
230
return referenceSchema ;
206
231
}
207
232
208
- private OpenApiSchema CreateRelationshipSchema ( Type relationshipSchemaType )
233
+ private OpenApiSchema CreateRelationshipReferenceSchema ( Type relationshipSchemaType )
209
234
{
210
235
OpenApiSchema referenceSchema = _defaultSchemaGenerator . GenerateSchema ( relationshipSchemaType , _schemaRepositoryAccessor . Current ) ;
211
236
212
237
OpenApiSchema fullSchema = _schemaRepositoryAccessor . Current . Schemas [ referenceSchema . Reference . Id ] ;
213
238
214
239
if ( IsDataPropertyNullableInRelationshipSchemaType ( relationshipSchemaType ) )
215
240
{
216
- fullSchema . Properties [ JsonApiPropertyName . Data ] = _nullableReferenceSchemaGenerator . GenerateSchema ( fullSchema . Properties [ JsonApiPropertyName . Data ] ) ;
241
+ fullSchema . Properties [ JsonApiPropertyName . Data ] . Nullable = true ;
217
242
}
218
243
219
244
if ( IsRelationshipInResponseType ( relationshipSchemaType ) )
0 commit comments