@voxgig/apidef 8.0.2 → 8.1.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/dist/guide/guide.js +17 -0
- package/dist/guide/guide.js.map +1 -1
- package/dist/guide/heuristic01.js +24 -1
- package/dist/guide/heuristic01.js.map +1 -1
- package/dist/transform/field.js +51 -0
- package/dist/transform/field.js.map +1 -1
- package/dist/transform/top.js +48 -0
- package/dist/transform/top.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/types.js.map +1 -1
- package/dist/utility.d.ts +6 -1
- package/dist/utility.js +102 -0
- package/dist/utility.js.map +1 -1
- package/package.json +1 -1
- package/src/guide/guide.ts +19 -0
- package/src/guide/heuristic01.ts +31 -2
- package/src/transform/field.ts +55 -0
- package/src/transform/top.ts +58 -1
- package/src/types.ts +7 -0
- package/src/utility.ts +127 -0
package/src/utility.ts
CHANGED
|
@@ -1243,6 +1243,131 @@ function guideActive(node: any): boolean {
|
|
|
1243
1243
|
}
|
|
1244
1244
|
|
|
1245
1245
|
|
|
1246
|
+
// An API's ACCESS-TOKEN EXCHANGE is not a resource, and must not become an
|
|
1247
|
+
// entity. The shape apidef looks for is the one every such endpoint has:
|
|
1248
|
+
//
|
|
1249
|
+
// 1. The spec as a whole is SECURED (a top-level `security` requirement).
|
|
1250
|
+
// Without that, a per-operation `security: []` clears nothing and
|
|
1251
|
+
// carries no signal at all.
|
|
1252
|
+
// 2. The operation clears that requirement with its own `security: []` —
|
|
1253
|
+
// it is the one call a client can make before it holds a credential,
|
|
1254
|
+
// because it is what issues them.
|
|
1255
|
+
// 3. It is a POST. A credential exchange writes; a GET that happens to
|
|
1256
|
+
// return a field called `token` is far likelier to be a resource.
|
|
1257
|
+
// 4. Its success response carries a TOKEN-shaped field.
|
|
1258
|
+
//
|
|
1259
|
+
// All four together, or it is a resource. There is deliberately no vendor
|
|
1260
|
+
// extension and no overlay to say otherwise (ADR-002): a spec apidef does
|
|
1261
|
+
// not control cannot be annotated anyway, and a heuristic that can be
|
|
1262
|
+
// corrected in guide.aon needs no second correction surface.
|
|
1263
|
+
//
|
|
1264
|
+
// Returns the field names the exchange uses, which is what sdkgen's
|
|
1265
|
+
// `secrets` feature needs to drive it, or null when this is a resource.
|
|
1266
|
+
const AUTH_TOKEN_FIELDS = [
|
|
1267
|
+
'access_token', 'accessToken', 'access-token',
|
|
1268
|
+
'id_token', 'idToken',
|
|
1269
|
+
'token', 'jwt',
|
|
1270
|
+
]
|
|
1271
|
+
|
|
1272
|
+
// What the exchange SENDS. Optional: an operation answering with an access
|
|
1273
|
+
// token is an exchange whether or not apidef recognises the credential it
|
|
1274
|
+
// was bought with, and sdkgen carries its own default for the field name.
|
|
1275
|
+
const AUTH_CREDENTIAL_FIELDS = [
|
|
1276
|
+
'refresh_token', 'refreshToken', 'refresh-token',
|
|
1277
|
+
'client_secret', 'clientSecret',
|
|
1278
|
+
'assertion', 'grant_type', 'grantType',
|
|
1279
|
+
'api_key', 'apiKey', 'apikey',
|
|
1280
|
+
'password', 'code',
|
|
1281
|
+
]
|
|
1282
|
+
|
|
1283
|
+
function authExchangeOp(
|
|
1284
|
+
op: any,
|
|
1285
|
+
specSecured: boolean
|
|
1286
|
+
): { request: string | null, response: string } | null {
|
|
1287
|
+
if (true !== specSecured) {
|
|
1288
|
+
return null
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
// An empty ARRAY, specifically. `security` absent means "inherit the
|
|
1292
|
+
// global requirement"; `security: []` means "no credential needed here".
|
|
1293
|
+
if (!Array.isArray(op?.security) || 0 !== op.security.length) {
|
|
1294
|
+
return null
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
if ('POST' !== String(op?.method ?? '').toUpperCase()) {
|
|
1298
|
+
return null
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
const response = firstFieldMatch(
|
|
1302
|
+
schemaProps(successResponseSchema(op?.responses)), AUTH_TOKEN_FIELDS)
|
|
1303
|
+
|
|
1304
|
+
if (null == response) {
|
|
1305
|
+
return null
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
const request = firstFieldMatch(
|
|
1309
|
+
schemaProps(requestBodySchema(op?.requestBody)), AUTH_CREDENTIAL_FIELDS)
|
|
1310
|
+
|
|
1311
|
+
return { request, response }
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
|
|
1315
|
+
// Does the spec require a credential by default? Only a non-empty top-level
|
|
1316
|
+
// `security` makes a per-operation `security: []` meaningful.
|
|
1317
|
+
function specSecuredByDefault(def: any): boolean {
|
|
1318
|
+
return Array.isArray(def?.security) && 0 < def.security.length
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
|
|
1322
|
+
// The 2xx body schema, OpenAPI 3 (`content`) or Swagger 2 (`schema`).
|
|
1323
|
+
function successResponseSchema(responses: any): any {
|
|
1324
|
+
const res = responses?.['200'] ?? responses?.[200] ??
|
|
1325
|
+
responses?.['201'] ?? responses?.[201]
|
|
1326
|
+
if (null == res) {
|
|
1327
|
+
return null
|
|
1328
|
+
}
|
|
1329
|
+
return res.content?.['application/json']?.schema ?? res.schema ?? null
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
|
|
1333
|
+
function requestBodySchema(requestBody: any): any {
|
|
1334
|
+
if (null == requestBody) {
|
|
1335
|
+
return null
|
|
1336
|
+
}
|
|
1337
|
+
return requestBody.content?.['application/json']?.schema ?? null
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
|
|
1341
|
+
function schemaProps(schema: any): string[] {
|
|
1342
|
+
const props = schema?.properties
|
|
1343
|
+
if (null == props || 'object' !== typeof props) {
|
|
1344
|
+
return []
|
|
1345
|
+
}
|
|
1346
|
+
return Object.keys(props)
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
|
|
1350
|
+
// First name in `names` that the schema declares, compared case-insensitively
|
|
1351
|
+
// so `Access_Token` matches `access_token`. Ordered by the CANDIDATE list, not
|
|
1352
|
+
// by declaration order, so `access_token` wins over a sibling `token`.
|
|
1353
|
+
function firstFieldMatch(props: string[], names: string[]): string | null {
|
|
1354
|
+
const lower = new Map<string, string>()
|
|
1355
|
+
for (const p of props) {
|
|
1356
|
+
const k = p.toLowerCase()
|
|
1357
|
+
if (!lower.has(k)) {
|
|
1358
|
+
lower.set(k, p)
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
for (const name of names) {
|
|
1362
|
+
const hit = lower.get(name.toLowerCase())
|
|
1363
|
+
if (null != hit) {
|
|
1364
|
+
return hit
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
return null
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
|
|
1246
1371
|
function cleanComponentName(
|
|
1247
1372
|
name: string,
|
|
1248
1373
|
isKnownCmp?: (canonizedRemainder: string) => boolean
|
|
@@ -1826,6 +1951,8 @@ export {
|
|
|
1826
1951
|
transliterate,
|
|
1827
1952
|
cleanComponentName,
|
|
1828
1953
|
guideActive,
|
|
1954
|
+
authExchangeOp,
|
|
1955
|
+
specSecuredByDefault,
|
|
1829
1956
|
ensureMinEntityName,
|
|
1830
1957
|
inferFieldType,
|
|
1831
1958
|
normalizeFieldName,
|