@api-client/core 0.5.10 → 0.5.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/build/browser.d.ts +2 -0
  2. package/build/browser.js +2 -0
  3. package/build/browser.js.map +1 -1
  4. package/build/index.d.ts +2 -0
  5. package/build/index.js +2 -0
  6. package/build/index.js.map +1 -1
  7. package/build/src/lib/calculators/DataCalculator.d.ts +3 -3
  8. package/build/src/lib/calculators/DataCalculator.js +3 -3
  9. package/build/src/lib/calculators/DataCalculator.js.map +1 -1
  10. package/build/src/lib/events/Utils.d.ts +1 -0
  11. package/build/src/lib/events/Utils.js +6 -0
  12. package/build/src/lib/events/Utils.js.map +1 -0
  13. package/build/src/lib/parsers/UriTemplate.d.ts +94 -0
  14. package/build/src/lib/parsers/UriTemplate.js +419 -0
  15. package/build/src/lib/parsers/UriTemplate.js.map +1 -0
  16. package/build/src/lib/parsers/UrlEncoder.d.ts +5 -0
  17. package/build/src/lib/parsers/UrlEncoder.js +49 -0
  18. package/build/src/lib/parsers/UrlEncoder.js.map +1 -1
  19. package/build/src/models/HttpProject.d.ts +4 -1
  20. package/build/src/models/HttpProject.js +9 -6
  21. package/build/src/models/HttpProject.js.map +1 -1
  22. package/build/src/models/ProjectParent.d.ts +9 -0
  23. package/build/src/models/ProjectParent.js +25 -0
  24. package/build/src/models/ProjectParent.js.map +1 -1
  25. package/build/src/models/Property.d.ts +8 -0
  26. package/build/src/models/Property.js +17 -0
  27. package/build/src/models/Property.js.map +1 -1
  28. package/build/src/models/Server.d.ts +14 -1
  29. package/build/src/models/Server.js +31 -1
  30. package/build/src/models/Server.js.map +1 -1
  31. package/build/src/runtime/actions/runnable/DeleteCookieRunnable.d.ts +1 -1
  32. package/build/src/runtime/actions/runnable/SetCookieRunnable.d.ts +1 -1
  33. package/build/src/runtime/actions/runnable/SetVariableRunnable.d.ts +1 -1
  34. package/build/src/runtime/http-engine/CoreEngine.d.ts +1 -1
  35. package/build/src/runtime/store/FilesSdk.d.ts +8 -0
  36. package/build/src/runtime/store/FilesSdk.js +15 -0
  37. package/build/src/runtime/store/FilesSdk.js.map +1 -1
  38. package/package.json +1 -1
  39. package/src/lib/calculators/DataCalculator.ts +3 -3
  40. package/src/lib/events/Utils.ts +5 -0
  41. package/src/lib/parsers/UriTemplate.ts +494 -0
  42. package/src/lib/parsers/UrlEncoder.ts +51 -0
  43. package/src/models/HttpProject.ts +10 -7
  44. package/src/models/ProjectParent.ts +27 -0
  45. package/src/models/Property.ts +18 -0
  46. package/src/models/Server.ts +32 -1
  47. package/src/runtime/actions/runnable/DeleteCookieRunnable.ts +1 -1
  48. package/src/runtime/actions/runnable/SetCookieRunnable.ts +1 -1
  49. package/src/runtime/actions/runnable/SetVariableRunnable.ts +1 -1
  50. package/src/runtime/http-engine/CoreEngine.ts +1 -1
  51. package/src/runtime/store/FilesSdk.ts +16 -0
