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

Last change on this file since 468 was 466, checked in by alloc, 15 months ago

Cleanup in OpenApiHelpers

File size: 7.4 KB
Line 
1using System;
2using System.Collections.Generic;
3using System.IO;
4using System.Reflection;
5using System.Text;
6using System.Text.RegularExpressions;
7using Webserver.UrlHandlers;
8
9namespace Webserver.WebAPI {
10 public class OpenApiHelpers {
11 private const string masterResourceName = "openapi.master.yaml";
12 private const string masterDocName = "openapi.yaml";
13
14 private struct OpenApiSpec {
15 public readonly Dictionary<string, string> ExportedPaths;
16 public readonly string Spec;
17
18 public OpenApiSpec (string _spec, Dictionary<string, string> _exportedPaths = null) {
19 ExportedPaths = _exportedPaths;
20 Spec = _spec;
21 }
22 }
23
24 private readonly Dictionary<string, OpenApiSpec> specs = new CaseInsensitiveStringDictionary<OpenApiSpec> ();
25
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
42 specs.Add (masterDocName, new OpenApiSpec(specText));
43 // Log.Out ($"[Web] Loaded main OpenAPI spec");
44 }
45
46 private void buildMainSpecRefs () {
47 if (!TryGetOpenApiSpec (null, out string mainSpec)) {
48 return;
49 }
50
51 StringBuilder sb = new StringBuilder (mainSpec);
52
53 foreach ((string apiSpecName, OpenApiSpec spec) in specs) {
54 if (apiSpecName.Equals (masterDocName)) {
55 continue;
56 }
57
58 if (spec.ExportedPaths == null || spec.ExportedPaths.Count < 1) {
59 continue;
60 }
61
62 foreach ((string exportedPath, string rebasedPath) in spec.ExportedPaths) {
63 writePath (sb, apiSpecName, exportedPath, rebasedPath);
64 }
65 }
66
67 specs[masterDocName] = new OpenApiSpec(sb.ToString ());
68
69 Log.Out ("[Web] OpenAPI preparation done");
70 }
71
72 private void writePath (StringBuilder _sb, string _apiSpecName, string _exportedPath, string _rebasedPath) {
73 _sb.AppendLine ($" {_rebasedPath ?? _exportedPath}:");
74 _sb.Append ($" $ref: './{_apiSpecName}#/paths/");
75
76 writeJsonPointerEncodedPath (_sb, _exportedPath);
77
78 _sb.AppendLine ("'");
79 }
80
81 public void LoadOpenApiSpec (AbsWebAPI _api) {
82 loadOpenApiSpec (_api.GetType ().Assembly, _api.Name, null);
83 }
84
85 public void LoadOpenApiSpec (AbsHandler _pathHandler) {
86 Type type = _pathHandler.GetType ();
87 loadOpenApiSpec (type.Assembly, type.Name, _pathHandler.UrlBasePath);
88 }
89
90 public void RegisterCustomSpec (Assembly _assembly, string _apiSpecName, string _replaceBasePath = null) {
91 loadOpenApiSpec (_assembly, _apiSpecName, _replaceBasePath);
92 }
93
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);
98 if (specText == null) {
99 return;
100 }
101
102 // Log.Out ($"[Web] Loaded OpenAPI spec for '{_apiName}'");
103 OpenApiSpec spec = new OpenApiSpec (specText, findExportedPaths (specText, _basePath));
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;
128 // Log.Out ($"[Web] Exports: {path}");
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
157 switch (c) {
158 // JSON string escaped characters
159 case '"':
160 _targetSb.Append ("\\\"");
161 break;
162 case '\\':
163 _targetSb.Append ("\\\\");
164 break;
165 case '\b':
166 _targetSb.Append ("\\b");
167 break;
168 case '\f':
169 _targetSb.Append ("\\f");
170 break;
171 case '\n':
172 _targetSb.Append ("\\n");
173 break;
174 case '\r':
175 _targetSb.Append ("\\r");
176 break;
177 case '\t':
178 _targetSb.Append ("\\t");
179 break;
180 case (char)0x00:
181 _targetSb.Append ("\\u0000");
182 break;
183 case (char)0x01:
184 _targetSb.Append ("\\u0001");
185 break;
186 case (char)0x02:
187 _targetSb.Append ("\\u0002");
188 break;
189 case (char)0x03:
190 _targetSb.Append ("\\u0003");
191 break;
192 case (char)0x04:
193 _targetSb.Append ("\\u0004");
194 break;
195 case (char)0x05:
196 _targetSb.Append ("\\u0005");
197 break;
198 case (char)0x06:
199 _targetSb.Append ("\\u0006");
200 break;
201 case (char)0x07:
202 _targetSb.Append ("\\u0007");
203 break;
204 case (char)0x0b:
205 _targetSb.Append ("\\u000b");
206 break;
207 case (char)0x0e:
208 _targetSb.Append ("\\u000e");
209 break;
210 case (char)0x0f:
211 _targetSb.Append ("\\u000f");
212 break;
213 case (char)0x10:
214 _targetSb.Append ("\\u0010");
215 break;
216 case (char)0x11:
217 _targetSb.Append ("\\u0011");
218 break;
219 case (char)0x12:
220 _targetSb.Append ("\\u0012");
221 break;
222 case (char)0x13:
223 _targetSb.Append ("\\u0013");
224 break;
225 case (char)0x14:
226 _targetSb.Append ("\\u0014");
227 break;
228 case (char)0x15:
229 _targetSb.Append ("\\u0015");
230 break;
231 case (char)0x16:
232 _targetSb.Append ("\\u0016");
233 break;
234 case (char)0x17:
235 _targetSb.Append ("\\u0017");
236 break;
237 case (char)0x18:
238 _targetSb.Append ("\\u0018");
239 break;
240 case (char)0x19:
241 _targetSb.Append ("\\u0019");
242 break;
243 case (char)0x1a:
244 _targetSb.Append ("\\u001a");
245 break;
246 case (char)0x1b:
247 _targetSb.Append ("\\u001b");
248 break;
249 case (char)0x1c:
250 _targetSb.Append ("\\u001c");
251 break;
252 case (char)0x1d:
253 _targetSb.Append ("\\u001d");
254 break;
255 case (char)0x1e:
256 _targetSb.Append ("\\u001e");
257 break;
258 case (char)0x1f:
259 _targetSb.Append ("\\u001f");
260 break;
261 // JSON Pointer specific
262 case '/':
263 _targetSb.Append ("~1");
264 break;
265 case '~':
266 _targetSb.Append ("~0");
267 break;
268 // Non escaped characters
269 default:
270 _targetSb.Append (c);
271 break;
272 }
273 }
274 }
275
276 }
277}
Note: See TracBrowser for help on using the repository browser.