@typespec/spector 0.1.0-alpha.0
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.
- package/CHANGELOG.md +1 -0
- package/LICENSE +21 -0
- package/cmd/cli.mjs +3 -0
- package/dist/generated-defs/TypeSpec.Spector.d.ts +22 -0
- package/dist/generated-defs/TypeSpec.Spector.d.ts.map +1 -0
- package/dist/generated-defs/TypeSpec.Spector.js +2 -0
- package/dist/generated-defs/TypeSpec.Spector.js.map +1 -0
- package/dist/generated-defs/TypeSpec.Spector.ts-test.d.ts +2 -0
- package/dist/generated-defs/TypeSpec.Spector.ts-test.d.ts.map +1 -0
- package/dist/generated-defs/TypeSpec.Spector.ts-test.js +5 -0
- package/dist/generated-defs/TypeSpec.Spector.ts-test.js.map +1 -0
- package/dist/src/actions/check-coverage.d.ts +11 -0
- package/dist/src/actions/check-coverage.d.ts.map +1 -0
- package/dist/src/actions/check-coverage.js +77 -0
- package/dist/src/actions/check-coverage.js.map +1 -0
- package/dist/src/actions/generate-scenario-summary.d.ts +9 -0
- package/dist/src/actions/generate-scenario-summary.d.ts.map +1 -0
- package/dist/src/actions/generate-scenario-summary.js +49 -0
- package/dist/src/actions/generate-scenario-summary.js.map +1 -0
- package/dist/src/actions/helper.d.ts +21 -0
- package/dist/src/actions/helper.d.ts.map +1 -0
- package/dist/src/actions/helper.js +81 -0
- package/dist/src/actions/helper.js.map +1 -0
- package/dist/src/actions/index.d.ts +2 -0
- package/dist/src/actions/index.d.ts.map +1 -0
- package/dist/src/actions/index.js +2 -0
- package/dist/src/actions/index.js.map +1 -0
- package/dist/src/actions/serve.d.ts +12 -0
- package/dist/src/actions/serve.d.ts.map +1 -0
- package/dist/src/actions/serve.js +73 -0
- package/dist/src/actions/serve.js.map +1 -0
- package/dist/src/actions/server-test.d.ts +7 -0
- package/dist/src/actions/server-test.d.ts.map +1 -0
- package/dist/src/actions/server-test.js +165 -0
- package/dist/src/actions/server-test.js.map +1 -0
- package/dist/src/actions/upload-coverage-report.d.ts +10 -0
- package/dist/src/actions/upload-coverage-report.d.ts.map +1 -0
- package/dist/src/actions/upload-coverage-report.js +19 -0
- package/dist/src/actions/upload-coverage-report.js.map +1 -0
- package/dist/src/actions/upload-scenario-manifest.d.ts +6 -0
- package/dist/src/actions/upload-scenario-manifest.d.ts.map +1 -0
- package/dist/src/actions/upload-scenario-manifest.js +18 -0
- package/dist/src/actions/upload-scenario-manifest.js.map +1 -0
- package/dist/src/actions/validate-mock-apis.d.ts +7 -0
- package/dist/src/actions/validate-mock-apis.d.ts.map +1 -0
- package/dist/src/actions/validate-mock-apis.js +71 -0
- package/dist/src/actions/validate-mock-apis.js.map +1 -0
- package/dist/src/actions/validate-scenarios.d.ts +7 -0
- package/dist/src/actions/validate-scenarios.d.ts.map +1 -0
- package/dist/src/actions/validate-scenarios.js +25 -0
- package/dist/src/actions/validate-scenarios.js.map +1 -0
- package/dist/src/app/app.d.ts +17 -0
- package/dist/src/app/app.d.ts.map +1 -0
- package/dist/src/app/app.js +134 -0
- package/dist/src/app/app.js.map +1 -0
- package/dist/src/app/config.d.ts +15 -0
- package/dist/src/app/config.d.ts.map +1 -0
- package/dist/src/app/config.js +2 -0
- package/dist/src/app/config.js.map +1 -0
- package/dist/src/app/index.d.ts +3 -0
- package/dist/src/app/index.d.ts.map +1 -0
- package/dist/src/app/index.js +3 -0
- package/dist/src/app/index.js.map +1 -0
- package/dist/src/app/request-processor.d.ts +5 -0
- package/dist/src/app/request-processor.d.ts.map +1 -0
- package/dist/src/app/request-processor.js +44 -0
- package/dist/src/app/request-processor.js.map +1 -0
- package/dist/src/cli/cli.d.ts +3 -0
- package/dist/src/cli/cli.d.ts.map +1 -0
- package/dist/src/cli/cli.js +316 -0
- package/dist/src/cli/cli.js.map +1 -0
- package/dist/src/config/config-schema.d.ts +4 -0
- package/dist/src/config/config-schema.d.ts.map +1 -0
- package/dist/src/config/config-schema.js +14 -0
- package/dist/src/config/config-schema.js.map +1 -0
- package/dist/src/config/config.d.ts +4 -0
- package/dist/src/config/config.d.ts.map +1 -0
- package/dist/src/config/config.js +12 -0
- package/dist/src/config/config.js.map +1 -0
- package/dist/src/config/schema-validator.d.ts +18 -0
- package/dist/src/config/schema-validator.d.ts.map +1 -0
- package/dist/src/config/schema-validator.js +43 -0
- package/dist/src/config/schema-validator.js.map +1 -0
- package/dist/src/config/types.d.ts +4 -0
- package/dist/src/config/types.d.ts.map +1 -0
- package/dist/src/config/types.js +2 -0
- package/dist/src/config/types.js.map +1 -0
- package/dist/src/constants.d.ts +4 -0
- package/dist/src/constants.d.ts.map +1 -0
- package/dist/src/constants.js +4 -0
- package/dist/src/constants.js.map +1 -0
- package/dist/src/coverage/common.d.ts +3 -0
- package/dist/src/coverage/common.d.ts.map +1 -0
- package/dist/src/coverage/common.js +9 -0
- package/dist/src/coverage/common.js.map +1 -0
- package/dist/src/coverage/coverage-report.d.ts +3 -0
- package/dist/src/coverage/coverage-report.d.ts.map +1 -0
- package/dist/src/coverage/coverage-report.js +9 -0
- package/dist/src/coverage/coverage-report.js.map +1 -0
- package/dist/src/coverage/coverage-tracker.d.ts +17 -0
- package/dist/src/coverage/coverage-tracker.d.ts.map +1 -0
- package/dist/src/coverage/coverage-tracker.js +125 -0
- package/dist/src/coverage/coverage-tracker.js.map +1 -0
- package/dist/src/coverage/index.d.ts +2 -0
- package/dist/src/coverage/index.d.ts.map +1 -0
- package/dist/src/coverage/index.js +2 -0
- package/dist/src/coverage/index.js.map +1 -0
- package/dist/src/coverage/scenario-manifest.d.ts +6 -0
- package/dist/src/coverage/scenario-manifest.d.ts.map +1 -0
- package/dist/src/coverage/scenario-manifest.js +32 -0
- package/dist/src/coverage/scenario-manifest.js.map +1 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +2 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/lib/decorators.d.ts +24 -0
- package/dist/src/lib/decorators.d.ts.map +1 -0
- package/dist/src/lib/decorators.js +158 -0
- package/dist/src/lib/decorators.js.map +1 -0
- package/dist/src/lib/index.d.ts +4 -0
- package/dist/src/lib/index.d.ts.map +1 -0
- package/dist/src/lib/index.js +6 -0
- package/dist/src/lib/index.js.map +1 -0
- package/dist/src/lib/lib.d.ts +33 -0
- package/dist/src/lib/lib.d.ts.map +1 -0
- package/dist/src/lib/lib.js +31 -0
- package/dist/src/lib/lib.js.map +1 -0
- package/dist/src/lib/tsp-index.d.ts +2 -0
- package/dist/src/lib/tsp-index.d.ts.map +1 -0
- package/dist/src/lib/tsp-index.js +11 -0
- package/dist/src/lib/tsp-index.js.map +1 -0
- package/dist/src/lib/validate.d.ts +3 -0
- package/dist/src/lib/validate.d.ts.map +1 -0
- package/dist/src/lib/validate.js +41 -0
- package/dist/src/lib/validate.js.map +1 -0
- package/dist/src/logger.d.ts +3 -0
- package/dist/src/logger.d.ts.map +1 -0
- package/dist/src/logger.js +10 -0
- package/dist/src/logger.js.map +1 -0
- package/dist/src/routes/admin.d.ts +3 -0
- package/dist/src/routes/admin.d.ts.map +1 -0
- package/dist/src/routes/admin.js +13 -0
- package/dist/src/routes/admin.js.map +1 -0
- package/dist/src/routes/index.d.ts +3 -0
- package/dist/src/routes/index.d.ts.map +1 -0
- package/dist/src/routes/index.js +6 -0
- package/dist/src/routes/index.js.map +1 -0
- package/dist/src/scenarios-resolver.d.ts +17 -0
- package/dist/src/scenarios-resolver.d.ts.map +1 -0
- package/dist/src/scenarios-resolver.js +163 -0
- package/dist/src/scenarios-resolver.js.map +1 -0
- package/dist/src/server/index.d.ts +2 -0
- package/dist/src/server/index.d.ts.map +1 -0
- package/dist/src/server/index.js +2 -0
- package/dist/src/server/index.js.map +1 -0
- package/dist/src/server/server.d.ts +14 -0
- package/dist/src/server/server.d.ts.map +1 -0
- package/dist/src/server/server.js +68 -0
- package/dist/src/server/server.js.map +1 -0
- package/dist/src/spec-utils/import-spec.d.ts +6 -0
- package/dist/src/spec-utils/import-spec.d.ts.map +1 -0
- package/dist/src/spec-utils/import-spec.js +39 -0
- package/dist/src/spec-utils/import-spec.js.map +1 -0
- package/dist/src/spec-utils/index.d.ts +2 -0
- package/dist/src/spec-utils/index.d.ts.map +1 -0
- package/dist/src/spec-utils/index.js +2 -0
- package/dist/src/spec-utils/index.js.map +1 -0
- package/dist/src/utils/body-utils.d.ts +8 -0
- package/dist/src/utils/body-utils.d.ts.map +1 -0
- package/dist/src/utils/body-utils.js +8 -0
- package/dist/src/utils/body-utils.js.map +1 -0
- package/dist/src/utils/diagnostic-reporter.d.ts +13 -0
- package/dist/src/utils/diagnostic-reporter.d.ts.map +1 -0
- package/dist/src/utils/diagnostic-reporter.js +35 -0
- package/dist/src/utils/diagnostic-reporter.js.map +1 -0
- package/dist/src/utils/exec.d.ts +9 -0
- package/dist/src/utils/exec.d.ts.map +1 -0
- package/dist/src/utils/exec.js +30 -0
- package/dist/src/utils/exec.js.map +1 -0
- package/dist/src/utils/file-utils.d.ts +7 -0
- package/dist/src/utils/file-utils.d.ts.map +1 -0
- package/dist/src/utils/file-utils.js +13 -0
- package/dist/src/utils/file-utils.js.map +1 -0
- package/dist/src/utils/index.d.ts +6 -0
- package/dist/src/utils/index.d.ts.map +1 -0
- package/dist/src/utils/index.js +6 -0
- package/dist/src/utils/index.js.map +1 -0
- package/dist/src/utils/misc-utils.d.ts +8 -0
- package/dist/src/utils/misc-utils.d.ts.map +1 -0
- package/dist/src/utils/misc-utils.js +47 -0
- package/dist/src/utils/misc-utils.js.map +1 -0
- package/dist/src/utils/path-utils.d.ts +2 -0
- package/dist/src/utils/path-utils.d.ts.map +1 -0
- package/dist/src/utils/path-utils.js +4 -0
- package/dist/src/utils/path-utils.js.map +1 -0
- package/dist/src/utils/request-utils.d.ts +3 -0
- package/dist/src/utils/request-utils.d.ts.map +1 -0
- package/dist/src/utils/request-utils.js +2 -0
- package/dist/src/utils/request-utils.js.map +1 -0
- package/generated-defs/TypeSpec.Spector.ts +46 -0
- package/generated-defs/TypeSpec.Spector.ts-test.ts +5 -0
- package/lib/main.tsp +37 -0
- package/package.json +79 -0
- package/src/actions/check-coverage.ts +98 -0
- package/src/actions/generate-scenario-summary.ts +63 -0
- package/src/actions/helper.ts +116 -0
- package/src/actions/index.ts +1 -0
- package/src/actions/serve.ts +88 -0
- package/src/actions/server-test.ts +198 -0
- package/src/actions/upload-coverage-report.ts +41 -0
- package/src/actions/upload-scenario-manifest.ts +30 -0
- package/src/actions/validate-mock-apis.ts +91 -0
- package/src/actions/validate-scenarios.ts +32 -0
- package/src/app/app.ts +166 -0
- package/src/app/config.ts +16 -0
- package/src/app/index.ts +2 -0
- package/src/app/request-processor.ts +71 -0
- package/src/cli/cli.ts +368 -0
- package/src/config/config-schema.ts +16 -0
- package/src/config/config.ts +15 -0
- package/src/config/schema-validator.ts +57 -0
- package/src/config/types.ts +3 -0
- package/src/constants.ts +3 -0
- package/src/coverage/common.ts +10 -0
- package/src/coverage/coverage-report.ts +13 -0
- package/src/coverage/coverage-tracker.ts +141 -0
- package/src/coverage/index.ts +1 -0
- package/src/coverage/scenario-manifest.ts +43 -0
- package/src/index.ts +1 -0
- package/src/lib/decorators.ts +225 -0
- package/src/lib/index.ts +18 -0
- package/src/lib/lib.ts +33 -0
- package/src/lib/tsp-index.ts +12 -0
- package/src/lib/validate.ts +55 -0
- package/src/logger.ts +10 -0
- package/src/routes/admin.ts +15 -0
- package/src/routes/index.ts +7 -0
- package/src/scenarios-resolver.ts +209 -0
- package/src/server/index.ts +1 -0
- package/src/server/server.ts +99 -0
- package/src/spec-utils/import-spec.ts +49 -0
- package/src/spec-utils/index.ts +1 -0
- package/src/utils/body-utils.test.ts +18 -0
- package/src/utils/body-utils.ts +8 -0
- package/src/utils/diagnostic-reporter.ts +49 -0
- package/src/utils/exec.ts +36 -0
- package/src/utils/file-utils.ts +14 -0
- package/src/utils/index.ts +5 -0
- package/src/utils/misc-utils.ts +54 -0
- package/src/utils/path-utils.ts +3 -0
- package/src/utils/request-utils.ts +4 -0
- package/temp/.tsbuildinfo +1 -0
- package/tsconfig.build.json +13 -0
- package/tsconfig.json +4 -0
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import {
|
|
2
|
+
$service,
|
|
3
|
+
Enum,
|
|
4
|
+
getNamespaceFullName,
|
|
5
|
+
getTypeName,
|
|
6
|
+
Interface,
|
|
7
|
+
listServices,
|
|
8
|
+
Model,
|
|
9
|
+
Namespace,
|
|
10
|
+
Operation,
|
|
11
|
+
Program,
|
|
12
|
+
} from "@typespec/compiler";
|
|
13
|
+
import {
|
|
14
|
+
$route,
|
|
15
|
+
$server,
|
|
16
|
+
getOperationVerb,
|
|
17
|
+
getRoutePath,
|
|
18
|
+
getServers,
|
|
19
|
+
HttpVerb,
|
|
20
|
+
} from "@typespec/http";
|
|
21
|
+
import { $versioned } from "@typespec/versioning";
|
|
22
|
+
import {
|
|
23
|
+
ScenarioDecorator,
|
|
24
|
+
ScenarioDocDecorator,
|
|
25
|
+
ScenarioServiceDecorator,
|
|
26
|
+
} from "../../generated-defs/TypeSpec.Spector.js";
|
|
27
|
+
import { SpectorStateKeys } from "./lib.js";
|
|
28
|
+
|
|
29
|
+
export const $scenario: ScenarioDecorator = (context, target, name?) => {
|
|
30
|
+
context.program.stateMap(SpectorStateKeys.Scenario).set(target, name ?? target.name);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const $scenarioDoc: ScenarioDocDecorator = (context, target, doc, formatArgs?) => {
|
|
34
|
+
const formattedDoc = formatArgs ? replaceTemplatedStringFromProperties(doc, formatArgs) : doc;
|
|
35
|
+
context.program.stateMap(SpectorStateKeys.ScenarioDoc).set(target, formattedDoc);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const $scenarioService: ScenarioServiceDecorator = (context, target, route, options?) => {
|
|
39
|
+
const properties = new Map().set("title", {
|
|
40
|
+
type: { kind: "String", value: getNamespaceFullName(target).replace(/\./g, "") },
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
context.program.stateSet(SpectorStateKeys.ScenarioService).add(target);
|
|
44
|
+
|
|
45
|
+
const versions = options ? (options as Model).properties.get("versioned")?.type : null;
|
|
46
|
+
if (versions) {
|
|
47
|
+
context.call($versioned, target, versions as Enum);
|
|
48
|
+
}
|
|
49
|
+
context.call($service, target, {
|
|
50
|
+
kind: "Model",
|
|
51
|
+
properties,
|
|
52
|
+
decorators: [],
|
|
53
|
+
projections: [],
|
|
54
|
+
name: "Service",
|
|
55
|
+
derivedModels: [],
|
|
56
|
+
projectionsByName: [],
|
|
57
|
+
} as any);
|
|
58
|
+
context.call($server, target, "http://localhost:3000", "TestServer endpoint");
|
|
59
|
+
context.call($route, target, route);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export function getScenarioDoc(
|
|
63
|
+
program: Program,
|
|
64
|
+
target: Operation | Interface | Namespace,
|
|
65
|
+
): string | undefined {
|
|
66
|
+
return program.stateMap(SpectorStateKeys.ScenarioDoc).get(target);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function replaceTemplatedStringFromProperties(formatString: string, formatArgs: Model) {
|
|
70
|
+
return formatString.replace(/{(\w+)}/g, (_, propName) => {
|
|
71
|
+
const type = formatArgs.properties.get(propName)?.type;
|
|
72
|
+
if (type === undefined) {
|
|
73
|
+
return "";
|
|
74
|
+
}
|
|
75
|
+
return "value" in type ? String(type.value) : getTypeName(type);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface Scenario {
|
|
80
|
+
name: string;
|
|
81
|
+
scenarioDoc: string;
|
|
82
|
+
target: Operation | Interface | Namespace;
|
|
83
|
+
endpoints: ScenarioEndpoint[];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface ScenarioEndpoint {
|
|
87
|
+
verb: HttpVerb;
|
|
88
|
+
path: string;
|
|
89
|
+
target: Operation;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function listScenarios(program: Program): Scenario[] {
|
|
93
|
+
return listScenarioIn(program, program.getGlobalNamespaceType());
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function getScenarioEndpoints(
|
|
97
|
+
program: Program,
|
|
98
|
+
target: Namespace | Interface | Operation,
|
|
99
|
+
): ScenarioEndpoint[] {
|
|
100
|
+
switch (target.kind) {
|
|
101
|
+
case "Namespace":
|
|
102
|
+
return [
|
|
103
|
+
...[...target.namespaces.values()].flatMap((x) => getScenarioEndpoints(program, x)),
|
|
104
|
+
...[...target.interfaces.values()].flatMap((x) => getScenarioEndpoints(program, x)),
|
|
105
|
+
...[...target.operations.values()].flatMap((x) => getScenarioEndpoints(program, x)),
|
|
106
|
+
];
|
|
107
|
+
case "Interface":
|
|
108
|
+
return [...target.operations.values()].flatMap((x) => getScenarioEndpoints(program, x));
|
|
109
|
+
case "Operation":
|
|
110
|
+
return [
|
|
111
|
+
{
|
|
112
|
+
verb: getOperationVerb(program, target) ?? "get",
|
|
113
|
+
path: getOperationRoute(program, target),
|
|
114
|
+
target,
|
|
115
|
+
},
|
|
116
|
+
];
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function getRouteSegments(program: Program, target: Operation | Interface | Namespace): string[] {
|
|
121
|
+
const route = getRoutePath(program, target)?.path;
|
|
122
|
+
const seg = route ? [route] : [];
|
|
123
|
+
switch (target.kind) {
|
|
124
|
+
case "Namespace":
|
|
125
|
+
return target.namespace ? [...getRouteSegments(program, target.namespace), ...seg] : seg;
|
|
126
|
+
case "Interface":
|
|
127
|
+
return target.namespace ? [...getRouteSegments(program, target.namespace), ...seg] : seg;
|
|
128
|
+
|
|
129
|
+
case "Operation":
|
|
130
|
+
return target.interface
|
|
131
|
+
? [...getRouteSegments(program, target.interface), ...seg]
|
|
132
|
+
: target.namespace
|
|
133
|
+
? [...getRouteSegments(program, target.namespace), ...seg]
|
|
134
|
+
: seg;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function getOperationRoute(program: Program, target: Operation): string {
|
|
139
|
+
const template = getRouteSegmentFromServer(program);
|
|
140
|
+
const segments = getRouteSegments(program, target);
|
|
141
|
+
return (
|
|
142
|
+
(template
|
|
143
|
+
? template.endsWith("/") || segments.length === 0
|
|
144
|
+
? template
|
|
145
|
+
: template + "/"
|
|
146
|
+
: "/") + segments.map((x) => (x.startsWith("/") ? x.substring(1) : x)).join("/")
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function getRouteSegmentFromServer(program: Program): string | undefined {
|
|
151
|
+
const serviceNs = listServices(program)[0]?.type;
|
|
152
|
+
const server = getServers(program, serviceNs);
|
|
153
|
+
if (server && server.length === 1) {
|
|
154
|
+
if (server[0].url.indexOf("localhost:3000") > -1) {
|
|
155
|
+
return server[0].url.split("localhost:3000")[1];
|
|
156
|
+
} else if (server[0].url.indexOf("{endpoint}") > -1) {
|
|
157
|
+
return server[0].url.split("{endpoint}")[1];
|
|
158
|
+
} else {
|
|
159
|
+
return server[0].url;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return undefined;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function listScenarioIn(
|
|
166
|
+
program: Program,
|
|
167
|
+
target: Namespace | Interface | Operation,
|
|
168
|
+
): Scenario[] {
|
|
169
|
+
const scenarioName = getScenarioName(program, target);
|
|
170
|
+
if (scenarioName) {
|
|
171
|
+
return [
|
|
172
|
+
{
|
|
173
|
+
target,
|
|
174
|
+
scenarioDoc: getScenarioDoc(program, target)!, /// `onValidate` validate against this happening
|
|
175
|
+
name: scenarioName,
|
|
176
|
+
endpoints: getScenarioEndpoints(program, target),
|
|
177
|
+
},
|
|
178
|
+
];
|
|
179
|
+
}
|
|
180
|
+
switch (target.kind) {
|
|
181
|
+
case "Namespace":
|
|
182
|
+
return [
|
|
183
|
+
...[...target.namespaces.values()].flatMap((x) => listScenarioIn(program, x)),
|
|
184
|
+
...[...target.interfaces.values()].flatMap((x) => listScenarioIn(program, x)),
|
|
185
|
+
...[...target.operations.values()].flatMap((x) => listScenarioIn(program, x)),
|
|
186
|
+
];
|
|
187
|
+
case "Interface":
|
|
188
|
+
return [...target.operations.values()].flatMap((x) => listScenarioIn(program, x));
|
|
189
|
+
case "Operation":
|
|
190
|
+
return [];
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function resolveScenarioName(target: Operation | Interface | Namespace, name: string): string {
|
|
195
|
+
const names = [name];
|
|
196
|
+
|
|
197
|
+
let current: Operation | Interface | Namespace | undefined = target;
|
|
198
|
+
while (true) {
|
|
199
|
+
current =
|
|
200
|
+
current.kind === "Operation" && current.interface ? current.interface : current.namespace;
|
|
201
|
+
if (
|
|
202
|
+
current === undefined ||
|
|
203
|
+
(current.kind === "Namespace" && (current.name === "" || current.name === "_Specs_"))
|
|
204
|
+
) {
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
names.unshift(current.name);
|
|
208
|
+
}
|
|
209
|
+
return names.join("_");
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function isScenario(program: Program, target: Operation | Interface | Namespace): boolean {
|
|
213
|
+
return program.stateMap(SpectorStateKeys.Scenario).has(target);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export function getScenarioName(
|
|
217
|
+
program: Program,
|
|
218
|
+
target: Operation | Interface | Namespace,
|
|
219
|
+
): string | undefined {
|
|
220
|
+
const name = program.stateMap(SpectorStateKeys.Scenario).get(target);
|
|
221
|
+
if (name === undefined) {
|
|
222
|
+
return undefined;
|
|
223
|
+
}
|
|
224
|
+
return resolveScenarioName(target, name);
|
|
225
|
+
}
|
package/src/lib/index.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export {
|
|
2
|
+
$scenario,
|
|
3
|
+
$scenarioDoc,
|
|
4
|
+
$scenarioService,
|
|
5
|
+
Scenario,
|
|
6
|
+
ScenarioEndpoint,
|
|
7
|
+
getScenarioDoc,
|
|
8
|
+
getScenarioEndpoints,
|
|
9
|
+
getScenarioName,
|
|
10
|
+
isScenario,
|
|
11
|
+
listScenarioIn,
|
|
12
|
+
listScenarios,
|
|
13
|
+
} from "./decorators.js";
|
|
14
|
+
export { $lib, reportDiagnostic } from "./lib.js";
|
|
15
|
+
export { $onValidate } from "./validate.js";
|
|
16
|
+
|
|
17
|
+
/** @internal */
|
|
18
|
+
export { $decorators } from "./tsp-index.js";
|
package/src/lib/lib.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { createTypeSpecLibrary, paramMessage } from "@typespec/compiler";
|
|
2
|
+
|
|
3
|
+
export const $lib = createTypeSpecLibrary({
|
|
4
|
+
name: "@typespec/spector",
|
|
5
|
+
diagnostics: {
|
|
6
|
+
"category-invalid": {
|
|
7
|
+
severity: "error",
|
|
8
|
+
messages: {
|
|
9
|
+
default: paramMessage`Category "${"category"}" is not one of the allowed category. Use ${"allowed"}. See https://github.com/Azure/cadl-ranch/blob/main/docs/decorators.md#category`,
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
|
|
13
|
+
"missing-scenario": {
|
|
14
|
+
severity: "warning",
|
|
15
|
+
messages: {
|
|
16
|
+
default: `Operation doesn't belong to a scenario. Use @scenario(name?: string) to mark it as a scenario. See https://github.com/Azure/cadl-ranch/blob/main/docs/decorators.md#scenario`,
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
"missing-scenario-doc": {
|
|
20
|
+
severity: "warning",
|
|
21
|
+
messages: {
|
|
22
|
+
default: `Operation is missing a scenario doc. Use @scenarioDoc to provide the name of the scenario. See https://github.com/Azure/cadl-ranch/blob/main/docs/decorators.md#scenariodoc`,
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
state: {
|
|
27
|
+
Scenario: { description: "Mark a scenario to be executed" },
|
|
28
|
+
ScenarioDoc: { description: "Mark a scenario documentation" },
|
|
29
|
+
ScenarioService: { description: "Mark a scenario service to be executed" },
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
export const { reportDiagnostic, createStateSymbol, stateKeys: SpectorStateKeys } = $lib;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { TypeSpecSpectorDecorators } from "../../generated-defs/TypeSpec.Spector.js";
|
|
2
|
+
import { $scenario, $scenarioDoc, $scenarioService } from "./decorators.js";
|
|
3
|
+
export { $lib } from "./lib.js";
|
|
4
|
+
|
|
5
|
+
/** @internal */
|
|
6
|
+
export const $decorators = {
|
|
7
|
+
"TypeSpec.Spector": {
|
|
8
|
+
scenario: $scenario,
|
|
9
|
+
scenarioDoc: $scenarioDoc,
|
|
10
|
+
scenarioService: $scenarioService,
|
|
11
|
+
} satisfies TypeSpecSpectorDecorators,
|
|
12
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Interface,
|
|
3
|
+
isDeclaredInNamespace,
|
|
4
|
+
isTemplateDeclaration,
|
|
5
|
+
listServices,
|
|
6
|
+
Namespace,
|
|
7
|
+
navigateProgram,
|
|
8
|
+
Operation,
|
|
9
|
+
Program,
|
|
10
|
+
} from "@typespec/compiler";
|
|
11
|
+
import { getScenarioDoc, getScenarioName } from "./decorators.js";
|
|
12
|
+
import { reportDiagnostic } from "./lib.js";
|
|
13
|
+
|
|
14
|
+
export function $onValidate(program: Program) {
|
|
15
|
+
const services = listServices(program);
|
|
16
|
+
navigateProgram(program, {
|
|
17
|
+
operation: (operation) => {
|
|
18
|
+
if (
|
|
19
|
+
(operation.interface && isTemplateDeclaration(operation.interface)) ||
|
|
20
|
+
isTemplateDeclaration(operation)
|
|
21
|
+
) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
// If the scenario is not defined in one of the scenario service then we can ignore it.
|
|
25
|
+
if (!services.some((x) => isDeclaredInNamespace(operation, x.type))) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const scenarioType = checkIsInScenario(program, operation);
|
|
29
|
+
if (!scenarioType) {
|
|
30
|
+
reportDiagnostic(program, { code: "missing-scenario", target: operation });
|
|
31
|
+
} else {
|
|
32
|
+
const doc = getScenarioDoc(program, scenarioType);
|
|
33
|
+
if (doc === undefined) {
|
|
34
|
+
reportDiagnostic(program, { code: "missing-scenario-doc", target: scenarioType });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function checkIsInScenario(
|
|
42
|
+
program: Program,
|
|
43
|
+
type: Operation | Interface | Namespace,
|
|
44
|
+
): Operation | Interface | Namespace | undefined {
|
|
45
|
+
if (getScenarioName(program, type)) {
|
|
46
|
+
return type;
|
|
47
|
+
}
|
|
48
|
+
if (type.kind === "Operation" && type.interface) {
|
|
49
|
+
return checkIsInScenario(program, type.interface);
|
|
50
|
+
}
|
|
51
|
+
if (type.namespace === undefined) {
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
return checkIsInScenario(program, type.namespace);
|
|
55
|
+
}
|
package/src/logger.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Router } from "express";
|
|
2
|
+
import { AdminUrls } from "../constants.js";
|
|
3
|
+
import { logger } from "../logger.js";
|
|
4
|
+
|
|
5
|
+
const router = Router();
|
|
6
|
+
|
|
7
|
+
router.post(AdminUrls.stop, (_req, res) => {
|
|
8
|
+
logger.info("Received signal to stop server. Exiting...");
|
|
9
|
+
res.status(202).end();
|
|
10
|
+
setTimeout(() => {
|
|
11
|
+
process.exit(0);
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export const adminRoutes: Router = router;
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { Operation } from "@typespec/compiler";
|
|
2
|
+
import { isSharedRoute } from "@typespec/http";
|
|
3
|
+
import { ScenarioMockApi } from "@typespec/spec-api";
|
|
4
|
+
import { dirname, join, relative, resolve } from "path";
|
|
5
|
+
import pc from "picocolors";
|
|
6
|
+
import { pathToFileURL } from "url";
|
|
7
|
+
import type { Scenario } from "./lib/decorators.js";
|
|
8
|
+
import { logger } from "./logger.js";
|
|
9
|
+
import { importSpecExpect, importTypeSpec, importTypeSpecHttp } from "./spec-utils/index.js";
|
|
10
|
+
import { findFilesFromPattern } from "./utils/file-utils.js";
|
|
11
|
+
import {
|
|
12
|
+
createDiagnosticReporter,
|
|
13
|
+
Diagnostic,
|
|
14
|
+
ensureScenariosPathExists,
|
|
15
|
+
getSourceLocationStr,
|
|
16
|
+
} from "./utils/index.js";
|
|
17
|
+
import { normalizePath } from "./utils/path-utils.js";
|
|
18
|
+
|
|
19
|
+
export interface MockApiFile {
|
|
20
|
+
path: string;
|
|
21
|
+
scenarios: Record<string, ScenarioMockApi>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface SpecScenarioFile {
|
|
25
|
+
name: string;
|
|
26
|
+
specFilePath: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function findScenarioSpecFiles(scenariosPath: string): Promise<SpecScenarioFile[]> {
|
|
30
|
+
await ensureScenariosPathExists(scenariosPath);
|
|
31
|
+
const normalizedScenarioPath = normalizePath(scenariosPath);
|
|
32
|
+
const pattern = [
|
|
33
|
+
`${normalizedScenarioPath}/**/client.tsp`,
|
|
34
|
+
`${normalizedScenarioPath}/**/main.tsp`,
|
|
35
|
+
];
|
|
36
|
+
logger.debug(`Looking for scenarios in ${pattern}`);
|
|
37
|
+
const fullScenarios = await findFilesFromPattern(pattern);
|
|
38
|
+
logger.info(`Found ${fullScenarios.length} full scenarios.`);
|
|
39
|
+
const scenarioSet = new Set(fullScenarios);
|
|
40
|
+
const scenarios = fullScenarios.filter((scenario) => {
|
|
41
|
+
// Exclude main.tsp that have a client.tsp next to it, we should use that instead
|
|
42
|
+
return !(
|
|
43
|
+
normalizePath(scenario).endsWith("/main.tsp") &&
|
|
44
|
+
scenarioSet.has(normalizePath(join(dirname(scenario), "client.tsp")))
|
|
45
|
+
);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
logger.info(`Found ${scenarios.length} scenarios.`);
|
|
49
|
+
|
|
50
|
+
return scenarios.map((name) => ({
|
|
51
|
+
name: normalizePath(relative(scenariosPath, name))
|
|
52
|
+
.replace("/main.tsp", "")
|
|
53
|
+
.replace("/client.tsp", ""),
|
|
54
|
+
specFilePath: normalizePath(resolve(scenariosPath, name)),
|
|
55
|
+
}));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export async function loadScenarios(
|
|
59
|
+
scenariosPath: string,
|
|
60
|
+
): Promise<[Scenario[], readonly Diagnostic[]]> {
|
|
61
|
+
const scenarioFiles = await findScenarioSpecFiles(scenariosPath);
|
|
62
|
+
const typespecCompiler = await importTypeSpec(scenariosPath);
|
|
63
|
+
const specExpect = await importSpecExpect(scenariosPath);
|
|
64
|
+
const typespecHttp = await importTypeSpecHttp(scenariosPath);
|
|
65
|
+
|
|
66
|
+
const scenarioNames = new Map<string, Scenario[]>();
|
|
67
|
+
const endpoints = new Map<string, Operation[]>();
|
|
68
|
+
const diagnostics = createDiagnosticReporter();
|
|
69
|
+
|
|
70
|
+
for (const { name, specFilePath } of scenarioFiles) {
|
|
71
|
+
logger.debug(`Found scenario "${specFilePath}"`);
|
|
72
|
+
const program = await typespecCompiler.compile(typespecCompiler.NodeHost, specFilePath, {
|
|
73
|
+
additionalImports: ["@typespec/spector"],
|
|
74
|
+
noEmit: true,
|
|
75
|
+
warningAsError: true,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// Workaround https://github.com/Azure/cadl-azure/issues/2458
|
|
79
|
+
const programDiagnostics = program.diagnostics.filter(
|
|
80
|
+
(d) =>
|
|
81
|
+
!(
|
|
82
|
+
d.code === "@azure-tools/typespec-azure-core/casing-style" &&
|
|
83
|
+
typeof d.target === "object" &&
|
|
84
|
+
"kind" in d.target &&
|
|
85
|
+
d.target.kind === "Namespace" &&
|
|
86
|
+
d.target.name === "DPG"
|
|
87
|
+
),
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
if (programDiagnostics.length > 0) {
|
|
91
|
+
for (const item of programDiagnostics) {
|
|
92
|
+
const sourceLocation = typespecCompiler.getSourceLocation(item.target);
|
|
93
|
+
diagnostics.reportDiagnostic({
|
|
94
|
+
message: `${item.message}: ${sourceLocation && getSourceLocationStr(sourceLocation)}`,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
diagnostics.reportDiagnostic({
|
|
99
|
+
message: `${pc.red("✘")} Scenario ${name} is invalid.`,
|
|
100
|
+
});
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const scenarios = specExpect.listScenarios(program);
|
|
105
|
+
logger.debug(` ${scenarios.length} scenarios`);
|
|
106
|
+
|
|
107
|
+
for (const scenario of scenarios) {
|
|
108
|
+
const existing = scenarioNames.get(scenario.name);
|
|
109
|
+
if (existing) {
|
|
110
|
+
existing.push(scenario);
|
|
111
|
+
} else {
|
|
112
|
+
scenarioNames.set(scenario.name, [scenario]);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const service = typespecCompiler.ignoreDiagnostics(typespecHttp.getAllHttpServices(program))[0];
|
|
117
|
+
const server = typespecHttp.getServers(program, service.namespace)?.[0];
|
|
118
|
+
if (server?.url === undefined || !server?.url.includes("{")) {
|
|
119
|
+
const serverPath = server ? new URL(server.url).pathname : "";
|
|
120
|
+
for (const route of service.operations) {
|
|
121
|
+
const path = serverPath + route.path;
|
|
122
|
+
const key = `${route.verb} ${path}`;
|
|
123
|
+
const existing = endpoints.get(key);
|
|
124
|
+
if (existing) {
|
|
125
|
+
if (!isSharedRoute(program, route.operation)) {
|
|
126
|
+
existing.push(route.operation);
|
|
127
|
+
}
|
|
128
|
+
} else {
|
|
129
|
+
endpoints.set(key, [route.operation]);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
for (const [name, scenarios] of scenarioNames.entries()) {
|
|
136
|
+
if (scenarios.length > 1) {
|
|
137
|
+
for (const scenario of scenarios) {
|
|
138
|
+
diagnostics.reportDiagnostic({
|
|
139
|
+
message: `Duplicate scenario name "${name}".`,
|
|
140
|
+
target: scenario.target,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
for (const [path, operations] of endpoints.entries()) {
|
|
147
|
+
if (operations.length > 1) {
|
|
148
|
+
for (const operation of operations) {
|
|
149
|
+
diagnostics.reportDiagnostic({
|
|
150
|
+
message: `Duplicate endpoint path "${path}".`,
|
|
151
|
+
target: operation,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return [[...scenarioNames.values()].map((x) => x[0]), diagnostics.diagnostics];
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export async function loadScenarioMockApiFiles(scenariosPath: string): Promise<MockApiFile[]> {
|
|
161
|
+
const pattern = normalizePath(join(scenariosPath, "../dist/**/*.js"));
|
|
162
|
+
logger.debug(`Looking for mock api files in ${pattern}`);
|
|
163
|
+
const files = await findFilesFromPattern(pattern);
|
|
164
|
+
logger.debug(`Detected ${files.length} mock api files: ${files}`);
|
|
165
|
+
const results: MockApiFile[] = [];
|
|
166
|
+
for (const file of files) {
|
|
167
|
+
const result = await import(pathToFileURL(file).href);
|
|
168
|
+
if (result.Scenarios) {
|
|
169
|
+
logger.debug(`File '${file}' contains ${Object.keys(result.Scenarios).length} scenarios.`);
|
|
170
|
+
results.push({
|
|
171
|
+
path: normalizePath(file),
|
|
172
|
+
scenarios: result.Scenarios,
|
|
173
|
+
});
|
|
174
|
+
} else {
|
|
175
|
+
logger.debug(`File '${file}' is not exporting any scenarios.`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
logger.info("result length: " + results.length);
|
|
179
|
+
return results;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export async function loadScenarioMockApis(
|
|
183
|
+
scenariosPath: string,
|
|
184
|
+
): Promise<Record<string, ScenarioMockApi>> {
|
|
185
|
+
const files = await loadScenarioMockApiFiles(scenariosPath);
|
|
186
|
+
const result: Record<string, ScenarioMockApi> = {};
|
|
187
|
+
|
|
188
|
+
const duplicateTracker: Record<string, string[]> = {};
|
|
189
|
+
for (const file of files) {
|
|
190
|
+
for (const [key, scenario] of Object.entries(file.scenarios)) {
|
|
191
|
+
if (duplicateTracker[key]) {
|
|
192
|
+
duplicateTracker[key].push(file.path);
|
|
193
|
+
} else {
|
|
194
|
+
duplicateTracker[key] = [file.path];
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
result[key] = scenario;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
for (const [key, paths] of Object.entries(duplicateTracker)) {
|
|
202
|
+
if (paths.length >= 2) {
|
|
203
|
+
logger.warn(
|
|
204
|
+
`Scenario ${key} is being defined multiple times in mockapis:\n${paths.map((x) => ` ${x}`).join("\n")}`,
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return result;
|
|
209
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./server.js";
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { RequestExt } from "@typespec/spec-api";
|
|
2
|
+
import bodyParser from "body-parser";
|
|
3
|
+
import express, { ErrorRequestHandler, RequestHandler, Response } from "express";
|
|
4
|
+
import { Server, ServerResponse } from "http";
|
|
5
|
+
import morgan from "morgan";
|
|
6
|
+
import multer from "multer";
|
|
7
|
+
import { logger } from "../logger.js";
|
|
8
|
+
import { cleanupBody } from "../utils/index.js";
|
|
9
|
+
|
|
10
|
+
export interface MockApiServerConfig {
|
|
11
|
+
port: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const errorHandler: ErrorRequestHandler = (err, _req, res, _next) => {
|
|
15
|
+
logger.error("Error", err);
|
|
16
|
+
|
|
17
|
+
const errResponse = err.toJSON
|
|
18
|
+
? err.toJSON()
|
|
19
|
+
: err instanceof Error
|
|
20
|
+
? { name: err.name, message: err.message, stack: err.stack }
|
|
21
|
+
: err;
|
|
22
|
+
|
|
23
|
+
res.status(err.status || 500);
|
|
24
|
+
res.contentType("application/json").send(errResponse).end();
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const rawBodySaver = (
|
|
28
|
+
req: RequestExt,
|
|
29
|
+
res: ServerResponse,
|
|
30
|
+
buf: Buffer,
|
|
31
|
+
encoding: BufferEncoding,
|
|
32
|
+
) => {
|
|
33
|
+
if (buf && buf.length) {
|
|
34
|
+
req.rawBody = cleanupBody(buf.toString(encoding || "utf8"));
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const rawBinaryBodySaver = (
|
|
39
|
+
req: RequestExt,
|
|
40
|
+
res: ServerResponse,
|
|
41
|
+
buf: Buffer,
|
|
42
|
+
encoding: BufferEncoding,
|
|
43
|
+
) => {
|
|
44
|
+
if (buf && buf.length) {
|
|
45
|
+
req.rawBody = buf;
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const loggerstream = {
|
|
50
|
+
write: (message: string) => logger.info(message),
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export class MockApiServer {
|
|
54
|
+
private app: express.Application;
|
|
55
|
+
|
|
56
|
+
constructor(private config: MockApiServerConfig) {
|
|
57
|
+
this.app = express();
|
|
58
|
+
this.app.use(morgan("dev", { stream: loggerstream }));
|
|
59
|
+
this.app.use(bodyParser.json({ verify: rawBodySaver, strict: false }));
|
|
60
|
+
this.app.use(
|
|
61
|
+
bodyParser.json({
|
|
62
|
+
type: "application/merge-patch+json",
|
|
63
|
+
verify: rawBodySaver,
|
|
64
|
+
strict: false,
|
|
65
|
+
}),
|
|
66
|
+
);
|
|
67
|
+
this.app.use(bodyParser.urlencoded({ verify: rawBodySaver, extended: true }));
|
|
68
|
+
this.app.use(bodyParser.text({ type: "*/xml", verify: rawBodySaver }));
|
|
69
|
+
this.app.use(bodyParser.text({ type: "*/pdf", verify: rawBodySaver }));
|
|
70
|
+
this.app.use(bodyParser.text({ type: "text/plain" }));
|
|
71
|
+
this.app.use(
|
|
72
|
+
bodyParser.raw({
|
|
73
|
+
type: ["application/octet-stream", "image/png"],
|
|
74
|
+
limit: "10mb",
|
|
75
|
+
verify: rawBinaryBodySaver,
|
|
76
|
+
}),
|
|
77
|
+
);
|
|
78
|
+
this.app.use(multer().any());
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
public use(route: string, ...handlers: RequestHandler[]): void {
|
|
82
|
+
this.app.use(route, ...handlers);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
public start(): void {
|
|
86
|
+
this.app.use(errorHandler);
|
|
87
|
+
|
|
88
|
+
const server = this.app.listen(this.config.port, () => {
|
|
89
|
+
logger.info(`Started server on ${getAddress(server)}`);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export type ServerRequestHandler = (request: RequestExt, response: Response) => void;
|
|
95
|
+
|
|
96
|
+
const getAddress = (server: Server): string => {
|
|
97
|
+
const address = server?.address();
|
|
98
|
+
return typeof address === "string" ? "pipe " + address : "port " + address?.port;
|
|
99
|
+
};
|