@@ -0,0 +1,494 @@
1
+ import { UrlEncoder } from './UrlEncoder.js';
2
+
3
+ const cache = new Map<string, UriTemplate>();
4
+
5
+ /**
6
+ * pattern to identify expressions [operator, variable-list] in template
7
+ */
8
+ const EXPRESSION_PATTERN = /\{([^a-zA-Z0-9%_]?)([^}]+)(\}|$)/g;
9
+
10
+ /**
11
+ * pattern to identify variables [name, explode, maxlength] in variable-list
12
+ */
13
+ const VARIABLE_PATTERN = /^([^*:.](?:\.?[^*:.])*)((\*)|:(\d+))?$/;
14
+
15
+ /**
16
+ * pattern to verify variable name integrity
17
+ */
18
+ const VARIABLE_NAME_PATTERN = /[^a-zA-Z0-9%_.]/;
19
+
20
+ /**
21
+ * pattern to verify literal integrity
22
+ */
23
+ const LITERAL_PATTERN = /[<>{}"`^| \\]/;
24
+
25
+ enum DataType {
26
+ // undefined/null
27
+ nil,
28
+ // string
29
+ string,
30
+ // object
31
+ object,
32
+ // array
33
+ array,
34
+ }
35
+
36
+ interface IData {
37
+ /**
38
+ * type of data 0: undefined/null, 1: string, 2: object, 3: array
39
+ */
40
+ type: DataType;
41
+ /**
42
+ * original values (except undefined/null)
43
+ */
44
+ val: (string | undefined)[][];
45
+ /**
46
+ * cache for encoded values (only for non-maxlength expansion)
47
+ */
48
+ strictEncode: (string | undefined)[][];
49
+ encodeReserved: (string | undefined)[][]
50
+ }
51
+
52
+ class Data {
53
+ cache: Record<string, IData>;
54
+ constructor(public data: Record<string, any>) {
55
+ this.cache = {};
56
+ }
57
+
58
+ get(key: string): IData {
59
+ const { data } = this;
60
+ const d: IData = {
61
+ type: DataType.nil,
62
+ val: [],
63
+ strictEncode: [],
64
+ encodeReserved: [],
65
+ };
66
+
67
+ if (this.cache[key] !== undefined) {
68
+ // we've already processed this key
69
+ return this.cache[key];
70
+ }
71
+
72
+ this.cache[key] = d;
73
+
74
+ let value: any;
75
+ if (typeof data === 'function') {
76
+ value = data(key);
77
+ } else if (typeof data[key] === 'function') {
78
+ value = data[key](key);
79
+ } else {
80
+ value = data[key];
81
+ }
82
+
83
+ // generalize input into [ [name1, value1], [name2, value2], … ]
84
+ // so expansion has to deal with a single data structure only
85
+ if (value === undefined || value === null) {
86
+ // undefined and null values are to be ignored completely
87
+ return d;
88
+ } else if (Array.isArray(value)) {
89
+ value.forEach((v) => {
90
+ if (v !== undefined && v !== null) {
91
+ // arrays don't have names
92
+ d.val.push([undefined, v])
93
+ }
94
+ });
95
+ if (d.val.length) {
96
+ d.type = DataType.array;
97
+ }
98
+ } else if (String(Object.prototype.toString.call(value)) === '[object Object]') {
99
+ Object.keys(value).forEach((k) => {
100
+ const v = value[k];
101
+ if (v !== undefined && v !== null) {
102
+ d.val.push([k, v])
103
+ }
104
+ });
105
+ if (d.val.length) {
106
+ d.type = DataType.object;
107
+ }
108
+ } else {
109
+ d.type = DataType.string;
110
+ d.val.push([undefined, String(value)]);
111
+ }
112
+
113
+ return d;
114
+ }
115
+ }
116
+
117
+ interface IOperator {
118
+ prefix: string,
119
+ separator: string;
120
+ named: boolean;
121
+ empty_name_separator: boolean;
122
+ encode: 'strictEncode' | 'encodeReserved';
123
+ }
124
+
125
+ interface IVariable {
126
+ name: string;
127
+ explode: boolean;
128
+ maxlength?: number;
129
+ }
130
+
131
+ interface IPart {
132
+ expression: string;
133
+ operator: string;
134
+ variables: IVariable[];
135
+ }
136
+
137
+ export interface IUriTemplateOptions {
138
+ /**
139
+ * Throws when a variable is not found to replaces a value in the template.
140
+ */
141
+ strict?: boolean;
142
+ /**
143
+ * When set it ignores replacing the value in the template when the variable is missing.
144
+ */
145
+ ignoreMissing?: boolean;
146
+ }
147
+
148
+ const operators: Record<string, IOperator> = {
149
+ // Simple string expansion
150
+ '': {
151
+ prefix: '',
152
+ separator: ',',
153
+ named: false,
154
+ empty_name_separator: false,
155
+ encode: 'strictEncode'
156
+ },
157
+ // Reserved character strings
158
+ '+': {
159
+ prefix: '',
160
+ separator: ',',
161
+ named: false,
162
+ empty_name_separator: false,
163
+ encode: 'encodeReserved'
164
+ },
165
+ // Fragment identifiers prefixed by '#'
166
+ '#': {
167
+ prefix: '#',
168
+ separator: ',',
169
+ named: false,
170
+ empty_name_separator: false,
171
+ encode: 'encodeReserved'
172
+ },
173
+ // Name labels or extensions prefixed by '.'
174
+ '.': {
175
+ prefix: '.',
176
+ separator: '.',
177
+ named: false,
178
+ empty_name_separator: false,
179
+ encode: 'strictEncode'
180
+ },
181
+ // Path segments prefixed by '/'
182
+ '/': {
183
+ prefix: '/',
184
+ separator: '/',
185
+ named: false,
186
+ empty_name_separator: false,
187
+ encode: 'strictEncode'
188
+ },
189
+ // Path parameter name or name=value pairs prefixed by ';'
190
+ ';': {
191
+ prefix: ';',
192
+ separator: ';',
193
+ named: true,
194
+ empty_name_separator: false,
195
+ encode: 'strictEncode'
196
+ },
197
+ // Query component beginning with '?' and consisting
198
+ // of name=value pairs separated by '&'; an
199
+ '?': {
200
+ prefix: '?',
201
+ separator: '&',
202
+ named: true,
203
+ empty_name_separator: true,
204
+ encode: 'strictEncode'
205
+ },
206
+ // Continuation of query-style &name=value pairs
207
+ // within a literal query component.
208
+ '&': {
209
+ prefix: '&',
210
+ separator: '&',
211
+ named: true,
212
+ empty_name_separator: true,
213
+ encode: 'strictEncode'
214
+ }
215
+
216
+ // The operator characters equals ("="), comma (","), exclamation ("!"),
217
+ // at sign ("@"), and pipe ("|") are reserved for future extensions.
218
+ };
219
+
220
+ /**
221
+ * Processor for URI templates: http://tools.ietf.org/html/rfc6570
222
+ */
223
+ export class UriTemplate {
224
+ parts?: (string | IPart)[];
225
+
226
+ /**
227
+ * Allows to ache and reuse cached instances.
228
+ *
229
+ * @param uri The URI string.
230
+ * @returns
231
+ */
232
+ static fromUri(uri: string): UriTemplate {
233
+ let cached = cache.get(uri);
234
+ if (!cached) {
235
+ cached = new UriTemplate(uri);
236
+ cache.set(uri, cached);
237
+ }
238
+ return cached;
239
+ }
240
+
241
+ /**
242
+ * @param expression The URI string.
243
+ */
244
+ constructor(protected expression: string) { }
245
+
246
+ /**
247
+ * Expands the template with the given map values.
248
+ *
249
+ * @param map The map with values
250
+ * @param opts Processing options
251
+ * @returns The expanded URI.
252
+ */
253
+ expand(map: Record<string, any>, opts: IUriTemplateOptions = {}): string {
254
+ let result = '';
255
+ if (!this.parts || !this.parts.length) {
256
+ this.parse();
257
+ }
258
+ const data = new Data(map);
259
+
260
+ for (const part of this.parts!) {
261
+ const item = typeof part === 'string' ? part : UriTemplate.expand(part, data, opts);
262
+ result += item;
263
+ }
264
+ return result;
265
+ }
266
+
267
+ /**
268
+ * Parses the template into action tokens.
269
+ */
270
+ parse(): void {
271
+ const { expression } = this;
272
+ const parts: (string | IPart)[] = [];
273
+ let pos = 0;
274
+
275
+ function checkLiteral(literal: string): string {
276
+ if (literal.match(LITERAL_PATTERN)) {
277
+ throw new Error(`Invalid Literal "${literal}"`);
278
+ }
279
+ return literal;
280
+ }
281
+
282
+ EXPRESSION_PATTERN.lastIndex = 0;
283
+
284
+ // eslint-disable-next-line no-constant-condition
285
+ while (true) {
286
+ const eMatch = EXPRESSION_PATTERN.exec(expression);
287
+ if (eMatch === null) {
288
+ // push trailing literal
289
+ parts.push(checkLiteral(expression.substring(pos)));
290
+ break;
291
+ } else {
292
+ // push leading literal
293
+ parts.push(checkLiteral(expression.substring(pos, eMatch.index)));
294
+ pos = eMatch.index + eMatch[0].length;
295
+ }
296
+ if (!operators[eMatch[1]]) {
297
+ throw new Error(`Unknown Operator "${eMatch[1]}" in "${eMatch[0]}"`);
298
+ } else if (!eMatch[3]) {
299
+ throw new Error(`Unclosed Expression "${eMatch[0]}"`);
300
+ }
301
+
302
+ // parse variable-list
303
+ const varParts = eMatch[2].split(',');
304
+ const vars: IVariable[] = [];
305
+
306
+ for (var i = 0, l = varParts.length; i < l; i++) {
307
+ const vMatch = varParts[i].match(VARIABLE_PATTERN);
308
+ if (vMatch === null) {
309
+ throw new Error(`Invalid Variable "${varParts[i]}" in "${eMatch[0]}"`);
310
+ } else if (vMatch[1].match(VARIABLE_NAME_PATTERN)) {
311
+ throw new Error(`Invalid Variable Name "${vMatch[1]}" in ""${eMatch[0]}"`);
312
+ }
313
+
314
+ vars[i] = {
315
+ name: vMatch[1],
316
+ explode: !!vMatch[3],
317
+ maxlength: vMatch[4] && parseInt(vMatch[4], 10) || undefined,
318
+ };
319
+ }
320
+
321
+ if (!vars.length) {
322
+ throw new Error(`Expression Missing Variable(s) "${eMatch[0]}"`);
323
+ }
324
+ parts.push({
325
+ expression: eMatch[0],
326
+ operator: eMatch[1],
327
+ variables: vars,
328
+ });
329
+ }
330
+
331
+ if (!parts.length) {
332
+ // template doesn't contain any expressions
333
+ // so it is a simple literal string
334
+ // this probably should fire a warning or something?
335
+ parts.push(checkLiteral(expression));
336
+ }
337
+
338
+ this.parts = parts;
339
+ }
340
+
341
+ protected static expand(expression: IPart, data: Data, opts: IUriTemplateOptions = {}): string {
342
+ const options = operators[expression.operator];
343
+ const type = options.named ? 'Named' : 'Unnamed';
344
+ const { variables } = expression;
345
+ const buffer = [];
346
+
347
+ for (const variable of variables) {
348
+ const d = data.get(variable.name);
349
+ if (d.type === DataType.nil && opts && opts.strict) {
350
+ throw new Error(`Missing expansion value for variable "${variable.name}"`);
351
+ }
352
+ if (d.type === DataType.nil && opts.ignoreMissing) {
353
+ buffer.push(expression.expression)
354
+ continue
355
+ }
356
+ if (!d.val.length) {
357
+ if (d.type !== DataType.nil) {
358
+ // empty variables (empty string) still lead to a separator being appended!
359
+ buffer.push('');
360
+ }
361
+ continue;
362
+ }
363
+ if (d.type > DataType.string && variable.maxlength) {
364
+ throw new Error(`Invalid expression: Prefix modifier not applicable to variable "${variable.name}"`);
365
+ }
366
+ buffer.push(UriTemplate[`expand${type}`](
367
+ d,
368
+ options,
369
+ variable.explode,
370
+ variable.explode && options.separator || ',',
371
+ variable.maxlength,
372
+ variable.name
373
+ ));
374
+ }
375
+
376
+ if (buffer.length) {
377
+ return options.prefix + buffer.join(options.separator);
378
+ }
379
+ // prefix is not prepended for empty expressions
380
+ return '';
381
+ }
382
+
383
+ /**
384
+ * Expands a named variable.
385
+ */
386
+ protected static expandNamed(d: IData, options: IOperator, explode: boolean, separator: string, length?: number, name?: string): string {
387
+ let result = '';
388
+ const { encode, empty_name_separator } = options;
389
+ const _encode = !d[encode].length;
390
+ let _name = d.type === DataType.object ? '' : UrlEncoder[encode](name!);
391
+
392
+ d.val.forEach((item, index) => {
393
+ let _value;
394
+ if (length) {
395
+ // maxlength must be determined before encoding can happen
396
+ _value = UrlEncoder[encode](item[1]!.substring(0, length));
397
+ if (d.type === DataType.object) {
398
+ // apply maxlength to keys of objects as well
399
+ _name = UrlEncoder[encode](item[0]!.substring(0, length));
400
+ }
401
+ } else if (_encode) {
402
+ // encode value
403
+ _value = UrlEncoder[encode](item[1]!);
404
+ if (d.type === DataType.object) {
405
+ // encode name and cache encoded value
406
+ _name = UrlEncoder[encode](item[0]!);
407
+ d[encode].push([_name, _value]);
408
+ } else {
409
+ // cache encoded value
410
+ d[encode].push([undefined, _value]);
411
+ }
412
+ } else {
413
+ // values are already encoded and can be pulled from cache
414
+ _value = d[encode][index][1];
415
+ if (d.type === DataType.object) {
416
+ _name = d[encode][index][0]!;
417
+ }
418
+ }
419
+
420
+ if (result) {
421
+ result += separator;
422
+ }
423
+
424
+ if (!explode) {
425
+ if (!index) {
426
+ result += UrlEncoder[encode](name!) + (empty_name_separator || _value ? '=' : '');
427
+ }
428
+ if (d.type === DataType.object) {
429
+ result += `${_name},`;
430
+ }
431
+ result += _value;
432
+ } else {
433
+ result += _name + (empty_name_separator || _value ? '=' : '') + _value;
434
+ }
435
+ });
436
+
437
+ return result;
438
+ }
439
+
440
+ /**
441
+ * Expands an unnamed variable.
442
+ */
443
+ protected static expandUnnamed(d: IData, options: IOperator, explode: boolean, separator: string, length?: number): string {
444
+ let result = '';
445
+ const { encode, empty_name_separator } = options;
446
+ const _encode = !d[encode].length;
447
+
448
+ d.val.forEach((item, index) => {
449
+ let _value: string;
450
+ if (length) {
451
+ // maxlength must be determined before encoding can happen
452
+ _value = UrlEncoder[encode](item[1]!.substring(0, length));
453
+ } else if (_encode) {
454
+ // encode and cache value
455
+ _value = UrlEncoder[encode](item[1]!);
456
+ d[encode].push([
457
+ d.type === DataType.object ? UrlEncoder[encode](item[0]!) : undefined,
458
+ _value
459
+ ]);
460
+ } else {
461
+ // value already encoded, pull from cache
462
+ _value = d[encode][index][1]!;
463
+ }
464
+
465
+ if (result) {
466
+ // unless we're the first value, prepend the separator
467
+ result += separator;
468
+ }
469
+
470
+ if (d.type === DataType.object) {
471
+ let _name;
472
+ if (length) {
473
+ // maxlength also applies to keys of objects
474
+ _name = UrlEncoder[encode](item[0]!.substring(0, length));
475
+ } else {
476
+ // at this point the name must already be encoded
477
+ _name = d[encode][index][0];
478
+ }
479
+
480
+ result += _name;
481
+ if (explode) {
482
+ // explode-modifier separates name and value by "="
483
+ result += (empty_name_separator || _value ? '=' : '');
484
+ } else {
485
+ // no explode-modifier separates name and value by ","
486
+ result += ',';
487
+ }
488
+ }
489
+
490
+ result += _value;
491
+ });
492
+ return result;
493
+ }
494
+ }
@@ -71,4 +71,55 @@ export class UrlEncoder {
71
71
  }
72
72
  return decodeURIComponent(result);
73
73
  }
