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

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

Updated to dashboard/marker files 0.8.2
Removed some debug logging for OpenAPI

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