Skip to content

Commit 2139d99

Browse files
Merge pull request #1094 from microsoft/mk/generate-schema-for-pocos
Add support for OpenAPI schema generation for POCOs
2 parents 563b046 + 0b4110a commit 2139d99

File tree

3 files changed

+190
-0
lines changed

3 files changed

+190
-0
lines changed
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT license.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using Microsoft.OpenApi.Models;
7+
8+
namespace Microsoft.OpenApi.Extensions
9+
{
10+
/// <summary>
11+
/// Extension methods for <see cref="Type"/>.
12+
/// </summary>
13+
public static class OpenApiTypeMapper
14+
{
15+
private static readonly Dictionary<Type, Func<OpenApiSchema>> _simpleTypeToOpenApiSchema = new()
16+
{
17+
[typeof(bool)] = () => new OpenApiSchema { Type = "boolean" },
18+
[typeof(byte)] = () => new OpenApiSchema { Type = "string", Format = "byte" },
19+
[typeof(int)] = () => new OpenApiSchema { Type = "integer", Format = "int32" },
20+
[typeof(uint)] = () => new OpenApiSchema { Type = "integer", Format = "int32" },
21+
[typeof(long)] = () => new OpenApiSchema { Type = "integer", Format = "int64" },
22+
[typeof(ulong)] = () => new OpenApiSchema { Type = "integer", Format = "int64" },
23+
[typeof(float)] = () => new OpenApiSchema { Type = "number", Format = "float" },
24+
[typeof(double)] = () => new OpenApiSchema { Type = "number", Format = "double" },
25+
[typeof(decimal)] = () => new OpenApiSchema { Type = "number", Format = "double" },
26+
[typeof(DateTime)] = () => new OpenApiSchema { Type = "string", Format = "date-time" },
27+
[typeof(DateTimeOffset)] = () => new OpenApiSchema { Type = "string", Format = "date-time" },
28+
[typeof(Guid)] = () => new OpenApiSchema { Type = "string", Format = "uuid" },
29+
[typeof(char)] = () => new OpenApiSchema { Type = "string" },
30+
31+
// Nullable types
32+
[typeof(bool?)] = () => new OpenApiSchema { Type = "boolean", Nullable = true },
33+
[typeof(byte?)] = () => new OpenApiSchema { Type = "string", Format = "byte", Nullable = true },
34+
[typeof(int?)] = () => new OpenApiSchema { Type = "integer", Format = "int32", Nullable = true },
35+
[typeof(uint?)] = () => new OpenApiSchema { Type = "integer", Format = "int32", Nullable = true },
36+
[typeof(long?)] = () => new OpenApiSchema { Type = "integer", Format = "int64", Nullable = true },
37+
[typeof(ulong?)] = () => new OpenApiSchema { Type = "integer", Format = "int64", Nullable = true },
38+
[typeof(float?)] = () => new OpenApiSchema { Type = "number", Format = "float", Nullable = true },
39+
[typeof(double?)] = () => new OpenApiSchema { Type = "number", Format = "double", Nullable = true },
40+
[typeof(decimal?)] = () => new OpenApiSchema { Type = "number", Format = "double", Nullable = true },
41+
[typeof(DateTime?)] = () => new OpenApiSchema { Type = "string", Format = "date-time", Nullable = true },
42+
[typeof(DateTimeOffset?)] = () => new OpenApiSchema { Type = "string", Format = "date-time", Nullable = true },
43+
[typeof(Guid?)] = () => new OpenApiSchema { Type = "string", Format = "uuid", Nullable = true },
44+
[typeof(char?)] = () => new OpenApiSchema { Type = "string", Nullable = true },
45+
46+
[typeof(Uri)] = () => new OpenApiSchema { Type = "string", Format = "uri"}, // Uri is treated as simple string
47+
[typeof(string)] = () => new OpenApiSchema { Type = "string" },
48+
[typeof(object)] = () => new OpenApiSchema { Type = "object" }
49+
};
50+
51+
/// <summary>
52+
/// Maps a simple type to an OpenAPI data type and format.
53+
/// </summary>
54+
/// <param name="type">Simple type.</param>
55+
/// <remarks>
56+
/// All the following types from http://swagger.io/specification/#data-types-12 are supported.
57+
/// Other types including nullables and URL are also supported.
58+
/// Common Name type format Comments
59+
/// =========== ======= ====== =========================================
60+
/// integer integer int32 signed 32 bits
61+
/// long integer int64 signed 64 bits
62+
/// float number float
63+
/// double number double
64+
/// string string [empty]
65+
/// byte string byte base64 encoded characters
66+
/// binary string binary any sequence of octets
67+
/// boolean boolean [empty]
68+
/// date string date As defined by full-date - RFC3339
69+
/// dateTime string date-time As defined by date-time - RFC3339
70+
/// password string password Used to hint UIs the input needs to be obscured.
71+
/// If the type is not recognized as "simple", System.String will be returned.
72+
/// </remarks>
73+
public static OpenApiSchema MapTypeToOpenApiPrimitiveType(this Type type)
74+
{
75+
if (type == null)
76+
{
77+
throw new ArgumentNullException(nameof(type));
78+
}
79+
80+
return _simpleTypeToOpenApiSchema.TryGetValue(type, out var result)
81+
? result()
82+
: new OpenApiSchema { Type = "string" };
83+
}
84+
85+
/// <summary>
86+
/// Maps an OpenAPI data type and format to a simple type.
87+
/// </summary>
88+
/// <param name="schema">The OpenApi data type</param>
89+
/// <returns>The simple type</returns>
90+
/// <exception cref="ArgumentNullException"></exception>
91+
public static Type MapOpenApiPrimitiveTypeToSimpleType(this OpenApiSchema schema)
92+
{
93+
if (schema == null)
94+
{
95+
throw new ArgumentNullException(nameof(schema));
96+
}
97+
98+
var type = (schema.Type?.ToLowerInvariant(), schema.Format?.ToLowerInvariant(), schema.Nullable) switch
99+
{
100+
("boolean", null, false) => typeof(bool),
101+
("integer", "int32", false) => typeof(int),
102+
("integer", "int64", false) => typeof(long),
103+
("number", "float", false) => typeof(float),
104+
("number", "double", false) => typeof(double),
105+
("number", "decimal", false) => typeof(decimal),
106+
("string", "byte", false) => typeof(byte),
107+
("string", "date-time", false) => typeof(DateTimeOffset),
108+
("string", "uuid", false) => typeof(Guid),
109+
("string", "duration", false) => typeof(TimeSpan),
110+
("string", "char", false) => typeof(char),
111+
("string", null, false) => typeof(string),
112+
("object", null, false) => typeof(object),
113+
("string", "uri", false) => typeof(Uri),
114+
("integer", "int32", true) => typeof(int?),
115+
("integer", "int64", true) => typeof(long?),
116+
("number", "float", true) => typeof(float?),
117+
("number", "double", true) => typeof(double?),
118+
("number", "decimal", true) => typeof(decimal?),
119+
("string", "byte", true) => typeof(byte?),
120+
("string", "date-time", true) => typeof(DateTimeOffset?),
121+
("string", "uuid", true) => typeof(Guid?),
122+
("string", "char", true) => typeof(char?),
123+
("boolean", null, true) => typeof(bool?),
124+
_ => typeof(string),
125+
};
126+
127+
return type;
128+
}
129+
}
130+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT license.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using FluentAssertions;
7+
using Microsoft.OpenApi.Extensions;
8+
using Microsoft.OpenApi.Models;
9+
using Xunit;
10+
11+
namespace Microsoft.OpenApi.Tests.Extensions
12+
{
13+
public class OpenApiTypeMapperTests
14+
{
15+
public static IEnumerable<object[]> PrimitiveTypeData => new List<object[]>
16+
{
17+
new object[] { typeof(int), new OpenApiSchema { Type = "integer", Format = "int32" } },
18+
new object[] { typeof(string), new OpenApiSchema { Type = "string" } },
19+
new object[] { typeof(double), new OpenApiSchema { Type = "number", Format = "double" } },
20+
new object[] { typeof(float?), new OpenApiSchema { Type = "number", Format = "float", Nullable = true } },
21+
new object[] { typeof(DateTimeOffset), new OpenApiSchema { Type = "string", Format = "date-time" } }
22+
};
23+
24+
public static IEnumerable<object[]> OpenApiDataTypes => new List<object[]>
25+
{
26+
new object[] { new OpenApiSchema { Type = "integer", Format = "int32"}, typeof(int) },
27+
new object[] { new OpenApiSchema { Type = "string" }, typeof(string) },
28+
new object[] { new OpenApiSchema { Type = "number", Format = "double" }, typeof(double) },
29+
new object[] { new OpenApiSchema { Type = "number", Format = "float", Nullable = true }, typeof(float?) },
30+
new object[] { new OpenApiSchema { Type = "string", Format = "date-time" }, typeof(DateTimeOffset) }
31+
};
32+
33+
[Theory]
34+
[MemberData(nameof(PrimitiveTypeData))]
35+
public void MapTypeToOpenApiPrimitiveTypeShouldSucceed(Type type, OpenApiSchema expected)
36+
{
37+
// Arrange & Act
38+
var actual = OpenApiTypeMapper.MapTypeToOpenApiPrimitiveType(type);
39+
40+
// Assert
41+
actual.Should().BeEquivalentTo(expected);
42+
}
43+
44+
[Theory]
45+
[MemberData(nameof(OpenApiDataTypes))]
46+
public void MapOpenApiSchemaTypeToSimpleTypeShouldSucceed(OpenApiSchema schema, Type expected)
47+
{
48+
// Arrange & Act
49+
var actual = OpenApiTypeMapper.MapOpenApiPrimitiveTypeToSimpleType(schema);
50+
51+
// Assert
52+
actual.Should().Be(expected);
53+
}
54+
}
55+
}

test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,11 @@ namespace Microsoft.OpenApi.Extensions
282282
public static void SerializeAsYaml<T>(this T element, System.IO.Stream stream, Microsoft.OpenApi.OpenApiSpecVersion specVersion)
283283
where T : Microsoft.OpenApi.Interfaces.IOpenApiSerializable { }
284284
}
285+
public static class OpenApiTypeMapper
286+
{
287+
public static System.Type MapOpenApiPrimitiveTypeToSimpleType(this Microsoft.OpenApi.Models.OpenApiSchema schema) { }
288+
public static Microsoft.OpenApi.Models.OpenApiSchema MapTypeToOpenApiPrimitiveType(this System.Type type) { }
289+
}
285290
public static class StringExtensions
286291
{
287292
public static T GetEnumFromDisplayName<T>(this string displayName) { }

0 commit comments

Comments
 (0)