@avtechno/sfr 2.1.0 → 2.1.2
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/dist/index.mjs +12 -2
- package/dist/sfr-pipeline.mjs +91 -2
- package/dist/types/sfr-pipeline.d.mts +9 -0
- package/package.json +1 -1
- package/src/index.mts +15 -2
- package/src/observability/logger.mts +24 -28
- package/src/observability/metrics.mts +16 -16
- package/src/observability/middleware/mq.mts +1 -3
- package/src/observability/middleware/rest.mts +1 -1
- package/src/sfr-pipeline.mts +109 -5
package/dist/index.mjs
CHANGED
|
@@ -58,16 +58,26 @@ async function write_combined_openapi_index(cfg, documents) {
|
|
|
58
58
|
const combined = {
|
|
59
59
|
openapi: "3.1.0",
|
|
60
60
|
info: {},
|
|
61
|
-
paths: {}
|
|
61
|
+
paths: {},
|
|
62
|
+
components: {
|
|
63
|
+
schemas: {}
|
|
64
|
+
}
|
|
62
65
|
};
|
|
63
66
|
for (const [protocol, documentation] of Object.entries(documents)) {
|
|
64
67
|
switch (protocol) {
|
|
65
68
|
case "REST":
|
|
66
69
|
{
|
|
67
70
|
const rest_doc = documentation;
|
|
68
|
-
// Merge REST OpenAPI info and
|
|
71
|
+
// Merge REST OpenAPI info, paths, and components into combined doc
|
|
69
72
|
combined.info = rest_doc.info;
|
|
70
73
|
combined.paths = rest_doc.paths;
|
|
74
|
+
// Merge component schemas if they exist
|
|
75
|
+
if (rest_doc.components?.schemas) {
|
|
76
|
+
combined.components.schemas = {
|
|
77
|
+
...combined.components.schemas,
|
|
78
|
+
...rest_doc.components.schemas
|
|
79
|
+
};
|
|
80
|
+
}
|
|
71
81
|
}
|
|
72
82
|
break;
|
|
73
83
|
}
|
package/dist/sfr-pipeline.mjs
CHANGED
|
@@ -509,7 +509,10 @@ export class SFRPipeline {
|
|
|
509
509
|
const spec = {
|
|
510
510
|
openapi: "3.0.0",
|
|
511
511
|
info: Object.assign({}, this.oas_cfg),
|
|
512
|
-
paths: {}
|
|
512
|
+
paths: {},
|
|
513
|
+
components: {
|
|
514
|
+
schemas: {}
|
|
515
|
+
}
|
|
513
516
|
};
|
|
514
517
|
// Iterate through each protocol (e.g., REST, WS, MQ)
|
|
515
518
|
Object.entries(declaration).forEach(([namespace, module]) => {
|
|
@@ -623,9 +626,30 @@ export class SFRPipeline {
|
|
|
623
626
|
document.description = (document.description || "") + rate_limit_note;
|
|
624
627
|
}
|
|
625
628
|
}
|
|
629
|
+
// Generate schema name from namespace and endpoint
|
|
630
|
+
const schema_name = this.generate_schema_name(namespace, endpoint, method);
|
|
626
631
|
//Insert either a parameter or a requestBody according to the method type
|
|
627
632
|
if (validator_type === "joi") {
|
|
628
|
-
|
|
633
|
+
const swagger_schema = j2s(validator).swagger;
|
|
634
|
+
if (method === "get") {
|
|
635
|
+
// For GET requests, convert schema properties to query parameters
|
|
636
|
+
document.parameters = this.convert_schema_to_query_parameters(swagger_schema, schema_name, spec);
|
|
637
|
+
}
|
|
638
|
+
else {
|
|
639
|
+
// For POST/PUT/PATCH/DELETE, use requestBody with application/json
|
|
640
|
+
// Store schema in components
|
|
641
|
+
spec.components.schemas[schema_name] = swagger_schema;
|
|
642
|
+
document.requestBody = {
|
|
643
|
+
required: true,
|
|
644
|
+
content: {
|
|
645
|
+
"application/json": {
|
|
646
|
+
schema: {
|
|
647
|
+
$ref: `#/components/schemas/${schema_name}`
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
};
|
|
652
|
+
}
|
|
629
653
|
}
|
|
630
654
|
// Convert Multer validators to multipart/form-data OpenAPI schema
|
|
631
655
|
if (validator_type === "multer") {
|
|
@@ -644,6 +668,71 @@ export class SFRPipeline {
|
|
|
644
668
|
spec.info.meta = Object.fromEntries(Object.entries(spec.info.meta).map(([k, v]) => [`x-${k}`, v]));
|
|
645
669
|
return spec;
|
|
646
670
|
}
|
|
671
|
+
/**
|
|
672
|
+
* Generates a unique schema name from namespace, endpoint, and method.
|
|
673
|
+
*/
|
|
674
|
+
generate_schema_name(namespace, endpoint, method) {
|
|
675
|
+
// Convert to PascalCase and create a descriptive schema name
|
|
676
|
+
const pascal_case = (str) => str
|
|
677
|
+
.split(/[-_]/)
|
|
678
|
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
679
|
+
.join('');
|
|
680
|
+
const method_prefix = method.charAt(0).toUpperCase() + method.slice(1).toLowerCase();
|
|
681
|
+
const namespace_part = pascal_case(namespace);
|
|
682
|
+
const endpoint_part = pascal_case(endpoint);
|
|
683
|
+
return `${method_prefix}${namespace_part}${endpoint_part}Request`;
|
|
684
|
+
}
|
|
685
|
+
/**
|
|
686
|
+
* Converts a JSON schema to OpenAPI query parameters array.
|
|
687
|
+
* Stores complex schemas in components and references them.
|
|
688
|
+
*/
|
|
689
|
+
convert_schema_to_query_parameters(schema, base_name, spec) {
|
|
690
|
+
const parameters = [];
|
|
691
|
+
if (!schema || !schema.properties) {
|
|
692
|
+
return parameters;
|
|
693
|
+
}
|
|
694
|
+
const required_fields = schema.required || [];
|
|
695
|
+
for (const [prop_name, prop_schema] of Object.entries(schema.properties)) {
|
|
696
|
+
const param_schema = prop_schema;
|
|
697
|
+
const is_required = required_fields.includes(prop_name);
|
|
698
|
+
// Check if the property is a complex type (object or array of objects)
|
|
699
|
+
const is_complex = param_schema.type === 'object' ||
|
|
700
|
+
(param_schema.type === 'array' && param_schema.items?.type === 'object');
|
|
701
|
+
if (is_complex) {
|
|
702
|
+
// Store complex schemas in components and reference them
|
|
703
|
+
const component_name = `${base_name}${prop_name.charAt(0).toUpperCase() + prop_name.slice(1)}`;
|
|
704
|
+
spec.components.schemas[component_name] = param_schema;
|
|
705
|
+
parameters.push({
|
|
706
|
+
name: prop_name,
|
|
707
|
+
in: "query",
|
|
708
|
+
required: is_required,
|
|
709
|
+
description: param_schema.description || "",
|
|
710
|
+
content: {
|
|
711
|
+
"application/json": {
|
|
712
|
+
schema: {
|
|
713
|
+
$ref: `#/components/schemas/${component_name}`
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
else {
|
|
720
|
+
// Simple types can be defined inline
|
|
721
|
+
const param = {
|
|
722
|
+
name: prop_name,
|
|
723
|
+
in: "query",
|
|
724
|
+
required: is_required,
|
|
725
|
+
schema: { ...param_schema }
|
|
726
|
+
};
|
|
727
|
+
if (param_schema.description) {
|
|
728
|
+
param.description = param_schema.description;
|
|
729
|
+
delete param.schema.description;
|
|
730
|
+
}
|
|
731
|
+
parameters.push(param);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
return parameters;
|
|
735
|
+
}
|
|
647
736
|
// Method to generate an AsyncAPI document from a WSNamespaceDeclaration
|
|
648
737
|
generate_ws_async_api_document(declaration) {
|
|
649
738
|
const spec = {
|
|
@@ -68,6 +68,15 @@ export declare class SFRPipeline {
|
|
|
68
68
|
private bind_mq_fns;
|
|
69
69
|
private spec_generation;
|
|
70
70
|
private generate_open_api_document;
|
|
71
|
+
/**
|
|
72
|
+
* Generates a unique schema name from namespace, endpoint, and method.
|
|
73
|
+
*/
|
|
74
|
+
private generate_schema_name;
|
|
75
|
+
/**
|
|
76
|
+
* Converts a JSON schema to OpenAPI query parameters array.
|
|
77
|
+
* Stores complex schemas in components and references them.
|
|
78
|
+
*/
|
|
79
|
+
private convert_schema_to_query_parameters;
|
|
71
80
|
private generate_ws_async_api_document;
|
|
72
81
|
private generate_mq_async_api_document;
|
|
73
82
|
private print_to_console;
|
package/package.json
CHANGED
package/src/index.mts
CHANGED
|
@@ -68,16 +68,27 @@ async function write_combined_openapi_index(cfg: ParserCFG, documents: ServiceDo
|
|
|
68
68
|
const combined: any = {
|
|
69
69
|
openapi: "3.1.0",
|
|
70
70
|
info: {},
|
|
71
|
-
paths: {}
|
|
71
|
+
paths: {},
|
|
72
|
+
components: {
|
|
73
|
+
schemas: {}
|
|
74
|
+
}
|
|
72
75
|
};
|
|
73
76
|
|
|
74
77
|
for (const [protocol, documentation] of Object.entries(documents)) {
|
|
75
78
|
switch (protocol) {
|
|
76
79
|
case "REST": {
|
|
77
80
|
const rest_doc = documentation as OAPI_Document;
|
|
78
|
-
// Merge REST OpenAPI info and
|
|
81
|
+
// Merge REST OpenAPI info, paths, and components into combined doc
|
|
79
82
|
combined.info = rest_doc.info;
|
|
80
83
|
combined.paths = rest_doc.paths;
|
|
84
|
+
|
|
85
|
+
// Merge component schemas if they exist
|
|
86
|
+
if (rest_doc.components?.schemas) {
|
|
87
|
+
combined.components.schemas = {
|
|
88
|
+
...combined.components.schemas,
|
|
89
|
+
...rest_doc.components.schemas
|
|
90
|
+
};
|
|
91
|
+
}
|
|
81
92
|
} break;
|
|
82
93
|
}
|
|
83
94
|
}
|
|
@@ -178,6 +189,8 @@ export {
|
|
|
178
189
|
type LoggerConfig
|
|
179
190
|
} from "./observability/logger.mjs";
|
|
180
191
|
|
|
192
|
+
export { sfr_rest_telemetry } from "./observability/middleware/rest.mjs";
|
|
193
|
+
|
|
181
194
|
export {
|
|
182
195
|
REST,
|
|
183
196
|
WS,
|
|
@@ -9,23 +9,23 @@ import { trace } from "@opentelemetry/api";
|
|
|
9
9
|
|
|
10
10
|
// Define log levels matching standard syslog levels
|
|
11
11
|
const levels = {
|
|
12
|
-
error: 0,
|
|
13
|
-
warn: 1,
|
|
14
|
-
info: 2,
|
|
15
|
-
http: 3,
|
|
16
|
-
verbose: 4,
|
|
17
|
-
debug: 5,
|
|
18
|
-
silly: 6
|
|
12
|
+
error : 0,
|
|
13
|
+
warn : 1,
|
|
14
|
+
info : 2,
|
|
15
|
+
http : 3,
|
|
16
|
+
verbose : 4,
|
|
17
|
+
debug : 5,
|
|
18
|
+
silly : 6
|
|
19
19
|
};
|
|
20
20
|
|
|
21
21
|
const colors = {
|
|
22
|
-
error: "red",
|
|
23
|
-
warn: "yellow",
|
|
24
|
-
info: "green",
|
|
25
|
-
http: "magenta",
|
|
26
|
-
verbose: "cyan",
|
|
27
|
-
debug: "blue",
|
|
28
|
-
silly: "gray"
|
|
22
|
+
error : "red",
|
|
23
|
+
warn : "yellow",
|
|
24
|
+
info : "green",
|
|
25
|
+
http : "magenta",
|
|
26
|
+
verbose : "cyan",
|
|
27
|
+
debug : "blue",
|
|
28
|
+
silly : "gray"
|
|
29
29
|
};
|
|
30
30
|
|
|
31
31
|
winston.addColors(colors);
|
|
@@ -74,14 +74,10 @@ const pretty_format = winston.format.combine(
|
|
|
74
74
|
winston.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss.SSS" }),
|
|
75
75
|
winston.format.colorize({ all: true }),
|
|
76
76
|
winston.format.printf((info) => {
|
|
77
|
-
const trace_suffix = info.trace_id
|
|
78
|
-
? ` [${String(info.trace_id).slice(0, 8)}]`
|
|
79
|
-
: "";
|
|
77
|
+
const trace_suffix = info.trace_id ? ` [${String(info.trace_id).slice(0, 8)}]` : "";
|
|
80
78
|
|
|
81
79
|
let meta_str = "";
|
|
82
|
-
const meta_keys = Object.keys(info).filter(
|
|
83
|
-
(k) => !["level", "message", "timestamp", "trace_id", "span_id", "trace_flags", "service"].includes(k)
|
|
84
|
-
);
|
|
80
|
+
const meta_keys = Object.keys(info).filter((k) => !["level", "message", "timestamp", "trace_id", "span_id", "trace_flags", "service"].includes(k));
|
|
85
81
|
|
|
86
82
|
if (meta_keys.length > 0) {
|
|
87
83
|
const meta_obj: Record<string, any> = {};
|
|
@@ -89,7 +85,7 @@ const pretty_format = winston.format.combine(
|
|
|
89
85
|
meta_str = ` ${JSON.stringify(meta_obj)}`;
|
|
90
86
|
}
|
|
91
87
|
|
|
92
|
-
return
|
|
88
|
+
return `[${info.timestamp}][${info.level}]${trace_suffix}: ${info.message} > ${meta_str}`;
|
|
93
89
|
})
|
|
94
90
|
);
|
|
95
91
|
|
|
@@ -150,13 +146,13 @@ export function create_child_logger(meta: Record<string, any>): winston.Logger {
|
|
|
150
146
|
* Automatically includes trace context when available.
|
|
151
147
|
*/
|
|
152
148
|
export const sfr_logger = {
|
|
153
|
-
error: (message: string, meta?: Record<string, any>) => get_logger().error(message, meta),
|
|
154
|
-
warn: (message: string, meta?: Record<string, any>) => get_logger().warn(message, meta),
|
|
155
|
-
info: (message: string, meta?: Record<string, any>) => get_logger().info(message, meta),
|
|
156
|
-
http: (message: string, meta?: Record<string, any>) => get_logger().http(message, meta),
|
|
157
|
-
verbose: (message: string, meta?: Record<string, any>) => get_logger().verbose(message, meta),
|
|
158
|
-
debug: (message: string, meta?: Record<string, any>) => get_logger().debug(message, meta),
|
|
159
|
-
silly: (message: string, meta?: Record<string, any>) => get_logger().silly(message, meta),
|
|
149
|
+
error : (message: string, meta?: Record<string, any>) => get_logger().error(message, meta),
|
|
150
|
+
warn : (message: string, meta?: Record<string, any>) => get_logger().warn(message, meta),
|
|
151
|
+
info : (message: string, meta?: Record<string, any>) => get_logger().info(message, meta),
|
|
152
|
+
http : (message: string, meta?: Record<string, any>) => get_logger().http(message, meta),
|
|
153
|
+
verbose : (message: string, meta?: Record<string, any>) => get_logger().verbose(message, meta),
|
|
154
|
+
debug : (message: string, meta?: Record<string, any>) => get_logger().debug(message, meta),
|
|
155
|
+
silly : (message: string, meta?: Record<string, any>) => get_logger().silly(message, meta),
|
|
160
156
|
|
|
161
157
|
/**
|
|
162
158
|
* Creates a child logger with additional context.
|
|
@@ -20,26 +20,26 @@ const METER_VERSION = "1.0.0";
|
|
|
20
20
|
*/
|
|
21
21
|
export interface SFRMetrics {
|
|
22
22
|
// REST Metrics
|
|
23
|
-
rest_requests_total: Counter;
|
|
24
|
-
rest_request_duration: Histogram;
|
|
25
|
-
rest_errors_total: Counter;
|
|
26
|
-
rest_request_size: Histogram;
|
|
27
|
-
rest_response_size: Histogram;
|
|
23
|
+
rest_requests_total : Counter;
|
|
24
|
+
rest_request_duration : Histogram;
|
|
25
|
+
rest_errors_total : Counter;
|
|
26
|
+
rest_request_size : Histogram;
|
|
27
|
+
rest_response_size : Histogram;
|
|
28
28
|
|
|
29
29
|
// WebSocket Metrics
|
|
30
|
-
ws_connections_active: UpDownCounter;
|
|
31
|
-
ws_connections_total: Counter;
|
|
32
|
-
ws_events_total: Counter;
|
|
33
|
-
ws_event_duration: Histogram;
|
|
34
|
-
ws_errors_total: Counter;
|
|
30
|
+
ws_connections_active : UpDownCounter;
|
|
31
|
+
ws_connections_total : Counter;
|
|
32
|
+
ws_events_total : Counter;
|
|
33
|
+
ws_event_duration : Histogram;
|
|
34
|
+
ws_errors_total : Counter;
|
|
35
35
|
|
|
36
36
|
// MQ Metrics
|
|
37
|
-
mq_messages_received: Counter;
|
|
38
|
-
mq_messages_published: Counter;
|
|
39
|
-
mq_processing_duration: Histogram;
|
|
40
|
-
mq_errors_total: Counter;
|
|
41
|
-
mq_messages_rejected: Counter;
|
|
42
|
-
mq_messages_acked: Counter;
|
|
37
|
+
mq_messages_received : Counter;
|
|
38
|
+
mq_messages_published : Counter;
|
|
39
|
+
mq_processing_duration : Histogram;
|
|
40
|
+
mq_errors_total : Counter;
|
|
41
|
+
mq_messages_rejected : Counter;
|
|
42
|
+
mq_messages_acked : Counter;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
let sfr_metrics: SFRMetrics | null = null;
|
|
@@ -53,9 +53,7 @@ export function wrap_mq_handler(
|
|
|
53
53
|
const ctx = trace.setSpan(parent_context, span);
|
|
54
54
|
|
|
55
55
|
// Record received metric
|
|
56
|
-
if (metrics) {
|
|
57
|
-
metrics.mq_messages_received.add(1, { queue, pattern });
|
|
58
|
-
}
|
|
56
|
+
if (metrics) metrics.mq_messages_received.add(1, { queue, pattern });
|
|
59
57
|
|
|
60
58
|
try {
|
|
61
59
|
await context.with(ctx, async () => {
|
|
@@ -48,7 +48,7 @@ export function sfr_rest_telemetry() {
|
|
|
48
48
|
"http.user_agent": req.get("user-agent") ?? "unknown",
|
|
49
49
|
"http.request_content_length": req.get("content-length") ?? 0,
|
|
50
50
|
"sfr.protocol": "REST",
|
|
51
|
-
"sfr.route": route
|
|
51
|
+
"sfr.route": route,
|
|
52
52
|
}
|
|
53
53
|
},
|
|
54
54
|
parent_context
|
package/src/sfr-pipeline.mts
CHANGED
|
@@ -88,6 +88,7 @@ export class SFRPipeline {
|
|
|
88
88
|
|
|
89
89
|
async init(base_url?: string): Promise<ServiceDocuments> {
|
|
90
90
|
this.base_url = base_url;
|
|
91
|
+
const observability_enabled = is_observability_enabled();
|
|
91
92
|
|
|
92
93
|
// Initialize observability with service config from oas_cfg
|
|
93
94
|
if (SFRPipeline.observability_options.enabled !== false) {
|
|
@@ -95,12 +96,12 @@ export class SFRPipeline {
|
|
|
95
96
|
}
|
|
96
97
|
|
|
97
98
|
// Add REST telemetry middleware if enabled
|
|
98
|
-
if (this.comms["REST"] &&
|
|
99
|
+
if (this.comms["REST"] && observability_enabled) {
|
|
99
100
|
this.comms["REST"].use(sfr_rest_telemetry());
|
|
100
101
|
}
|
|
101
102
|
|
|
102
103
|
// Instrument Socket.IO if enabled
|
|
103
|
-
if (this.comms["WS"] &&
|
|
104
|
+
if (this.comms["WS"] && observability_enabled) {
|
|
104
105
|
instrument_socket_io(this.comms["WS"]);
|
|
105
106
|
}
|
|
106
107
|
|
|
@@ -562,7 +563,10 @@ export class SFRPipeline {
|
|
|
562
563
|
const spec: OAPI_Document = {
|
|
563
564
|
openapi: "3.0.0",
|
|
564
565
|
info : Object.assign({}, this.oas_cfg),
|
|
565
|
-
paths: {}
|
|
566
|
+
paths: {},
|
|
567
|
+
components: {
|
|
568
|
+
schemas: {}
|
|
569
|
+
}
|
|
566
570
|
};
|
|
567
571
|
|
|
568
572
|
// Iterate through each protocol (e.g., REST, WS, MQ)
|
|
@@ -686,9 +690,32 @@ export class SFRPipeline {
|
|
|
686
690
|
}
|
|
687
691
|
}
|
|
688
692
|
|
|
693
|
+
// Generate schema name from namespace and endpoint
|
|
694
|
+
const schema_name = this.generate_schema_name(namespace, endpoint, method);
|
|
695
|
+
|
|
689
696
|
//Insert either a parameter or a requestBody according to the method type
|
|
690
|
-
if(validator_type === "joi"){
|
|
691
|
-
|
|
697
|
+
if (validator_type === "joi") {
|
|
698
|
+
const swagger_schema = j2s(validator).swagger;
|
|
699
|
+
|
|
700
|
+
if (method === "get") {
|
|
701
|
+
// For GET requests, convert schema properties to query parameters
|
|
702
|
+
document.parameters = this.convert_schema_to_query_parameters(swagger_schema, schema_name, spec);
|
|
703
|
+
} else {
|
|
704
|
+
// For POST/PUT/PATCH/DELETE, use requestBody with application/json
|
|
705
|
+
// Store schema in components
|
|
706
|
+
spec.components.schemas[schema_name] = swagger_schema;
|
|
707
|
+
|
|
708
|
+
document.requestBody = {
|
|
709
|
+
required: true,
|
|
710
|
+
content: {
|
|
711
|
+
"application/json": {
|
|
712
|
+
schema: {
|
|
713
|
+
$ref: `#/components/schemas/${schema_name}`
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
};
|
|
718
|
+
}
|
|
692
719
|
}
|
|
693
720
|
|
|
694
721
|
// Convert Multer validators to multipart/form-data OpenAPI schema
|
|
@@ -710,6 +737,83 @@ export class SFRPipeline {
|
|
|
710
737
|
return spec;
|
|
711
738
|
}
|
|
712
739
|
|
|
740
|
+
/**
|
|
741
|
+
* Generates a unique schema name from namespace, endpoint, and method.
|
|
742
|
+
*/
|
|
743
|
+
private generate_schema_name(namespace: string, endpoint: string, method: string): string {
|
|
744
|
+
// Convert to PascalCase and create a descriptive schema name
|
|
745
|
+
const pascal_case = (str: string) => str
|
|
746
|
+
.split(/[-_]/)
|
|
747
|
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
748
|
+
.join('');
|
|
749
|
+
|
|
750
|
+
const method_prefix = method.charAt(0).toUpperCase() + method.slice(1).toLowerCase();
|
|
751
|
+
const namespace_part = pascal_case(namespace);
|
|
752
|
+
const endpoint_part = pascal_case(endpoint);
|
|
753
|
+
|
|
754
|
+
return `${method_prefix}${namespace_part}${endpoint_part}Request`;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
/**
|
|
758
|
+
* Converts a JSON schema to OpenAPI query parameters array.
|
|
759
|
+
* Stores complex schemas in components and references them.
|
|
760
|
+
*/
|
|
761
|
+
private convert_schema_to_query_parameters(schema: any, base_name: string, spec: OAPI_Document): any[] {
|
|
762
|
+
const parameters: any[] = [];
|
|
763
|
+
|
|
764
|
+
if (!schema || !schema.properties) {
|
|
765
|
+
return parameters;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
const required_fields = schema.required || [];
|
|
769
|
+
|
|
770
|
+
for (const [prop_name, prop_schema] of Object.entries(schema.properties)) {
|
|
771
|
+
const param_schema: any = prop_schema;
|
|
772
|
+
const is_required = required_fields.includes(prop_name);
|
|
773
|
+
|
|
774
|
+
// Check if the property is a complex type (object or array of objects)
|
|
775
|
+
const is_complex = param_schema.type === 'object' ||
|
|
776
|
+
(param_schema.type === 'array' && param_schema.items?.type === 'object');
|
|
777
|
+
|
|
778
|
+
if (is_complex) {
|
|
779
|
+
// Store complex schemas in components and reference them
|
|
780
|
+
const component_name = `${base_name}${prop_name.charAt(0).toUpperCase() + prop_name.slice(1)}`;
|
|
781
|
+
spec.components.schemas[component_name] = param_schema;
|
|
782
|
+
|
|
783
|
+
parameters.push({
|
|
784
|
+
name: prop_name,
|
|
785
|
+
in: "query",
|
|
786
|
+
required: is_required,
|
|
787
|
+
description: param_schema.description || "",
|
|
788
|
+
content: {
|
|
789
|
+
"application/json": {
|
|
790
|
+
schema: {
|
|
791
|
+
$ref: `#/components/schemas/${component_name}`
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
});
|
|
796
|
+
} else {
|
|
797
|
+
// Simple types can be defined inline
|
|
798
|
+
const param: any = {
|
|
799
|
+
name: prop_name,
|
|
800
|
+
in: "query",
|
|
801
|
+
required: is_required,
|
|
802
|
+
schema: { ...param_schema }
|
|
803
|
+
};
|
|
804
|
+
|
|
805
|
+
if (param_schema.description) {
|
|
806
|
+
param.description = param_schema.description;
|
|
807
|
+
delete param.schema.description;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
parameters.push(param);
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
return parameters;
|
|
815
|
+
}
|
|
816
|
+
|
|
713
817
|
// Method to generate an AsyncAPI document from a WSNamespaceDeclaration
|
|
714
818
|
private generate_ws_async_api_document(declaration: WSNamespaceDeclaration): AsyncAPIDocument {
|
|
715
819
|
const spec: any = {
|