@@ -120,8 +120,15 @@ internal static void ApplyValidationAttributes(this JsonNode schema, IEnumerable
120120 }
121121 else if ( attribute is MinLengthAttribute minLengthAttribute )
122122 {
123- var targetKey = schema [ OpenApiSchemaKeywords . TypeKeyword ] ? . GetValue < string > ( ) == "array" ? OpenApiSchemaKeywords . MinItemsKeyword : OpenApiSchemaKeywords . MinLengthKeyword ;
124- schema [ targetKey ] = minLengthAttribute . Length ;
123+ if ( MapJsonNodeToSchemaType ( schema [ OpenApiSchemaKeywords . TypeKeyword ] ) is { } schemaTypes &&
124+ schemaTypes . HasFlag ( JsonSchemaType . Array ) )
125+ {
126+ schema [ OpenApiSchemaKeywords . MinItemsKeyword ] = minLengthAttribute . Length ;
127+ }
128+ else
129+ {
130+ schema [ OpenApiSchemaKeywords . MinLengthKeyword ] = minLengthAttribute . Length ;
131+ }
125132 }
126133 else if ( attribute is LengthAttribute lengthAttribute )
127134 {
@@ -191,14 +198,13 @@ internal static void ApplyPrimitiveTypesAndFormats(this JsonNode schema, JsonSch
191198 var underlyingType = Nullable . GetUnderlyingType ( type ) ;
192199 if ( _simpleTypeToOpenApiSchema . TryGetValue ( underlyingType ?? type , out var openApiSchema ) )
193200 {
194- schema [ OpenApiSchemaKeywords . NullableKeyword ] = openApiSchema . Nullable || ( schema [ OpenApiSchemaKeywords . TypeKeyword ] is JsonArray schemaType && schemaType . GetValues < string > ( ) . Contains ( "null" ) ) ;
195- schema [ OpenApiSchemaKeywords . TypeKeyword ] = openApiSchema . Type . ToString ( ) ;
201+ if ( underlyingType != null && MapJsonNodeToSchemaType ( schema [ OpenApiSchemaKeywords . TypeKeyword ] ) is { } schemaTypes &&
202+ ! schemaTypes . HasFlag ( JsonSchemaType . Null ) )
203+ {
204+ schema [ OpenApiSchemaKeywords . TypeKeyword ] = ( schemaTypes | JsonSchemaType . Null ) . ToString ( ) ;
205+ }
196206 schema [ OpenApiSchemaKeywords . FormatKeyword ] = openApiSchema . Format ;
197207 schema [ OpenApiConstants . SchemaId ] = createSchemaReferenceId ( context . TypeInfo ) ;
198- schema [ OpenApiSchemaKeywords . NullableKeyword ] = underlyingType != null ;
199- // Clear out patterns that the underlying JSON schema generator uses to represent
200- // validations for DateTime, DateTimeOffset, and integers.
201- schema [ OpenApiSchemaKeywords . PatternKeyword ] = null ;
202208 }
203209 }
204210
@@ -334,14 +340,17 @@ internal static void ApplyParameterInfo(this JsonNode schema, ApiParameterDescri
334340 schema . ApplyRouteConstraints ( constraints ) ;
335341 }
336342
337- if ( parameterDescription . Source is { } bindingSource && SupportsNullableProperty ( bindingSource ) )
343+ if ( parameterDescription . Source is { } bindingSource
344+ && SupportsNullableProperty ( bindingSource )
345+ && MapJsonNodeToSchemaType ( schema [ OpenApiSchemaKeywords . TypeKeyword ] ) is { } schemaTypes &&
346+ schemaTypes . HasFlag ( JsonSchemaType . Null ) )
338347 {
339- schema [ OpenApiSchemaKeywords . NullableKeyword ] = false ;
348+ schema [ OpenApiSchemaKeywords . TypeKeyword ] = ( schemaTypes & ~ JsonSchemaType . Null ) . ToString ( ) ;
340349 }
341350
342351 // Parameters sourced from the header, query, route, and/or form cannot be nullable based on our binding
343352 // rules but can be optional.
344- static bool SupportsNullableProperty ( BindingSource bindingSource ) => bindingSource == BindingSource . Header
353+ static bool SupportsNullableProperty ( BindingSource bindingSource ) => bindingSource == BindingSource . Header
345354 || bindingSource == BindingSource . Query
346355 || bindingSource == BindingSource . Path
347356 || bindingSource == BindingSource . Form
@@ -435,9 +444,11 @@ internal static void ApplyNullabilityContextInfo(this JsonNode schema, Parameter
435444
436445 var nullabilityInfoContext = new NullabilityInfoContext ( ) ;
437446 var nullabilityInfo = nullabilityInfoContext . Create ( parameterInfo ) ;
438- if ( nullabilityInfo . WriteState == NullabilityState . Nullable )
447+ if ( nullabilityInfo . WriteState == NullabilityState . Nullable
448+ && MapJsonNodeToSchemaType ( schema [ OpenApiSchemaKeywords . TypeKeyword ] ) is { } schemaTypes
449+ && ! schemaTypes . HasFlag ( JsonSchemaType . Null ) )
439450 {
440- schema [ OpenApiSchemaKeywords . NullableKeyword ] = true ;
451+ schema [ OpenApiSchemaKeywords . TypeKeyword ] = ( schemaTypes | JsonSchemaType . Null ) . ToString ( ) ;
441452 }
442453 }
443454
@@ -452,7 +463,54 @@ internal static void ApplyNullabilityContextInfo(this JsonNode schema, JsonPrope
452463 // all schema (no type, no format, no constraints).
453464 if ( propertyInfo . PropertyType != typeof ( object ) && ( propertyInfo . IsGetNullable || propertyInfo . IsSetNullable ) )
454465 {
455- schema [ OpenApiSchemaKeywords . NullableKeyword ] = true ;
466+ if ( MapJsonNodeToSchemaType ( schema [ OpenApiSchemaKeywords . TypeKeyword ] ) is { } schemaTypes &&
467+ ! schemaTypes . HasFlag ( JsonSchemaType . Null ) )
468+ {
469+ schema [ OpenApiSchemaKeywords . TypeKeyword ] = ( schemaTypes | JsonSchemaType . Null ) . ToString ( ) ;
470+ }
471+ }
472+ }
473+
474+ private static JsonSchemaType ? MapJsonNodeToSchemaType ( JsonNode ? jsonNode )
475+ {
476+ if ( jsonNode is not JsonArray jsonArray )
477+ {
478+ if ( Enum . TryParse < JsonSchemaType > ( jsonNode ? . GetValue < string > ( ) , true , out var openApiSchemaType ) )
479+ {
480+ return openApiSchemaType ;
481+ }
482+
483+ return jsonNode is JsonValue jsonValue && jsonValue . TryGetValue < string > ( out var identifier )
484+ ? ToSchemaType ( identifier )
485+ : null ;
486+ }
487+
488+ JsonSchemaType ? schemaType = null ;
489+
490+ foreach ( var node in jsonArray )
491+ {
492+ if ( node is JsonValue jsonValue && jsonValue . TryGetValue < string > ( out var identifier ) )
493+ {
494+ var type = ToSchemaType ( identifier ) ;
495+ schemaType = schemaType . HasValue ? ( schemaType | type ) : type ;
496+ }
497+ }
498+
499+ return schemaType ;
500+
501+ static JsonSchemaType ToSchemaType ( string identifier )
502+ {
503+ return identifier . ToLowerInvariant ( ) switch
504+ {
505+ "null" => JsonSchemaType . Null ,
506+ "boolean" => JsonSchemaType . Boolean ,
507+ "integer" => JsonSchemaType . Integer ,
508+ "number" => JsonSchemaType . Number ,
509+ "string" => JsonSchemaType . String ,
510+ "array" => JsonSchemaType . Array ,
511+ "object" => JsonSchemaType . Object ,
512+ _ => throw new InvalidOperationException ( $ "Unknown schema type: { identifier } ") ,
513+ } ;
456514 }
457515 }
458516}
0 commit comments