74
+
75
+ static strictEncode(str: string): string {
76
+ if (!str) {
77
+ return str;
78
+ }
79
+ const escaped = {
80
+ '!': '%21',
81
+ "'": '%27',
82
+ '(': '%28',
83
+ ')': '%29',
84
+ };
85
+ return encodeURIComponent(str).replace(/\*/g, '%2A')
86
+ // @ts-ignore
87
+ .replace(/[!'()*]/g, (c) => escaped[c] );
88
+ }
89
+
90
+ /**
91
+ * For URI templates encodes the URL string without encoding the reserved characters.
92
+ */
93
+ static encodeReserved(str: string): string {
94
+ if (!str) {
95
+ return str;
96
+ }
97
+ const expression = /%(21|23|24|26|27|28|29|2A|2B|2C|2F|3A|3B|3D|3F|40|5B|5D)/ig;
98
+ const map = {
99
+ // gen-delims
100
+ '%3A': ':',
101
+ '%2F': '/',
102
+ '%3F': '?',
103
+ '%23': '#',
104
+ '%5B': '[',
105
+ '%5D': ']',
106
+ '%40': '@',
107
+ // sub-delims
108
+ '%21': '!',
109
+ '%24': '$',
110
+ '%26': '&',
111
+ '%27': '\'',
112
+ '%28': '(',
113
+ '%29': ')',
114
+ '%2A': '*',
115
+ '%2B': '+',
116
+ '%2C': ',',
117
+ '%3B': ';',
118
+ '%3D': '='
119
+ };
120
+ let result = UrlEncoder.strictEncode(str);
121
+ // @ts-ignore
122
+ result = result.replace(expression, (c) => map[c]);
123
+ return result;
124
+ }
74
125
  }
@@ -1107,6 +1107,16 @@ export class HttpProject extends ProjectParent {
1107
1107
  return result.reverse();
1108
1108
  }
1109
1109
 
1110
+ /**
1111
+ * @returns Returns the effective environments. If the project has been initialized with an environment then it is returned. Otherwise other environments.
1112
+ */
1113
+ getEnvironments(): Environment[] {
1114
+ if (Array.isArray(this.initEnvironments)) {
1115
+ return this.initEnvironments;
1116
+ }
1117
+ return super.getEnvironments();
1118
+ }
1119
+
1110
1120
  /**
1111
1121
  * Makes a copy of this project.
1112
1122
  */
@@ -1257,13 +1267,6 @@ export class HttpProject extends ProjectParent {
1257
1267
  return this.definitions.schemas;
1258
1268
  }
1259
1269
 
1260
- getEnvironments(): Environment[] {
1261
- if (Array.isArray(this.initEnvironments)) {
1262
- return this.initEnvironments;
1263
- }
1264
- return super.getEnvironments();
1265
- }
1266
-
1267
1270
  /**
1268
1271
  * Iterates over requests in the project.
1269
1272
  */
@@ -110,4 +110,31 @@ export abstract class ProjectParent implements ProjectDefinitionProperty {
110
110
  });
