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