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

Last change on this file since 460 was 460, checked in by alloc, 2 years ago

More OpenAPI specs added
OpenAPI specs cleanup to have everything validate fine
Added CORS support to API endpoints

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