@blamejs/core 0.7.106 → 0.8.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 +19 -1
- package/NOTICE +17 -1
- package/README.md +4 -3
- package/index.js +16 -0
- package/lib/asyncapi-bindings.js +160 -0
- package/lib/asyncapi-traits.js +143 -0
- package/lib/asyncapi.js +531 -0
- package/lib/audit.js +6 -0
- package/lib/auth/acr-vocabulary.js +265 -0
- package/lib/auth/auth-time-tracker.js +111 -0
- package/lib/auth/elevation-grant.js +306 -0
- package/lib/auth/sd-jwt-vc-disclosure.js +95 -0
- package/lib/auth/sd-jwt-vc-holder.js +203 -0
- package/lib/auth/sd-jwt-vc-issuer.js +197 -0
- package/lib/auth/sd-jwt-vc.js +526 -0
- package/lib/auth/step-up-policy.js +335 -0
- package/lib/auth/step-up.js +445 -0
- package/lib/compliance-ai-act-logging.js +186 -0
- package/lib/compliance-ai-act-prohibited.js +205 -0
- package/lib/compliance-ai-act-risk.js +189 -0
- package/lib/compliance-ai-act-transparency.js +200 -0
- package/lib/compliance-ai-act.js +558 -0
- package/lib/compliance.js +2 -0
- package/lib/crypto.js +32 -0
- package/lib/flag-cache.js +136 -0
- package/lib/flag-evaluation-context.js +135 -0
- package/lib/flag-providers.js +279 -0
- package/lib/flag-targeting.js +210 -0
- package/lib/flag.js +284 -0
- package/lib/inbox.js +367 -0
- package/lib/mail-arc-sign.js +372 -0
- package/lib/mail-auth.js +2 -0
- package/lib/middleware/ai-act-disclosure.js +166 -0
- package/lib/middleware/asyncapi-serve.js +136 -0
- package/lib/middleware/flag-context.js +76 -0
- package/lib/middleware/index.js +15 -0
- package/lib/middleware/openapi-serve.js +143 -0
- package/lib/middleware/require-step-up.js +186 -0
- package/lib/openapi-paths-builder.js +248 -0
- package/lib/openapi-schema-walk.js +192 -0
- package/lib/openapi-security.js +169 -0
- package/lib/openapi-yaml.js +154 -0
- package/lib/openapi.js +443 -0
- package/lib/pqc-software.js +195 -0
- package/lib/vault/index.js +3 -0
- package/lib/vault-aad.js +259 -0
- package/lib/vendor/MANIFEST.json +29 -0
- package/lib/vendor/noble-post-quantum.cjs +18 -0
- package/lib/ws-client.js +829 -0
- package/package.json +1 -1
- package/sbom.cyclonedx.json +6 -6
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* OpenAPI 3.1 — minimal YAML 1.2 emitter for the document JSON.
|
|
4
|
+
*
|
|
5
|
+
* Why a hand-rolled emitter: the framework is zero-runtime-dep and the
|
|
6
|
+
* vendored yaml-min already handles most cases. This module bridges
|
|
7
|
+
* the OpenAPI document into a YAML rendering operators can paste into
|
|
8
|
+
* Swagger-UI / docs-as-code pipelines that want yaml.
|
|
9
|
+
*
|
|
10
|
+
* Output is pure ASCII, uses 2-space indentation, quotes strings that
|
|
11
|
+
* could be parsed as numbers / booleans / null / yaml-tags / contain
|
|
12
|
+
* special characters. Operators with strict yaml-emitter requirements
|
|
13
|
+
* pipe the document JSON through their own yaml stack instead.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
var { defineClass } = require("./framework-error");
|
|
17
|
+
var OpenApiError = defineClass("OpenApiError", { alwaysPermanent: true });
|
|
18
|
+
|
|
19
|
+
var SPECIAL_KEYS = ["true", "false", "null", "True", "False", "Null",
|
|
20
|
+
"TRUE", "FALSE", "NULL", "yes", "no", "on", "off",
|
|
21
|
+
"Yes", "No", "On", "Off", "YES", "NO", "ON", "OFF",
|
|
22
|
+
"~"];
|
|
23
|
+
|
|
24
|
+
var QUOTE_PATTERN = /[:#&*!|>'"%@`{}[\],?]|^\s|\s$|^-\s|\t/;
|
|
25
|
+
var NUMBER_PATTERN = /^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$/;
|
|
26
|
+
var INT_PATTERN = /^-?\d+$/;
|
|
27
|
+
var DATE_PATTERN = /^\d{4}-\d{2}-\d{2}/;
|
|
28
|
+
|
|
29
|
+
function _needsQuoting(str) {
|
|
30
|
+
if (str.length === 0) return true;
|
|
31
|
+
if (SPECIAL_KEYS.indexOf(str) !== -1) return true;
|
|
32
|
+
if (NUMBER_PATTERN.test(str)) return true;
|
|
33
|
+
if (INT_PATTERN.test(str)) return true;
|
|
34
|
+
if (DATE_PATTERN.test(str)) return true;
|
|
35
|
+
if (QUOTE_PATTERN.test(str)) return true;
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function _quoteString(str) {
|
|
40
|
+
// Use double quotes; escape backslashes + quotes + control chars.
|
|
41
|
+
var out = '"';
|
|
42
|
+
for (var i = 0; i < str.length; i += 1) {
|
|
43
|
+
var ch = str.charAt(i);
|
|
44
|
+
var code = str.charCodeAt(i);
|
|
45
|
+
if (ch === "\\") out += "\\\\";
|
|
46
|
+
else if (ch === '"') out += '\\"';
|
|
47
|
+
else if (code === 0x0a) out += "\\n";
|
|
48
|
+
else if (code === 0x0d) out += "\\r";
|
|
49
|
+
else if (code === 0x09) out += "\\t";
|
|
50
|
+
else if (code < 0x20) out += "\\u" + code.toString(16).padStart(4, "0"); // allow:raw-byte-literal — codepoint hex padding
|
|
51
|
+
else out += ch;
|
|
52
|
+
}
|
|
53
|
+
out += '"';
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function _encodeScalar(value) {
|
|
58
|
+
if (value === null) return "null";
|
|
59
|
+
if (value === undefined) return "null";
|
|
60
|
+
if (typeof value === "boolean") return value ? "true" : "false";
|
|
61
|
+
if (typeof value === "number") {
|
|
62
|
+
if (!isFinite(value)) return "null";
|
|
63
|
+
return String(value);
|
|
64
|
+
}
|
|
65
|
+
if (typeof value === "string") {
|
|
66
|
+
if (_needsQuoting(value)) return _quoteString(value);
|
|
67
|
+
return value;
|
|
68
|
+
}
|
|
69
|
+
if (typeof value === "bigint") return String(value);
|
|
70
|
+
return _quoteString(String(value));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function _encodeKey(key) {
|
|
74
|
+
if (typeof key !== "string") return _quoteString(String(key));
|
|
75
|
+
if (_needsQuoting(key)) return _quoteString(key);
|
|
76
|
+
return key;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function _isScalar(v) {
|
|
80
|
+
return v === null || v === undefined ||
|
|
81
|
+
typeof v === "boolean" || typeof v === "number" ||
|
|
82
|
+
typeof v === "string" || typeof v === "bigint";
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function _isPlainObject(v) {
|
|
86
|
+
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function emit(value, indent) {
|
|
90
|
+
indent = indent || 0;
|
|
91
|
+
var pad = _pad(indent);
|
|
92
|
+
|
|
93
|
+
if (_isScalar(value)) {
|
|
94
|
+
return _encodeScalar(value);
|
|
95
|
+
}
|
|
96
|
+
if (Array.isArray(value)) {
|
|
97
|
+
if (value.length === 0) return "[]";
|
|
98
|
+
var arrLines = [];
|
|
99
|
+
for (var i = 0; i < value.length; i += 1) {
|
|
100
|
+
var entry = value[i];
|
|
101
|
+
if (_isScalar(entry)) {
|
|
102
|
+
arrLines.push(pad + "- " + _encodeScalar(entry));
|
|
103
|
+
} else if (Array.isArray(entry)) {
|
|
104
|
+
arrLines.push(pad + "- " + emit(entry, indent + 1).replace(/^\s+/, ""));
|
|
105
|
+
} else {
|
|
106
|
+
arrLines.push(pad + "-");
|
|
107
|
+
arrLines.push(emit(entry, indent + 1));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return arrLines.join("\n");
|
|
111
|
+
}
|
|
112
|
+
if (_isPlainObject(value)) {
|
|
113
|
+
var keys = Object.keys(value);
|
|
114
|
+
if (keys.length === 0) return "{}";
|
|
115
|
+
var objLines = [];
|
|
116
|
+
for (var k = 0; k < keys.length; k += 1) {
|
|
117
|
+
var keyStr = keys[k];
|
|
118
|
+
var v = value[keyStr];
|
|
119
|
+
var encodedKey = _encodeKey(keyStr);
|
|
120
|
+
if (_isScalar(v)) {
|
|
121
|
+
objLines.push(pad + encodedKey + ": " + _encodeScalar(v));
|
|
122
|
+
} else if (Array.isArray(v) && v.length === 0) {
|
|
123
|
+
objLines.push(pad + encodedKey + ": []");
|
|
124
|
+
} else if (_isPlainObject(v) && Object.keys(v).length === 0) {
|
|
125
|
+
objLines.push(pad + encodedKey + ": {}");
|
|
126
|
+
} else {
|
|
127
|
+
objLines.push(pad + encodedKey + ":");
|
|
128
|
+
objLines.push(emit(v, indent + 1));
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return objLines.join("\n");
|
|
132
|
+
}
|
|
133
|
+
return _encodeScalar(value);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function _pad(indent) {
|
|
137
|
+
var s = "";
|
|
138
|
+
for (var i = 0; i < indent; i += 1) s += " ";
|
|
139
|
+
return s;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function toYaml(doc) {
|
|
143
|
+
if (doc == null || typeof doc !== "object") {
|
|
144
|
+
throw new OpenApiError("openapi/bad-yaml-input",
|
|
145
|
+
"openapi.toYaml: input must be a non-null object");
|
|
146
|
+
}
|
|
147
|
+
return emit(doc, 0) + "\n";
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
module.exports = {
|
|
151
|
+
toYaml: toYaml,
|
|
152
|
+
emit: emit,
|
|
153
|
+
OpenApiError: OpenApiError,
|
|
154
|
+
};
|
package/lib/openapi.js
ADDED
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* b.openapi — OpenAPI 3.1 schema-document builder.
|
|
4
|
+
*
|
|
5
|
+
* Operators describe their public HTTP surface as an OpenAPI 3.1
|
|
6
|
+
* document the framework can serve at /openapi.json (or any path of
|
|
7
|
+
* their choice) for downstream tooling: API consumers, Postman, code-
|
|
8
|
+
* generators, contract-testing rigs.
|
|
9
|
+
*
|
|
10
|
+
* The builder is FRAMEWORK-FACING: it produces a valid OpenAPI 3.1
|
|
11
|
+
* document, but the operator's hand-written contract is the source of
|
|
12
|
+
* truth — it does NOT auto-walk b.router routes (operators frequently
|
|
13
|
+
* want a smaller / different surface published than what the router
|
|
14
|
+
* exposes internally). Future patch may add `fromRouter()` once the
|
|
15
|
+
* route-shape is stable.
|
|
16
|
+
*
|
|
17
|
+
* Public surface:
|
|
18
|
+
*
|
|
19
|
+
* b.openapi.create({ info, servers, externalDocs, tags })
|
|
20
|
+
* -> builder
|
|
21
|
+
*
|
|
22
|
+
* builder.path(method, urlPattern, opts) // add operation
|
|
23
|
+
* builder.schema(name, schema) // reusable component schema
|
|
24
|
+
* builder.response(name, response) // reusable response
|
|
25
|
+
* builder.parameter(name, parameter) // reusable parameter
|
|
26
|
+
* builder.security.add(name, scheme) // add security scheme
|
|
27
|
+
* builder.security.require(requirement) // doc-level security
|
|
28
|
+
* builder.tag({ name, description }) // tag group
|
|
29
|
+
* builder.server({ url, description, variables }) // server URL
|
|
30
|
+
*
|
|
31
|
+
* builder.toJson() -> OpenAPI 3.1 JSON document
|
|
32
|
+
* builder.toYaml() -> YAML serialisation (if vendored YAML present)
|
|
33
|
+
* builder.middleware(opts) -> request-time middleware that serves the doc
|
|
34
|
+
*
|
|
35
|
+
* b.openapi.security.{bearer,basic,apiKey,oauth2,openIdConnect,mtls,dpop}
|
|
36
|
+
* -> security-scheme builders (delegated from openapi-security.js)
|
|
37
|
+
*
|
|
38
|
+
* b.openapi.schemaWalk(input)
|
|
39
|
+
* -> safeSchema -> JSON Schema utility (delegated)
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
var validateOpts = require("./validate-opts");
|
|
43
|
+
var lazyRequire = require("./lazy-require");
|
|
44
|
+
var schemaWalk = require("./openapi-schema-walk");
|
|
45
|
+
var pathsBuilderMod = require("./openapi-paths-builder");
|
|
46
|
+
var openapiSecurity = require("./openapi-security");
|
|
47
|
+
var openapiYaml = require("./openapi-yaml");
|
|
48
|
+
var { defineClass } = require("./framework-error");
|
|
49
|
+
var audit = lazyRequire(function () { return require("./audit"); });
|
|
50
|
+
|
|
51
|
+
var OpenApiError = defineClass("OpenApiError", { alwaysPermanent: true });
|
|
52
|
+
|
|
53
|
+
var OPENAPI_VERSION = "3.1.0";
|
|
54
|
+
|
|
55
|
+
function create(opts) {
|
|
56
|
+
opts = opts || {};
|
|
57
|
+
validateOpts(opts, [
|
|
58
|
+
"info", "servers", "externalDocs", "tags", "security",
|
|
59
|
+
], "openapi.create");
|
|
60
|
+
if (!opts.info || typeof opts.info !== "object") {
|
|
61
|
+
throw new OpenApiError("openapi/bad-info",
|
|
62
|
+
"openapi.create: info object is required (title + version)");
|
|
63
|
+
}
|
|
64
|
+
validateOpts.requireNonEmptyString(opts.info.title,
|
|
65
|
+
"openapi.create: info.title", OpenApiError, "openapi/bad-info");
|
|
66
|
+
validateOpts.requireNonEmptyString(opts.info.version,
|
|
67
|
+
"openapi.create: info.version", OpenApiError, "openapi/bad-info");
|
|
68
|
+
|
|
69
|
+
var paths = new pathsBuilderMod.PathsBuilder();
|
|
70
|
+
var components = {
|
|
71
|
+
schemas: {},
|
|
72
|
+
responses: {},
|
|
73
|
+
parameters: {},
|
|
74
|
+
securitySchemes: {},
|
|
75
|
+
requestBodies: {},
|
|
76
|
+
headers: {},
|
|
77
|
+
examples: {},
|
|
78
|
+
};
|
|
79
|
+
var docTags = Array.isArray(opts.tags) ? opts.tags.slice() : [];
|
|
80
|
+
var docServers = Array.isArray(opts.servers) ? opts.servers.slice() : [];
|
|
81
|
+
var docSecurity = Array.isArray(opts.security) ? opts.security.slice() : [];
|
|
82
|
+
var externalDocs = opts.externalDocs || null;
|
|
83
|
+
|
|
84
|
+
function _validateServerEntry(entry, label) {
|
|
85
|
+
if (!entry || typeof entry !== "object") {
|
|
86
|
+
throw new OpenApiError("openapi/bad-server",
|
|
87
|
+
label + ": server must be an object");
|
|
88
|
+
}
|
|
89
|
+
validateOpts.requireNonEmptyString(entry.url,
|
|
90
|
+
label + ": url", OpenApiError, "openapi/bad-server");
|
|
91
|
+
}
|
|
92
|
+
for (var s = 0; s < docServers.length; s += 1) {
|
|
93
|
+
_validateServerEntry(docServers[s], "openapi.create: servers[" + s + "]");
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
var builder = {
|
|
97
|
+
info: Object.assign({}, opts.info),
|
|
98
|
+
|
|
99
|
+
path: function (method, urlPattern, pathOpts) {
|
|
100
|
+
paths.add(method, urlPattern, pathOpts || {});
|
|
101
|
+
return builder;
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
schema: function (name, schemaSpec) {
|
|
105
|
+
validateOpts.requireNonEmptyString(name, "schema: name",
|
|
106
|
+
OpenApiError, "openapi/bad-component");
|
|
107
|
+
if (Object.prototype.hasOwnProperty.call(components.schemas, name)) {
|
|
108
|
+
throw new OpenApiError("openapi/duplicate-component",
|
|
109
|
+
"schema: component schemas." + name + " already registered");
|
|
110
|
+
}
|
|
111
|
+
components.schemas[name] = schemaWalk.walk(schemaSpec);
|
|
112
|
+
return builder;
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
response: function (name, responseSpec) {
|
|
116
|
+
validateOpts.requireNonEmptyString(name, "response: name",
|
|
117
|
+
OpenApiError, "openapi/bad-component");
|
|
118
|
+
if (!responseSpec || typeof responseSpec !== "object") {
|
|
119
|
+
throw new OpenApiError("openapi/bad-response",
|
|
120
|
+
"response: responseSpec must be an object");
|
|
121
|
+
}
|
|
122
|
+
if (typeof responseSpec.description !== "string") {
|
|
123
|
+
throw new OpenApiError("openapi/missing-response-description",
|
|
124
|
+
"response: description is required");
|
|
125
|
+
}
|
|
126
|
+
components.responses[name] = responseSpec;
|
|
127
|
+
return builder;
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
parameter: function (name, paramSpec) {
|
|
131
|
+
validateOpts.requireNonEmptyString(name, "parameter: name",
|
|
132
|
+
OpenApiError, "openapi/bad-component");
|
|
133
|
+
if (!paramSpec || typeof paramSpec !== "object") {
|
|
134
|
+
throw new OpenApiError("openapi/bad-parameter",
|
|
135
|
+
"parameter: paramSpec must be an object");
|
|
136
|
+
}
|
|
137
|
+
var p = Object.assign({}, paramSpec);
|
|
138
|
+
if (p.schema != null) p.schema = schemaWalk.walk(p.schema);
|
|
139
|
+
components.parameters[name] = p;
|
|
140
|
+
return builder;
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
requestBody: function (name, bodySpec) {
|
|
144
|
+
validateOpts.requireNonEmptyString(name, "requestBody: name",
|
|
145
|
+
OpenApiError, "openapi/bad-component");
|
|
146
|
+
if (!bodySpec || typeof bodySpec !== "object") {
|
|
147
|
+
throw new OpenApiError("openapi/bad-request-body",
|
|
148
|
+
"requestBody: bodySpec must be an object");
|
|
149
|
+
}
|
|
150
|
+
components.requestBodies[name] = bodySpec;
|
|
151
|
+
return builder;
|
|
152
|
+
},
|
|
153
|
+
|
|
154
|
+
header: function (name, headerSpec) {
|
|
155
|
+
validateOpts.requireNonEmptyString(name, "header: name",
|
|
156
|
+
OpenApiError, "openapi/bad-component");
|
|
157
|
+
components.headers[name] = headerSpec;
|
|
158
|
+
return builder;
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
example: function (name, exampleSpec) {
|
|
162
|
+
validateOpts.requireNonEmptyString(name, "example: name",
|
|
163
|
+
OpenApiError, "openapi/bad-component");
|
|
164
|
+
components.examples[name] = exampleSpec;
|
|
165
|
+
return builder;
|
|
166
|
+
},
|
|
167
|
+
|
|
168
|
+
security: {
|
|
169
|
+
add: function (name, scheme) {
|
|
170
|
+
validateOpts.requireNonEmptyString(name, "security.add: name",
|
|
171
|
+
OpenApiError, "openapi/bad-security");
|
|
172
|
+
if (!scheme || typeof scheme !== "object" || typeof scheme.type !== "string") {
|
|
173
|
+
throw new OpenApiError("openapi/bad-security",
|
|
174
|
+
"security.add: scheme must be a securityScheme object with a type");
|
|
175
|
+
}
|
|
176
|
+
components.securitySchemes[name] = scheme;
|
|
177
|
+
return builder;
|
|
178
|
+
},
|
|
179
|
+
require: function (requirement) {
|
|
180
|
+
if (!requirement || typeof requirement !== "object") {
|
|
181
|
+
throw new OpenApiError("openapi/bad-security",
|
|
182
|
+
"security.require: requirement must be an object like { schemeName: ['scope'] }");
|
|
183
|
+
}
|
|
184
|
+
docSecurity.push(requirement);
|
|
185
|
+
return builder;
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
|
|
189
|
+
tag: function (tagSpec) {
|
|
190
|
+
if (!tagSpec || typeof tagSpec !== "object" ||
|
|
191
|
+
typeof tagSpec.name !== "string" || tagSpec.name.length === 0) {
|
|
192
|
+
throw new OpenApiError("openapi/bad-tag",
|
|
193
|
+
"tag: tagSpec.name is required");
|
|
194
|
+
}
|
|
195
|
+
docTags.push(tagSpec);
|
|
196
|
+
return builder;
|
|
197
|
+
},
|
|
198
|
+
|
|
199
|
+
server: function (serverSpec) {
|
|
200
|
+
_validateServerEntry(serverSpec, "server");
|
|
201
|
+
docServers.push(serverSpec);
|
|
202
|
+
return builder;
|
|
203
|
+
},
|
|
204
|
+
|
|
205
|
+
toJson: function () {
|
|
206
|
+
var doc = {
|
|
207
|
+
openapi: OPENAPI_VERSION,
|
|
208
|
+
info: builder.info,
|
|
209
|
+
};
|
|
210
|
+
if (docServers.length > 0) doc.servers = docServers.slice();
|
|
211
|
+
doc.paths = paths.toMap();
|
|
212
|
+
var anyComponent = false;
|
|
213
|
+
var componentsOut = {};
|
|
214
|
+
var keys = ["schemas", "responses", "parameters", "requestBodies",
|
|
215
|
+
"headers", "examples", "securitySchemes"];
|
|
216
|
+
for (var k = 0; k < keys.length; k += 1) {
|
|
217
|
+
var key = keys[k];
|
|
218
|
+
if (Object.keys(components[key]).length > 0) {
|
|
219
|
+
componentsOut[key] = components[key];
|
|
220
|
+
anyComponent = true;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (anyComponent) doc.components = componentsOut;
|
|
224
|
+
if (docSecurity.length > 0) doc.security = docSecurity.slice();
|
|
225
|
+
if (docTags.length > 0) doc.tags = docTags.slice();
|
|
226
|
+
if (externalDocs) doc.externalDocs = externalDocs;
|
|
227
|
+
// Validate security references — every requirement key must be a
|
|
228
|
+
// registered security scheme.
|
|
229
|
+
for (var r = 0; r < docSecurity.length; r += 1) {
|
|
230
|
+
for (var schemeName in docSecurity[r]) {
|
|
231
|
+
if (!Object.prototype.hasOwnProperty.call(docSecurity[r], schemeName)) continue;
|
|
232
|
+
if (!components.securitySchemes[schemeName]) {
|
|
233
|
+
throw new OpenApiError("openapi/dangling-security",
|
|
234
|
+
"toJson: doc-level security references undefined scheme " +
|
|
235
|
+
JSON.stringify(schemeName));
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
// Same check on per-operation security.
|
|
240
|
+
for (var pathKey in doc.paths) {
|
|
241
|
+
if (!Object.prototype.hasOwnProperty.call(doc.paths, pathKey)) continue;
|
|
242
|
+
var pathItem = doc.paths[pathKey];
|
|
243
|
+
for (var methodKey in pathItem) {
|
|
244
|
+
if (!Object.prototype.hasOwnProperty.call(pathItem, methodKey)) continue;
|
|
245
|
+
var op = pathItem[methodKey];
|
|
246
|
+
if (Array.isArray(op.security)) {
|
|
247
|
+
for (var os = 0; os < op.security.length; os += 1) {
|
|
248
|
+
for (var sn in op.security[os]) {
|
|
249
|
+
if (!Object.prototype.hasOwnProperty.call(op.security[os], sn)) continue;
|
|
250
|
+
if (!components.securitySchemes[sn]) {
|
|
251
|
+
throw new OpenApiError("openapi/dangling-security",
|
|
252
|
+
"toJson: " + methodKey.toUpperCase() + " " + pathKey +
|
|
253
|
+
" references undefined security scheme " + JSON.stringify(sn));
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
try {
|
|
261
|
+
audit().safeEmit({
|
|
262
|
+
action: "openapi.document.built",
|
|
263
|
+
outcome: "success",
|
|
264
|
+
actor: null,
|
|
265
|
+
metadata: {
|
|
266
|
+
title: builder.info.title,
|
|
267
|
+
version: builder.info.version,
|
|
268
|
+
pathCount: Object.keys(doc.paths).length,
|
|
269
|
+
schemaCount: Object.keys(components.schemas).length,
|
|
270
|
+
securityCount: Object.keys(components.securitySchemes).length,
|
|
271
|
+
},
|
|
272
|
+
});
|
|
273
|
+
} catch (_e) { /* drop-silent */ }
|
|
274
|
+
return doc;
|
|
275
|
+
},
|
|
276
|
+
|
|
277
|
+
toJsonString: function (indent) {
|
|
278
|
+
return JSON.stringify(builder.toJson(), null, indent || 2);
|
|
279
|
+
},
|
|
280
|
+
|
|
281
|
+
toYaml: function () {
|
|
282
|
+
return openapiYaml.toYaml(builder.toJson());
|
|
283
|
+
},
|
|
284
|
+
|
|
285
|
+
middleware: function (mwOpts) {
|
|
286
|
+
mwOpts = mwOpts || {};
|
|
287
|
+
validateOpts(mwOpts, ["pretty", "cacheControl"], "openapi.builder.middleware");
|
|
288
|
+
var pretty = mwOpts.pretty === true ? 2 : 0;
|
|
289
|
+
var cacheControl = (typeof mwOpts.cacheControl === "string" && mwOpts.cacheControl.length > 0)
|
|
290
|
+
? mwOpts.cacheControl
|
|
291
|
+
: "public, max-age=300";
|
|
292
|
+
// Memoize the JSON between calls; re-build if the operator
|
|
293
|
+
// calls forceRebuild().
|
|
294
|
+
var cached = null;
|
|
295
|
+
var cachedString = null;
|
|
296
|
+
function _rebuild() {
|
|
297
|
+
cached = builder.toJson();
|
|
298
|
+
cachedString = JSON.stringify(cached, null, pretty);
|
|
299
|
+
}
|
|
300
|
+
_rebuild();
|
|
301
|
+
var mw = function (req, res, next) {
|
|
302
|
+
if (typeof res.writeHead !== "function") return next();
|
|
303
|
+
var body = cachedString;
|
|
304
|
+
res.writeHead(200, { // allow:raw-byte-literal — HTTP 200 status
|
|
305
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
306
|
+
"Content-Length": Buffer.byteLength(body),
|
|
307
|
+
"Cache-Control": cacheControl,
|
|
308
|
+
});
|
|
309
|
+
res.end(body);
|
|
310
|
+
};
|
|
311
|
+
mw.forceRebuild = _rebuild;
|
|
312
|
+
return mw;
|
|
313
|
+
},
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
return builder;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Parse + validate an external OpenAPI 3.1 JSON document. Operators
|
|
320
|
+
// hand a doc that arrived from a downstream integration (consumer
|
|
321
|
+
// hand-edited, contract-test fixture, third-party publish) and want
|
|
322
|
+
// the framework's gate to enforce the same shape rules `toJson` does
|
|
323
|
+
// on builder output.
|
|
324
|
+
//
|
|
325
|
+
// Returns `{ doc, errors[] }`. `doc` is the parsed object (whether
|
|
326
|
+
// valid or not, so the operator can inspect what they got);
|
|
327
|
+
// `errors` is an array of strings — empty on a valid document.
|
|
328
|
+
//
|
|
329
|
+
// Throws (config-time entry-point) on invalid JSON / wrong type.
|
|
330
|
+
function parse(jsonStringOrObject) {
|
|
331
|
+
var doc;
|
|
332
|
+
if (typeof jsonStringOrObject === "string") {
|
|
333
|
+
try { doc = JSON.parse(jsonStringOrObject); } // allow:bare-json-parse — operator-supplied OpenAPI doc; size-bounded by caller
|
|
334
|
+
catch (e) {
|
|
335
|
+
throw new OpenApiError("openapi/bad-json",
|
|
336
|
+
"openapi.parse: invalid JSON — " + e.message);
|
|
337
|
+
}
|
|
338
|
+
} else if (jsonStringOrObject != null && typeof jsonStringOrObject === "object") {
|
|
339
|
+
doc = jsonStringOrObject;
|
|
340
|
+
} else {
|
|
341
|
+
throw new OpenApiError("openapi/bad-input",
|
|
342
|
+
"openapi.parse: input must be a JSON string or a plain object");
|
|
343
|
+
}
|
|
344
|
+
var errors = [];
|
|
345
|
+
if (typeof doc.openapi !== "string") {
|
|
346
|
+
errors.push("missing or non-string `openapi` version field (must be 3.1.x)");
|
|
347
|
+
} else if (doc.openapi.indexOf("3.1") !== 0) {
|
|
348
|
+
errors.push("`openapi` version must be 3.1.x — got " + JSON.stringify(doc.openapi));
|
|
349
|
+
}
|
|
350
|
+
if (!doc.info || typeof doc.info !== "object") {
|
|
351
|
+
errors.push("missing or non-object `info`");
|
|
352
|
+
} else {
|
|
353
|
+
if (typeof doc.info.title !== "string" || doc.info.title.length === 0) {
|
|
354
|
+
errors.push("info.title must be a non-empty string");
|
|
355
|
+
}
|
|
356
|
+
if (typeof doc.info.version !== "string" || doc.info.version.length === 0) {
|
|
357
|
+
errors.push("info.version must be a non-empty string");
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
if (doc.paths != null && typeof doc.paths !== "object") {
|
|
361
|
+
errors.push("`paths` must be an object when present");
|
|
362
|
+
} else if (doc.paths) {
|
|
363
|
+
for (var pathKey in doc.paths) {
|
|
364
|
+
if (!Object.prototype.hasOwnProperty.call(doc.paths, pathKey)) continue;
|
|
365
|
+
if (pathKey.charAt(0) !== "/") {
|
|
366
|
+
errors.push("path " + JSON.stringify(pathKey) + " must start with '/'");
|
|
367
|
+
}
|
|
368
|
+
var pathItem = doc.paths[pathKey];
|
|
369
|
+
if (!pathItem || typeof pathItem !== "object") {
|
|
370
|
+
errors.push("paths[" + JSON.stringify(pathKey) + "] must be an object");
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
var validMethods = ["get", "put", "post", "delete", "options", "head", "patch", "trace"];
|
|
374
|
+
for (var methodKey in pathItem) {
|
|
375
|
+
if (!Object.prototype.hasOwnProperty.call(pathItem, methodKey)) continue;
|
|
376
|
+
if (validMethods.indexOf(methodKey) === -1) continue; // allow non-method fields like 'parameters', 'summary', '$ref'
|
|
377
|
+
var op = pathItem[methodKey];
|
|
378
|
+
if (!op || typeof op !== "object") {
|
|
379
|
+
errors.push(methodKey.toUpperCase() + " " + pathKey + ": operation must be an object");
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
if (!op.responses || typeof op.responses !== "object" ||
|
|
383
|
+
Object.keys(op.responses).length === 0) {
|
|
384
|
+
errors.push(methodKey.toUpperCase() + " " + pathKey +
|
|
385
|
+
": responses object required (per OpenAPI 3.1 §4.8.5)");
|
|
386
|
+
} else {
|
|
387
|
+
for (var statusKey in op.responses) {
|
|
388
|
+
if (!Object.prototype.hasOwnProperty.call(op.responses, statusKey)) continue;
|
|
389
|
+
var resp = op.responses[statusKey];
|
|
390
|
+
if (!resp || typeof resp !== "object") {
|
|
391
|
+
errors.push(methodKey.toUpperCase() + " " + pathKey +
|
|
392
|
+
" response " + statusKey + ": must be an object");
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
if (resp["$ref"]) continue; // $ref short-circuit
|
|
396
|
+
if (typeof resp.description !== "string" || resp.description.length === 0) {
|
|
397
|
+
errors.push(methodKey.toUpperCase() + " " + pathKey +
|
|
398
|
+
" response " + statusKey +
|
|
399
|
+
": description is required (per OpenAPI 3.1 §4.8.16)");
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
if (Array.isArray(op.parameters)) {
|
|
404
|
+
for (var pi = 0; pi < op.parameters.length; pi += 1) {
|
|
405
|
+
var p = op.parameters[pi];
|
|
406
|
+
if (!p || typeof p !== "object") continue;
|
|
407
|
+
if (p["$ref"]) continue;
|
|
408
|
+
if (p.in === "path" && p.required !== true) {
|
|
409
|
+
errors.push(methodKey.toUpperCase() + " " + pathKey +
|
|
410
|
+
" parameters[" + pi + "]: path parameter " +
|
|
411
|
+
JSON.stringify(p.name) + " must have required=true");
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
// Dangling security references — every requirement key must resolve
|
|
419
|
+
// to a registered security scheme.
|
|
420
|
+
var securitySchemes = (doc.components && doc.components.securitySchemes) || {};
|
|
421
|
+
if (Array.isArray(doc.security)) {
|
|
422
|
+
for (var s = 0; s < doc.security.length; s += 1) {
|
|
423
|
+
for (var schemeName in doc.security[s]) {
|
|
424
|
+
if (!Object.prototype.hasOwnProperty.call(doc.security[s], schemeName)) continue;
|
|
425
|
+
if (!securitySchemes[schemeName]) {
|
|
426
|
+
errors.push("doc-level security references undefined scheme " +
|
|
427
|
+
JSON.stringify(schemeName));
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
return { doc: doc, errors: errors, valid: errors.length === 0 };
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
module.exports = {
|
|
436
|
+
create: create,
|
|
437
|
+
parse: parse,
|
|
438
|
+
schemaWalk: schemaWalk.walk,
|
|
439
|
+
security: openapiSecurity,
|
|
440
|
+
toYaml: openapiYaml.toYaml,
|
|
441
|
+
VERSION: OPENAPI_VERSION,
|
|
442
|
+
OpenApiError: OpenApiError,
|
|
443
|
+
};
|