UNPKG

7.7 kBPlain TextView Raw
1import { appendFileSync, createWriteStream, writeFileSync } from 'fs';
2import { generate } from 'node-plantuml';
3import { OUTPUT_DIR } from '.';
4import { isArrayOfStrings, objectFromEntries, trim } from './util';
5
6export interface DiagramConfiguration {
7 readonly hiddenFields?: string[];
8 readonly hidePlaintext?: boolean;
9}
10
11export function isValidDiagramConfiguration(
12 toBeValidated: unknown,
13): toBeValidated is DiagramConfiguration {
14 if (typeof toBeValidated !== 'object' || toBeValidated === null) {
15 return false;
16 }
17 const diagramConfiguration = toBeValidated as DiagramConfiguration;
18
19 return (
20 ['boolean', 'undefined'].includes(
21 typeof diagramConfiguration.hidePlaintext,
22 ) &&
23 (typeof diagramConfiguration.hiddenFields === 'undefined' ||
24 isArrayOfStrings(diagramConfiguration.hiddenFields))
25 );
26}
27
28function getInputFile(scenario: string): string {
29 return `${OUTPUT_DIR()}/_${scenario}.input`;
30}
31
32function getOutputFile(scenario: string): string {
33 return `${OUTPUT_DIR()}/_${scenario}.png`;
34}
35
36const hidingText = '***';
37
38function hideFields(payload: object, hiddenFields: string[]): object {
39 return objectFromEntries(
40 Object.entries(payload).map(([key, value]) =>
41 hiddenFields.includes(key) ? [key, hidingText] : [key, value],
42 ),
43 );
44}
45
46function hideFieldsIfNeeded(
47 payload: unknown,
48 hiddenFields?: string[],
49): unknown {
50 return typeof payload === 'object' &&
51 payload !== null &&
52 hiddenFields !== undefined &&
53 hiddenFields.length !== 0
54 ? hideFields(payload, hiddenFields)
55 : payload;
56}
57
58function hidePlaintextIfNeeded(payload: string, hidePlaintext = false): string {
59 return hidePlaintext ? hidingText : payload;
60}
61
62function formatBinaryPayload(payload: Buffer): string {
63 return `binary data (${(payload as Buffer).length} bytes)`;
64}
65
66function formatPlaintextPayload(
67 payload: string,
68 diagramConfiguration: DiagramConfiguration,
69): string {
70 return trim(
71 hidePlaintextIfNeeded(payload, diagramConfiguration.hidePlaintext),
72 30,
73 );
74}
75
76function formatObjectPayload(
77 payload: unknown,
78 diagramConfiguration: DiagramConfiguration,
79): string {
80 return JSON.stringify(
81 hideFieldsIfNeeded(payload, diagramConfiguration.hiddenFields),
82 null,
83 1,
84 );
85}
86
87export function formatPayload(
88 payload: unknown,
89 diagramConfiguration: DiagramConfiguration,
90): string {
91 if (Buffer.isBuffer(payload)) {
92 return formatBinaryPayload(payload);
93 }
94 if (typeof payload === 'string') {
95 return formatPlaintextPayload(payload, diagramConfiguration);
96 }
97 return formatObjectPayload(payload, diagramConfiguration);
98}
99
100function currentTimestamp(): string {
101 return new Date().toISOString();
102}
103
104function enquote(str: string): string {
105 return `"${str}"`;
106}
107
108export const initDiagramCreation = (scenarioId: string): void => {
109 writeFileSync(getInputFile(scenarioId), '');
110 const initValues = [
111 '@startuml',
112 'autonumber',
113 'skinparam handwritten false',
114 'control MQTT',
115 'actor ALT #red\n',
116 ];
117 appendFileSync(getInputFile(scenarioId), initValues.join('\n'));
118};
119
120export const addRequest = (
121 scenarioId: string,
122 target: string,
123 url: string,
124 data: unknown,
125 diagramConfiguration: DiagramConfiguration,
126): void => {
127 const enquotedTarget = enquote(target);
128 const request = `ALT -> ${enquotedTarget}: ${url}\nactivate ${enquotedTarget}\n${
129 data
130 ? `note right\n**${currentTimestamp()}**\n${formatPayload(
131 data,
132 diagramConfiguration,
133 )}\nend note\n`
134 : ''
135 }`;
136
137 appendFileSync(getInputFile(scenarioId), request);
138};
139
140export const addSuccessfulResponse = (
141 scenarioId: string,
142 source: string,
143 status: string,
144 body: unknown,
145 diagramConfiguration: DiagramConfiguration,
146): void => {
147 doAddResponse(scenarioId, source, status, 'green');
148 if (body) {
149 const note = `note left\n**${currentTimestamp()}**\n${formatPayload(
150 body,
151 diagramConfiguration,
152 )}\nend note\n`;
153 appendFileSync(getInputFile(scenarioId), note);
154 }
155};
156
157export const addFailedResponse = (
158 scenarioId: string,
159 source: string,
160 status: string,
161 body: string,
162 diagramConfiguration: DiagramConfiguration,
163): void => {
164 doAddResponse(scenarioId, source, status, 'red');
165 appendFileSync(
166 getInputFile(scenarioId),
167 `note right: <color red>${formatPayload(
168 body,
169 diagramConfiguration,
170 )}</color>\n||20||\n`,
171 );
172};
173
174const doAddResponse = (
175 scenarioId: string,
176 source: string,
177 status: string,
178 color: string,
179): void => {
180 const enquotedSource = enquote(source);
181 appendFileSync(
182 getInputFile(scenarioId),
183 `${enquotedSource} --> ALT: <color ${color}>${status}</color>\ndeactivate ${enquotedSource}\n`,
184 );
185};
186
187export const addDelay = (scenarioId: string, durationInSec: number): void => {
188 appendFileSync(
189 getInputFile(scenarioId),
190 `\n...sleep ${durationInSec} s...\n`,
191 );
192};
193
194export const addWsMessage = (
195 scenarioId: string,
196 source: string,
197 payload: unknown,
198 diagramConfiguration: DiagramConfiguration,
199): void => {
200 const enquotedSource = enquote(source);
201 appendFileSync(
202 getInputFile(scenarioId),
203 `${enquotedSource} -[#0000FF]->o ALT : [WS]\n`,
204 );
205 const note = `note left #aqua\n**${currentTimestamp()}**\n${formatPayload(
206 payload,
207 diagramConfiguration,
208 )}\nend note\n`;
209 appendFileSync(getInputFile(scenarioId), note);
210};
211
212export const addMqttMessage = (
213 scenarioId: string,
214 topic: string,
215 payload: unknown,
216 diagramConfiguration: DiagramConfiguration,
217): void => {
218 appendFileSync(
219 getInputFile(scenarioId),
220 `MQTT -[#green]->o ALT : ${topic}\n`,
221 );
222 const note = `note right #99FF99\n**${currentTimestamp()}**\n${formatPayload(
223 payload,
224 diagramConfiguration,
225 )}\nend note\n`;
226 appendFileSync(getInputFile(scenarioId), note);
227};
228
229export const addMqttPublishMessage = (
230 scenarioId: string,
231 topic: string,
232 payload: any,
233 diagramConfiguration: DiagramConfiguration,
234): void => {
235 appendFileSync(
236 getInputFile(scenarioId),
237 `ALT -[#green]->o MQTT : ${topic}\n`,
238 );
239 const note = `note left #99FF99\n**${currentTimestamp()}**\n${formatPayload(
240 JSON.parse(payload),
241 diagramConfiguration,
242 )}\nend note\n`;
243 appendFileSync(getInputFile(scenarioId), note);
244};
245
246export const addAMQPReceivedMessage = (
247 scenarioId: string,
248 source: string,
249 exchange: string,
250 routingKey: string,
251 payload: unknown,
252 diagramConfiguration: DiagramConfiguration,
253): void => {
254 const enquotedSource = enquote(source);
255 appendFileSync(
256 getInputFile(scenarioId),
257 `${enquotedSource} -[#FF6600]->o ALT : ${exchange}/${routingKey}\n`,
258 );
259 const note = `note left #FF6600\n**${currentTimestamp()}**\n${formatPayload(
260 payload,
261 diagramConfiguration,
262 )}\nend note\n`;
263 appendFileSync(getInputFile(scenarioId), note);
264};
265
266export const generateSequenceDiagram = (scenarioId: string): Promise<void> =>
267 new Promise<void>(resolve => {
268 appendFileSync(getInputFile(scenarioId), '\n@enduml');
269 const gen = generate(getInputFile(scenarioId));
270 gen.out.pipe(createWriteStream(getOutputFile(scenarioId)));
271 gen.out.on('end', () => resolve());
272 });