Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
2 changes: 0 additions & 2 deletions src/OpenApi/src/Extensions/JsonNodeSchemaExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@ internal static void ApplyValidationAttributes(this JsonNode schema, IEnumerable
{
if (attribute is Base64StringAttribute)
{
schema[OpenApiSchemaKeywords.TypeKeyword] = JsonSchemaType.String.ToString();
schema[OpenApiSchemaKeywords.FormatKeyword] = "byte";
}
else if (attribute is RangeAttribute rangeAttribute)
Expand Down Expand Up @@ -153,7 +152,6 @@ internal static void ApplyValidationAttributes(this JsonNode schema, IEnumerable
}
else if (attribute is UrlAttribute)
{
schema[OpenApiSchemaKeywords.TypeKeyword] = JsonSchemaType.String.ToString();
schema[OpenApiSchemaKeywords.FormatKeyword] = "uri";
}
else if (attribute is StringLengthAttribute stringLengthAttribute)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,236 @@ await VerifyOpenApiDocument(builder, document =>
});
}

[Fact]
public async Task GetOpenApiSchema_Base64StringAttribute_StringProperties()
{
var builder = CreateBuilder();
builder.MapPost("/api", (PropertiesWithDataAnnotations model) => { });

await VerifyOpenApiDocument(builder, document =>
{
var schema = document.Paths["/api"].Operations[HttpMethod.Post].RequestBody.Content.First().Value.Schema;
var nonNullable = schema.Properties["base64StringValue"];
var nullable = schema.Properties["nullableBase64StringValue"];
Assert.Equal(JsonSchemaType.String, nonNullable.Type);
Assert.Equal("byte", nonNullable.Format);
Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, nullable.Type);
Assert.Equal("byte", nullable.Format);
});
}

[Fact]
public async Task GetOpenApiSchema_RangeAttribute_IntProperties()
{
var builder = CreateBuilder();
builder.MapPost("/api", (PropertiesWithDataAnnotations model) => { });

await VerifyOpenApiDocument(builder, document =>
{
var schema = document.Paths["/api"].Operations[HttpMethod.Post].RequestBody.Content.First().Value.Schema;
var nonNullable = schema.Properties["rangeIntValue"];
var nullable = schema.Properties["nullableRangeIntValue"];
Assert.Equal(JsonSchemaType.Integer, nonNullable.Type);
Assert.Equal("int32", nonNullable.Format);
Assert.Equal("1", nonNullable.Minimum);
Assert.Equal("100", nonNullable.Maximum);
Assert.Equal(JsonSchemaType.Integer | JsonSchemaType.Null, nullable.Type);
Assert.Equal("int32", nullable.Format);
Assert.Equal("1", nullable.Minimum);
Assert.Equal("100", nullable.Maximum);
});
}

[Fact]
public async Task GetOpenApiSchema_RangeAttribute_DoubleProperties()
{
var builder = CreateBuilder();
builder.MapPost("/api", (PropertiesWithDataAnnotations model) => { });

await VerifyOpenApiDocument(builder, document =>
{
var schema = document.Paths["/api"].Operations[HttpMethod.Post].RequestBody.Content.First().Value.Schema;
var nonNullable = schema.Properties["rangeDoubleValue"];
var nullable = schema.Properties["nullableRangeDoubleValue"];
Assert.Equal(JsonSchemaType.Number, nonNullable.Type);
Assert.Equal("double", nonNullable.Format);
Assert.Equal("0.1", nonNullable.Minimum as string);
Assert.Equal("99.9", nonNullable.Maximum as string);
Assert.Equal(JsonSchemaType.Number | JsonSchemaType.Null, nullable.Type);
Assert.Equal("double", nullable.Format);
Assert.Equal("0.1", nullable.Minimum as string);
Assert.Equal("99.9", nullable.Maximum as string);
});
}

[Fact]
public async Task GetOpenApiSchema_RegularExpressionAttribute_StringProperties()
{
var builder = CreateBuilder();
builder.MapPost("/api", (PropertiesWithDataAnnotations model) => { });

await VerifyOpenApiDocument(builder, document =>
{
var schema = document.Paths["/api"].Operations[HttpMethod.Post].RequestBody.Content.First().Value.Schema;
var nonNullable = schema.Properties["regexStringValue"];
var nullable = schema.Properties["nullableRegexStringValue"];
Assert.Equal(JsonSchemaType.String, nonNullable.Type);
Assert.Equal("^[A-Z]{3}$", nonNullable.Pattern);
Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, nullable.Type);
Assert.Equal("^[A-Z]{3}$", nullable.Pattern);
});
}

