1- using System . Text . Json ;
1+ using System . Reflection ;
22using JsonApiDotNetCore . OpenApi . JsonApiMetadata ;
33using JsonApiDotNetCore . OpenApi . JsonApiObjects . Relationships ;
44using JsonApiDotNetCore . OpenApi . JsonApiObjects . ResourceObjects ;
@@ -35,14 +35,14 @@ internal sealed class ResourceFieldObjectSchemaBuilder
3535 private readonly SchemaGenerator _defaultSchemaGenerator ;
3636 private readonly ResourceTypeSchemaGenerator _resourceTypeSchemaGenerator ;
3737 private readonly SchemaRepository _resourceSchemaRepository = new ( ) ;
38- private readonly NullableReferenceSchemaGenerator _nullableReferenceSchemaGenerator ;
3938 private readonly IDictionary < string , OpenApiSchema > _schemasForResourceFields ;
4039 private readonly ResourceFieldValidationMetadataProvider _resourceFieldValidationMetadataProvider ;
4140 private readonly RelationshipTypeFactory _relationshipTypeFactory ;
41+ private readonly NullabilityInfoContext _nullabilityInfoContext = new ( ) ;
4242 private readonly ResourceObjectDocumentationReader _resourceObjectDocumentationReader ;
4343
4444 public ResourceFieldObjectSchemaBuilder ( ResourceTypeInfo resourceTypeInfo , ISchemaRepositoryAccessor schemaRepositoryAccessor ,
45- SchemaGenerator defaultSchemaGenerator , ResourceTypeSchemaGenerator resourceTypeSchemaGenerator , JsonNamingPolicy ? namingPolicy ,
45+ SchemaGenerator defaultSchemaGenerator , ResourceTypeSchemaGenerator resourceTypeSchemaGenerator ,
4646 ResourceFieldValidationMetadataProvider resourceFieldValidationMetadataProvider )
4747 {
4848 ArgumentGuard . NotNull ( resourceTypeInfo ) ;
@@ -57,7 +57,6 @@ public ResourceFieldObjectSchemaBuilder(ResourceTypeInfo resourceTypeInfo, ISche
5757 _resourceTypeSchemaGenerator = resourceTypeSchemaGenerator ;
5858 _resourceFieldValidationMetadataProvider = resourceFieldValidationMetadataProvider ;
5959
60- _nullableReferenceSchemaGenerator = new NullableReferenceSchemaGenerator ( schemaRepositoryAccessor , namingPolicy ) ;
6160 _relationshipTypeFactory = new RelationshipTypeFactory ( resourceFieldValidationMetadataProvider ) ;
6261 _schemasForResourceFields = GetFieldSchemas ( ) ;
6362 _resourceObjectDocumentationReader = new ResourceObjectDocumentationReader ( ) ;
@@ -86,7 +85,15 @@ public void SetMembersOfAttributesObject(OpenApiSchema fullSchemaForAttributesOb
8685
8786 if ( matchingAttribute != null && matchingAttribute . Capabilities . HasFlag ( requiredCapability ) )
8887 {
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 ;
9097
9198 resourceFieldSchema . Nullable = _resourceFieldValidationMetadataProvider . IsNullable ( matchingAttribute ) ;
9299
@@ -107,21 +114,33 @@ private static AttrCapabilities GetRequiredCapabilityForAttributes(Type resource
107114 resourceObjectOpenType == typeof ( ResourceObjectInPatchRequest < > ) ? AttrCapabilities . AllowChange : throw new UnreachableCodeException ( ) ;
108115 }
109116
110- private void AddAttributeSchemaToResourceObject ( AttrAttribute attribute , OpenApiSchema attributesObjectSchema , OpenApiSchema resourceAttributeSchema )
117+ private void EnsureAttributeSchemaIsExposed ( OpenApiSchema attributeReferenceSchema , AttrAttribute attribute )
111118 {
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 _ ) )
113122 {
114- ExposeSchema ( resourceAttributeSchema . Reference , attribute . Property . PropertyType ) ;
123+ return ;
115124 }
116125
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 ) ;
118131 }
119132
120- private void ExposeSchema ( OpenApiReference openApiReference , Type typeRepresentedBySchema )
133+ private Type GetRepresentedTypeForAttributeSchema ( AttrAttribute attribute )
121134 {
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 ;
125144 }
126145
127146 private bool IsFieldRequired ( ResourceFieldAttribute field )
@@ -135,18 +154,14 @@ public void SetMembersOfRelationshipsObject(OpenApiSchema fullSchemaForRelations
135154 {
136155 ArgumentGuard . NotNull ( fullSchemaForRelationshipsObject ) ;
137156
138- foreach ( ( string fieldName , OpenApiSchema resourceFieldSchema ) in _schemasForResourceFields )
157+ foreach ( string fieldName in _schemasForResourceFields . Keys )
139158 {
140159 RelationshipAttribute ? matchingRelationship = _resourceTypeInfo . ResourceType . FindRelationshipByPublicName ( fieldName ) ;
141160
142161 if ( matchingRelationship != null )
143162 {
144163 EnsureResourceIdentifierObjectSchemaExists ( matchingRelationship ) ;
145164 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 ) ;
150165 }
151166 }
152167 }
@@ -182,9 +197,19 @@ private void AddRelationshipSchemaToResourceObject(RelationshipAttribute relatio
182197 {
183198 Type relationshipSchemaType = GetRelationshipSchemaType ( relationship , _resourceTypeInfo . ResourceObjectOpenType ) ;
184199
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+ } ;
186211
187- fullSchemaForRelationshipsObject . Properties . Add ( relationship . PublicName , relationshipSchema ) ;
212+ fullSchemaForRelationshipsObject . Properties . Add ( relationship . PublicName , extendedReferenceSchemaForRelationship ) ;
188213
189214 if ( IsFieldRequired ( relationship ) )
190215 {
@@ -205,15 +230,15 @@ private Type GetRelationshipSchemaType(RelationshipAttribute relationship, Type
205230 return referenceSchema ;
206231 }
207232
208- private OpenApiSchema CreateRelationshipSchema ( Type relationshipSchemaType )
233+ private OpenApiSchema CreateRelationshipReferenceSchema ( Type relationshipSchemaType )
209234 {
210235 OpenApiSchema referenceSchema = _defaultSchemaGenerator . GenerateSchema ( relationshipSchemaType , _schemaRepositoryAccessor . Current ) ;
211236
212237 OpenApiSchema fullSchema = _schemaRepositoryAccessor . Current . Schemas [ referenceSchema . Reference . Id ] ;
213238
214239 if ( IsDataPropertyNullableInRelationshipSchemaType ( relationshipSchemaType ) )
215240 {
216- fullSchema . Properties [ JsonApiPropertyName . Data ] = _nullableReferenceSchemaGenerator . GenerateSchema ( fullSchema . Properties [ JsonApiPropertyName . Data ] ) ;
241+ fullSchema . Properties [ JsonApiPropertyName . Data ] . Nullable = true ;
217242 }
218243
219244 if ( IsRelationshipInResponseType ( relationshipSchemaType ) )
0 commit comments