source: TFP-WebServer/WebServer/src/WebAPI/OpenApiHelpers.cs@ 474

Last change on this file since 474 was 466, checked in by alloc, 16 months ago

Cleanup in OpenApiHelpers

File size: 7.4 KB
RevLine 
[466]1using System;
[459]2using System.Collections.Generic;
3using System.IO;
4using System.Reflection;
5using System.Text;
[460]6using System.Text.RegularExpressions;
[463]7using Webserver.UrlHandlers;
[459]8
9namespace Webserver.WebAPI {
10 public class OpenApiHelpers {
11 private const string masterResourceName = "openapi.master.yaml";
12 private const string masterDocName = "openapi.yaml";
13
[460]14 private struct OpenApiSpec {
[463]15 public readonly Dictionary<string, string> ExportedPaths;
[460]16 public readonly string Spec;
[459]17
[463]18 public OpenApiSpec (string _spec, Dictionary<string, string> _exportedPaths = null) {
[460]19 ExportedPaths = _exportedPaths;
20 Spec = _spec;
21 }
22 }
23
24 private readonly Dictionary<string, OpenApiSpec> specs = new CaseInsensitiveStringDictionary<OpenApiSpec> ();
25
[459]26 public OpenApiHelpers () {
27 loadMainSpec ();
28 Web.ServerInitialized += _ => {
29 buildMainSpecRefs ();
30 };
31 }
32
33 private void loadMainSpec () {
34 Assembly apiAssembly = GetType ().Assembly;
35
36 string specText = ResourceHelpers.GetManifestResourceText (apiAssembly, masterResourceName, true);
37 if (specText == null) {
38 Log.Warning ($"[Web] Failed loading main OpenAPI spec from assembly '{Path.GetFileName (apiAssembly.Location)}'");
39 return;
40 }
41
[460]42 specs.Add (masterDocName, new OpenApiSpec(specText));
[465]43 // Log.Out ($"[Web] Loaded main OpenAPI spec");
[459]44 }
45
46 private void buildMainSpecRefs () {
47 if (!TryGetOpenApiSpec (null, out string mainSpec)) {
48 return;
49 }
50
51 StringBuilder sb = new StringBuilder (mainSpec);
52
[460]53 foreach ((string apiSpecName, OpenApiSpec spec) in specs) {
[459]54 if (apiSpecName.Equals (masterDocName)) {
55 continue;
56 }
57
[463]58 if (spec.ExportedPaths == null || spec.ExportedPaths.Count < 1) {
[460]59 continue;
60 }
61
[463]62 foreach ((string exportedPath, string rebasedPath) in spec.ExportedPaths) {
63 writePath (sb, apiSpecName, exportedPath, rebasedPath);
[460]64 }
[459]65 }
66
[460]67 specs[masterDocName] = new OpenApiSpec(sb.ToString ());
[459]68
69 Log.Out ("[Web] OpenAPI preparation done");
70 }
71
[463]72 private void writePath (StringBuilder _sb, string _apiSpecName, string _exportedPath, string _rebasedPath) {
73 _sb.AppendLine ($" {_rebasedPath ?? _exportedPath}:");
[460]74 _sb.Append ($" $ref: './{_apiSpecName}#/paths/");
75
[463]76 writeJsonPointerEncodedPath (_sb, _exportedPath);
77
78 _sb.AppendLine ("'");
79 }
[460]80
[463]81 public void LoadOpenApiSpec (AbsWebAPI _api) {
[466]82 loadOpenApiSpec (_api.GetType ().Assembly, _api.Name, null);
[463]83 }
84
85 public void LoadOpenApiSpec (AbsHandler _pathHandler) {
[466]86 Type type = _pathHandler.GetType ();
87 loadOpenApiSpec (type.Assembly, type.Name, _pathHandler.UrlBasePath);
[463]88 }
89
90 public void RegisterCustomSpec (Assembly _assembly, string _apiSpecName, string _replaceBasePath = null) {
[466]91 loadOpenApiSpec (_assembly, _apiSpecName, _replaceBasePath);
92 }
[463]93
[466]94 private void loadOpenApiSpec (Assembly _containingAssembly, string _apiName, string _basePath) {
95 string apiSpecName = $"{_apiName}.openapi.yaml";
96
97 string specText = ResourceHelpers.GetManifestResourceText (_containingAssembly, apiSpecName, true);
[463]98 if (specText == null) {
99 return;
100 }
101
[466]102 // Log.Out ($"[Web] Loaded OpenAPI spec for '{_apiName}'");
103 OpenApiSpec spec = new OpenApiSpec (specText, findExportedPaths (specText, _basePath));
[463]104 specs.Add (apiSpecName, spec);
105 }
106
107 private static readonly Regex pathMatcher = new Regex ("^\\s{1,2}(/\\S+):.*$", RegexOptions.Compiled | RegexOptions.CultureInvariant);
108 private Dictionary<string, string> findExportedPaths (string _spec, string _replaceBasePath = null) {
109 Dictionary<string, string> result = new Dictionary<string, string> ();
110
111 using TextReader tr = new StringReader (_spec);
112
113 string line;
114 bool inPaths = false;
115 while ((line = tr.ReadLine ()) != null) {
116 if (!inPaths) {
117 if (line.StartsWith ("paths:")) {
118 inPaths = true;
119 }
120 } else {
121 Match match = pathMatcher.Match (line);
122 if (!match.Success) {
123 continue;
124 }
125
126 string path = match.Groups [1].Value;
127 string rebasedPath = null;
[465]128 // Log.Out ($"[Web] Exports: {path}");
[463]129 if (_replaceBasePath != null) {
130 rebasedPath = path.Replace ("/BASEPATH/", _replaceBasePath);
131 }
132 result [path] = rebasedPath;
133 }
134 }
135
136 return result;
137 }
138
139 public bool TryGetOpenApiSpec (string _name, out string _specText) {
140 if (string.IsNullOrEmpty (_name)) {
141 _name = masterDocName;
142 }
143
144 if (!specs.TryGetValue (_name, out OpenApiSpec spec)) {
145 _specText = null;
146 return false;
147 }
148
149 _specText = spec.Spec;
150 return true;
151 }
152
153 private void writeJsonPointerEncodedPath (StringBuilder _targetSb, string _path) {
154 for (int i = 0; i < _path.Length; i++) {
155 char c = _path[i];
156
[460]157 switch (c) {
158 // JSON string escaped characters
159 case '"':
[463]160 _targetSb.Append ("\\\"");
[460]161 break;
162 case '\\':
[463]163 _targetSb.Append ("\\\\");
[460]164 break;
165 case '\b':
[463]166 _targetSb.Append ("\\b");
[460]167 break;
168 case '\f':
[463]169 _targetSb.Append ("\\f");
[460]170 break;
171 case '\n':
[463]172 _targetSb.Append ("\\n");
[460]173 break;
174 case '\r':
[463]175 _targetSb.Append ("\\r");
[460]176 break;
177 case '\t':
[463]178 _targetSb.Append ("\\t");
[460]179 break;
180 case (char)0x00:
[463]181 _targetSb.Append ("\\u0000");
[460]182 break;
183 case (char)0x01:
[463]184 _targetSb.Append ("\\u0001");
[460]185 break;
186 case (char)0x02:
[463]187 _targetSb.Append ("\\u0002");
[460]188 break;
189 case (char)0x03:
[463]190 _targetSb.Append ("\\u0003");
[460]191 break;
192 case (char)0x04:
[463]193 _targetSb.Append ("\\u0004");
[460]194 break;
195 case (char)0x05:
[463]196 _targetSb.Append ("\\u0005");
[460]197 break;
198 case (char)0x06:
[463]199 _targetSb.Append ("\\u0006");
[460]200 break;
201 case (char)0x07:
[463]202 _targetSb.Append ("\\u0007");
[460]203 break;
204 case (char)0x0b:
[463]205 _targetSb.Append ("\\u000b");
[460]206 break;
207 case (char)0x0e:
[463]208 _targetSb.Append ("\\u000e");
[460]209 break;
210 case (char)0x0f:
[463]211 _targetSb.Append ("\\u000f");
[460]212 break;
213 case (char)0x10:
[463]214 _targetSb.Append ("\\u0010");
[460]215 break;
216 case (char)0x11:
[463]217 _targetSb.Append ("\\u0011");
[460]218 break;
219 case (char)0x12:
[463]220 _targetSb.Append ("\\u0012");
[460]221 break;
222 case (char)0x13:
[463]223 _targetSb.Append ("\\u0013");
[460]224 break;
225 case (char)0x14:
[463]226 _targetSb.Append ("\\u0014");
[460]227 break;
228 case (char)0x15:
[463]229 _targetSb.Append ("\\u0015");
[460]230 break;
231 case (char)0x16:
[463]232 _targetSb.Append ("\\u0016");
[460]233 break;
234 case (char)0x17:
[463]235 _targetSb.Append ("\\u0017");
[460]236 break;
237 case (char)0x18:
[463]238 _targetSb.Append ("\\u0018");
[460]239 break;
240 case (char)0x19:
[463]241 _targetSb.Append ("\\u0019");
[460]242 break;
243 case (char)0x1a:
[463]244 _targetSb.Append ("\\u001a");
[460]245 break;
246 case (char)0x1b:
[463]247 _targetSb.Append ("\\u001b");
[460]248 break;
249 case (char)0x1c:
[463]250 _targetSb.Append ("\\u001c");
[460]251 break;
252 case (char)0x1d:
[463]253 _targetSb.Append ("\\u001d");
[460]254 break;
255 case (char)0x1e:
[463]256 _targetSb.Append ("\\u001e");
[460]257 break;
258 case (char)0x1f:
[463]259 _targetSb.Append ("\\u001f");
[460]260 break;
261 // JSON Pointer specific
262 case '/':
[463]263 _targetSb.Append ("~1");
[460]264 break;
265 case '~':
[463]266 _targetSb.Append ("~0");
[460]267 break;
268 // Non escaped characters
269 default:
[463]270 _targetSb.Append (c);
[460]271 break;
272 }
273 }
274 }
275
[459]276 }
277}
Note: See TracBrowser for help on using the repository browser.