[Fact]
public async Task GetOpenApiSchema_MaxLengthAttribute_StringProperties()
{
var builder = CreateBuilder();
builder.MapPost("/api", (PropertiesWithDataAnnotations model) => { });

await VerifyOpenApiDocument(builder, document =>
{
var schema = document.Paths["/api"].Operations[HttpMethod.Post].RequestBody.Content.First().Value.Schema;
var nonNullable = schema.Properties["maxLengthStringValue"];
var nullable = schema.Properties["nullableMaxLengthStringValue"];
Assert.Equal(JsonSchemaType.String, nonNullable.Type);
Assert.Equal(10, nonNullable.MaxLength);
Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, nullable.Type);
Assert.Equal(10, nullable.MaxLength);
});
}

[Fact]
public async Task GetOpenApiSchema_MaxLengthAttribute_ArrayProperties()
{
var builder = CreateBuilder();
builder.MapPost("/api", (PropertiesWithDataAnnotations model) => { });

await VerifyOpenApiDocument(builder, document =>
{
var schema = document.Paths["/api"].Operations[HttpMethod.Post].RequestBody.Content.First().Value.Schema;
var nonNullable = schema.Properties["maxLengthArrayValue"];
var nullable = schema.Properties["nullableMaxLengthArrayValue"];
Assert.Equal(JsonSchemaType.Array, nonNullable.Type);
Assert.Equal(5, nonNullable.MaxItems);
Assert.Equal(JsonSchemaType.Array | JsonSchemaType.Null, nullable.Type);
Assert.Equal(5, nullable.MaxItems);
});
}

[Fact]
public async Task GetOpenApiSchema_MinLengthAttribute_StringProperties()
{
var builder = CreateBuilder();
builder.MapPost("/api", (PropertiesWithDataAnnotations model) => { });

await VerifyOpenApiDocument(builder, document =>
{
var schema = document.Paths["/api"].Operations[HttpMethod.Post].RequestBody.Content.First().Value.Schema;
var nonNullable = schema.Properties["minLengthStringValue"];
var nullable = schema.Properties["nullableMinLengthStringValue"];
Assert.Equal(JsonSchemaType.String, nonNullable.Type);
Assert.Equal(3, nonNullable.MinLength);
Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, nullable.Type);
Assert.Equal(3, nullable.MinLength);
});
}

[Fact]
public async Task GetOpenApiSchema_MinLengthAttribute_ArrayProperties()
{
var builder = CreateBuilder();
builder.MapPost("/api", (PropertiesWithDataAnnotations model) => { });

await VerifyOpenApiDocument(builder, document =>
{
var schema = document.Paths["/api"].Operations[HttpMethod.Post].RequestBody.Content.First().Value.Schema;
var nonNullable = schema.Properties["minLengthArrayValue"];
var nullable = schema.Properties["nullableMinLengthArrayValue"];
Assert.Equal(JsonSchemaType.Array, nonNullable.Type);
Assert.Equal(2, nonNullable.MinItems);
Assert.Equal(JsonSchemaType.Array | JsonSchemaType.Null, nullable.Type);
Assert.Equal(2, nullable.MinItems);
});
}

[Fact]
public async Task GetOpenApiSchema_LengthAttribute_StringProperties()
{
var builder = CreateBuilder();
builder.MapPost("/api", (PropertiesWithDataAnnotations model) => { });

await VerifyOpenApiDocument(builder, document =>
{
var schema = document.Paths["/api"].Operations[HttpMethod.Post].RequestBody.Content.First().Value.Schema;
var nonNullable = schema.Properties["lengthStringValue"];
var nullable = schema.Properties["nullableLengthStringValue"];
Assert.Equal(JsonSchemaType.String, nonNullable.Type);
Assert.Equal(2, nonNullable.MinLength);
Assert.Equal(8, nonNullable.MaxLength);
Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, nullable.Type);
Assert.Equal(2, nullable.MinLength);
Assert.Equal(8, nullable.MaxLength);
});
}

[Fact]
public async Task GetOpenApiSchema_LengthAttribute_ArrayProperties()
{
var builder = CreateBuilder();
builder.MapPost("/api", (PropertiesWithDataAnnotations model) => { });

await VerifyOpenApiDocument(builder, document =>
{
var schema = document.Paths["/api"].Operations[HttpMethod.Post].RequestBody.Content.First().Value.Schema;
var nonNullable = schema.Properties["lengthArrayValue"];
var nullable = schema.Properties["nullableLengthArrayValue"];
Assert.Equal(JsonSchemaType.Array, nonNullable.Type);
Assert.Equal(1, nonNullable.MinItems);
Assert.Equal(4, nonNullable.MaxItems);
Assert.Equal(JsonSchemaType.Array | JsonSchemaType.Null, nullable.Type);
Assert.Equal(1, nullable.MinItems);
Assert.Equal(4, nullable.MaxItems);
});
}