111
111
  return result;
112
112
  }
113
+
114
+ /**
115
+ * @param key The environment key to read.
116
+ */
117
+ getEnvironment(key: string): Environment | undefined {
118
+ const { environments } = this;
119
+ if (!environments.length) {
120
+ return undefined;
121
+ }
122
+ const has = environments.includes(key);
123
+ if (!has) {
124
+ return undefined;
125
+ }
126
+ const project = this.getProject();
127
+ if (!project.definitions.environments) {
128
+ return undefined;
129
+ }
130
+ return project.definitions.environments.find(i => i.key === key);
131
+ }
132
+
133
+ /**
134
+ * This is a link to the `getEnvironments()`. The difference is that on the
135
+ * project level it won't return environments defined with the class initialization.
136
+ */
137
+ listEnvironments(): Environment[] {
138
+ return this.getEnvironments();
139
+ }
113
140
  }
@@ -420,4 +420,22 @@ export class Property {
420
420
  }
421
421
  return result;
422
422
  }
423
+
424
+ /**
425
+ * Maps the list of properties to a single map.
426
+ * It overrides previously defined properties on the map in case of multiple variables with the same name.
427
+ *
428
+ * @param properties The list of properties to map.
429
+ * @returns A map where keys are property names and values are the property values.
430
+ */
431
+ static toMap(properties: (IProperty | Property)[]): Record<string, any> {
432
+ const result: Record<string, any> = {};
433
+ properties.forEach(p => {
434
+ if (p.enabled === false) {
435
+ return;
436
+ }
437
+ result[p.name] = p.value;
438
+ });
439
+ return result;
440
+ }
423
441
  }
