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

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

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

File size: 6.3 KB
Line 
1using System.Collections.Generic;
2using System.IO;
3using System.Reflection;
4using System.Text;
5using System.Text.RegularExpressions;
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
15 private struct OpenApiSpec {
16 public readonly List<string> ExportedPaths;
17 public readonly string Spec;
18
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
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
43 specs.Add (masterDocName, new OpenApiSpec(specText));
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
54 foreach ((string apiSpecName, OpenApiSpec spec) in specs) {
55 if (apiSpecName.Equals (masterDocName)) {
56 continue;
57 }
58
59 if ((spec.ExportedPaths?.Count ?? 0) < 1) {
60 continue;
61 }
62
63 foreach (string exportedPath in spec.ExportedPaths) {
64 writePath (sb, apiSpecName, exportedPath);
65 }
66 }
67
68 specs[masterDocName] = new OpenApiSpec(sb.ToString ());
69
70 Log.Out ("[Web] OpenAPI preparation done");
71 }
72
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
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}'");
212 OpenApiSpec spec = new OpenApiSpec (specText, findExportedPaths (specText));
213 specs.Add (apiSpecName, spec);
214 }
215
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
244 public bool TryGetOpenApiSpec (string _name, out string _specText) {
245 if (string.IsNullOrEmpty (_name)) {
246 _name = masterDocName;
247 }
248
249 if (!specs.TryGetValue (_name, out OpenApiSpec spec)) {
250 _specText = null;
251 return false;
252 }
253
254 _specText = spec.Spec;
255 return true;
256 }
257 }
258}
Note: See TracBrowser for help on using the repository browser.