[Fact]
public async Task GetOpenApiSchema_UrlAttribute_StringProperties()
{
var builder = CreateBuilder();
builder.MapPost("/api", (PropertiesWithDataAnnotations model) => { });

await VerifyOpenApiDocument(builder, document =>
{
var schema = document.Paths["/api"].Operations[HttpMethod.Post].RequestBody.Content.First().Value.Schema;
var nonNullable = schema.Properties["urlStringValue"];
var nullable = schema.Properties["nullableUrlStringValue"];
Assert.Equal(JsonSchemaType.String, nonNullable.Type);
Assert.Equal("uri", nonNullable.Format);
Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, nullable.Type);
Assert.Equal("uri", nullable.Format);
});
}

[Fact]
public async Task GetOpenApiSchema_StringLengthAttribute_StringProperties()
{
var builder = CreateBuilder();
builder.MapPost("/api", (PropertiesWithDataAnnotations model) => { });

await VerifyOpenApiDocument(builder, document =>
{
var schema = document.Paths["/api"].Operations[HttpMethod.Post].RequestBody.Content.First().Value.Schema;
var nonNullable = schema.Properties["stringLengthValue"];
var nullable = schema.Properties["nullableStringLengthValue"];
Assert.Equal(JsonSchemaType.String, nonNullable.Type);
Assert.Equal(5, nonNullable.MinLength);
Assert.Equal(20, nonNullable.MaxLength);
Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, nullable.Type);
Assert.Equal(5, nullable.MinLength);
Assert.Equal(20, nullable.MaxLength);
});
}

#nullable enable
private class NullablePropertiesTestModel
{
Expand Down Expand Up @@ -452,5 +682,72 @@ private class NullablePropertiesWithValidationModel
[Description("A description field")]
public string? NullableDescription { get; set; }
}

private class PropertiesWithDataAnnotations
{
// Base64StringAttribute
[Base64String]
public string Base64StringValue { get; set; } = string.Empty;
[Base64String]
public string? NullableBase64StringValue { get; set; }

// RangeAttribute
[Range(1, 100)]
public int RangeIntValue { get; set; } = 0;
[Range(1, 100)]
public int? NullableRangeIntValue { get; set; }
[Range(0.1, 99.9)]
public double RangeDoubleValue { get; set; } = 0.0;
[Range(0.1, 99.9)]
public double? NullableRangeDoubleValue { get; set; }

// RegularExpressionAttribute
[RegularExpression(@"^[A-Z]{3}$")]
public string RegexStringValue { get; set; } = string.Empty;
[RegularExpression(@"^[A-Z]{3}$")]
public string? NullableRegexStringValue { get; set; }

// MaxLengthAttribute
[MaxLength(10)]
public string MaxLengthStringValue { get; set; } = string.Empty;
[MaxLength(10)]
public string? NullableMaxLengthStringValue { get; set; }
[MaxLength(5)]
public int[] MaxLengthArrayValue { get; set; } = [];
[MaxLength(5)]
public int[]? NullableMaxLengthArrayValue { get; set; }

// MinLengthAttribute
[MinLength(3)]
public string MinLengthStringValue { get; set; } = string.Empty;
[MinLength(3)]
public string? NullableMinLengthStringValue { get; set; }
[MinLength(2)]
public int[] MinLengthArrayValue { get; set; } = [];
[MinLength(2)]
public int[]? NullableMinLengthArrayValue { get; set; }

// LengthAttribute (custom, if available)
[Length(2, 8)]
public string LengthStringValue { get; set; } = string.Empty;
[Length(2, 8)]
public string? NullableLengthStringValue { get; set; }
[Length(1, 4)]
public int[] LengthArrayValue { get; set; } = [];
[Length(1, 4)]
public int[]? NullableLengthArrayValue { get; set; }

// UrlAttribute
[Url]
public string UrlStringValue { get; set; } = string.Empty;
[Url]
public string? NullableUrlStringValue { get; set; }

// StringLengthAttribute
[StringLength(20, MinimumLength = 5)]
public string StringLengthValue { get; set; } = string.Empty;
[StringLength(20, MinimumLength = 5)]
public string? NullableStringLengthValue { get; set; }
}
#nullable restore
}
Loading