@@ -1,3 +1,6 @@
1
+ import { IProperty, Property } from './Property.js';
2
+ import { UriTemplate } from '../lib/parsers/UriTemplate.js';
3
+
1
4
  export const Kind = 'Core#Server';
2
5
 
3
6
  export interface IServer {
@@ -129,8 +132,10 @@ export class Server {
129
132
 
130
133
  /**
131
134
  * Constructs the final URI from the server configuration.
135
+ *
136
+ * @param variables When set it evaluates the generated URI against these variables
132
137
  */
133
- readUri(): string {
138
+ readUri(variables?: (IProperty | Property)[]): string {
134
139
  const { uri, protocol, basePath } = this;
135
140
  let result = '';
136
141
  if (!uri) {
@@ -148,6 +153,32 @@ export class Server {
148
153
  }
149
154
  result += basePath.startsWith('/') ? basePath : `/${basePath}`
150
155
  }
156
+ if (variables) {
157
+ return this.evaluateUri(result, variables);
158
+ }
159
+ return result;
160
+ }
161
+
162
+ /**
163
+ * Evaluates the URI against the variables.
164
+ *
165
+ * Note, this doesn't throw errors. When error occurs it returns the input string.
166
+ *
167
+ * @param uri The URI to process
168
+ * @param variables The list of variables to use
169
+ * @returns Expanded URI.
170
+ */
171
+ protected evaluateUri(uri: string, variables: (IProperty | Property)[]): string {
172
+ if (!variables || !variables.length) {
173
+ return uri;
174
+ }
175
+ let result = uri;
176
+ try {
177
+ const map = Property.toMap(variables);
178
+ result = new UriTemplate(uri).expand(map, { ignoreMissing: true });
179
+ } catch (e) {
180
+ //
181
+ }
151
182
  return result;
152
183
  }
153
184
  }
@@ -2,7 +2,7 @@ import { IHttpRequest } from 'src/models/HttpRequest.js';
2
2
  import { ActionRunnable } from './ActionRunnable.js';
3
3
  import { IDeleteCookieAction } from '../../../models/actions/runnable/DeleteCookieAction.js';
4
4
  import { Events } from '../../../events/Events.js';
5
- import { IRequestLog } from 'src/models/RequestLog.js';
5
+ import { IRequestLog } from '../../../models/RequestLog.js';
6
6
 
7
7
  export class DeleteCookieRunnable extends ActionRunnable {
8
8
  async response(log: IRequestLog): Promise<void> {
@@ -4,7 +4,7 @@ import { ActionRunnable } from './ActionRunnable.js';
4
4
  import { ISetCookieAction } from '../../../models/actions/runnable/SetCookieAction.js';
5
5
  import { Events } from '../../../events/Events.js';
6
6
  import { RequestDataExtractor } from '../../../data/RequestDataExtractor.js';
7
- import { IRequestLog } from 'src/models/RequestLog.js';
7
+ import { IRequestLog } from '../../../models/RequestLog.js';
8
8
 
9
9
  export class SetCookieRunnable extends ActionRunnable {
10
10
  async request(request: IHttpRequest): Promise<void> {