SchemaCrawler.cs 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. using Newtonsoft.Json;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.ComponentModel.DataAnnotations;
  5. using System.Linq;
  6. using System.Reflection;
  7. using VCommon.Reflection;
  8. using VCommon.VApplication.DataAnnotations;
  9. namespace VCommon.VOpenApi.Docgen
  10. {
  11. internal class SchemaCrawler
  12. {
  13. public static IDictionary<string, Schema> Crawl(IEnumerable<Type> modelsToCrawl, string refPath = "#/definitions/")
  14. {
  15. var dic = new Dictionary<string, Schema>();
  16. foreach (var item in modelsToCrawl) CrawlInternal(dic, item, refPath);
  17. return dic;
  18. }
  19. private static void CrawlInternal(IDictionary<string, Schema> dic, Type toCrawl, string refPath)
  20. {
  21. if (toCrawl.IsPrimitiveType())
  22. {
  23. var primitiveTypeName = toCrawl.GetPrimitiveTypeName();
  24. if (dic.ContainsKey(primitiveTypeName)) return;
  25. dic[primitiveTypeName] = CreatePrimitiveSchema(toCrawl);
  26. }
  27. else
  28. {
  29. var fullName = toCrawl.GetFriendlyTypeName();
  30. if (dic.ContainsKey(fullName)) return;
  31. if (toCrawl.IsArray())
  32. {
  33. var arrayElementType = toCrawl.GetArrayElementType();
  34. dic[fullName] = CreateArrayTypeSchema(refPath, arrayElementType.GetFriendlyTypeName());
  35. CrawlInternal(dic, arrayElementType, refPath);
  36. }
  37. else
  38. {
  39. var typeSchema = dic[fullName] = new Schema
  40. {
  41. type = "object",
  42. properties = new Dictionary<string, Schema>(),
  43. required = new List<string>()
  44. };
  45. foreach (var propertyInfo in toCrawl.GetPublicInstanceProperties())
  46. {
  47. if (propertyInfo.IsDefined(typeof(JsonIgnoreAttribute))) continue;
  48. if (propertyInfo.IsDefined(typeof(OutputIgnoreAttribute))) continue;
  49. if (propertyInfo.IsDefined(typeof(IgnoreSchema))) continue;
  50. var propertySchema = new Schema();
  51. var propertyName = propertyInfo.GetCustomAttribute<JsonPropertyAttribute>()?.PropertyName ?? propertyInfo.Name;
  52. if (propertyInfo.IsDefined(typeof(RequiredAttribute))) typeSchema.required.Add(propertyInfo.Name);
  53. if (propertyInfo.IsDefinedIncludeInterface(typeof(ExampleValueAttribute))) propertySchema.example = propertyInfo.GetCustomAttributeIncludeInterface<ExampleValueAttribute>().First().Value;
  54. var ptype = propertyInfo.PropertyType;
  55. if (ptype.IsPrimitiveType())
  56. {
  57. propertySchema.@ref = refPath + ptype.GetPrimitiveTypeName();
  58. CrawlInternal(dic, ptype, refPath);
  59. }
  60. else if (ptype.IsArray())
  61. {
  62. var arrayElementType = ptype.GetArrayElementType();
  63. propertySchema = CreateArrayTypeSchema(refPath, arrayElementType.GetFriendlyTypeName());
  64. CrawlInternal(dic, arrayElementType, refPath);
  65. }
  66. else
  67. {
  68. propertySchema.@ref = refPath + ptype.GetFriendlyTypeName();
  69. CrawlInternal(dic, ptype, refPath);
  70. }
  71. typeSchema.properties[propertyName] = propertySchema;
  72. }
  73. }
  74. }
  75. }
  76. private static Schema CreateArrayTypeSchema(string refPath, string fullName)
  77. {
  78. return new Schema
  79. {
  80. type = "array",
  81. items = new Schema
  82. {
  83. @ref = refPath + fullName
  84. }
  85. };
  86. }
  87. private static Schema CreatePrimitiveSchema(Type type)
  88. {
  89. if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
  90. {
  91. type = type.GetGenericArguments()[0];
  92. }
  93. if (type.IsEnum)
  94. return CreateEnumSchema(type);
  95. switch (type.FullName)
  96. {
  97. case "System.Boolean":
  98. return new Schema { type = "boolean" };
  99. case "System.Byte":
  100. case "System.SByte":
  101. case "System.Int16":
  102. case "System.UInt16":
  103. case "System.Int32":
  104. case "System.UInt32":
  105. return new Schema { type = "integer", format = "int32" };
  106. case "System.Int64":
  107. case "System.UInt64":
  108. return new Schema { type = "integer", format = "int64" };
  109. case "System.Single":
  110. return new Schema { type = "number", format = "float" };
  111. case "System.Double":
  112. case "System.Decimal":
  113. return new Schema { type = "number", format = "double" };
  114. case "System.Byte[]":
  115. return new Schema { type = "string", format = "byte" };
  116. case "System.DateTime":
  117. case "System.DateTimeOffset":
  118. return new Schema { type = "string", format = "date-time" };
  119. case "System.Guid": return new Schema { type = "string", format = "uuid", example = Guid.Empty };
  120. case "System.Object": return new Schema { type = "object" };
  121. default: return new Schema { type = "string" };
  122. }
  123. }
  124. private static Schema CreateEnumSchema(IReflect enumType)
  125. {
  126. return new Schema
  127. {
  128. type = "string",
  129. @enum = enumType.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static)
  130. .Select(fieldInfo => (object)fieldInfo.Name)
  131. .ToArray()
  132. };
  133. }
  134. }
  135. }