From bbee29b57533aaf84ca25b39c78f4cc7f7753289 Mon Sep 17 00:00:00 2001 From: kubectl Date: Sat, 27 Jul 2024 22:31:28 +0200 Subject: [PATCH 1/3] feat: add x-feature plugin --- .github/redocly-plugins/x-feature.js | 141 +++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 .github/redocly-plugins/x-feature.js diff --git a/.github/redocly-plugins/x-feature.js b/.github/redocly-plugins/x-feature.js new file mode 100644 index 00000000..59a2110a --- /dev/null +++ b/.github/redocly-plugins/x-feature.js @@ -0,0 +1,141 @@ +const id = 'x-feature'; + +/** +Example: + +source file +/auth/user: + get: + summary: Login and/or Get Current User Info + tags: + - authentication + x-codeSamples: + $ref: "../codeSamples/authentication.yaml#/~1auth~1user/get" + responses: + '200': + x-feature: + required-features: [ oneOf ] + desired: + schema: + oneOf: + - $ref: ../responses/authentication/CurrentUserLoginResponse.yaml + - $ref: '#/components/schemas/TwoFactorRequired' + fallback: + $ref: ../responses/authentication/CurrentUserLoginResponse.yaml + '401': + $ref: ../responses/MissingCredentialsError.yaml + +rendered file with x-feature enabled with available features: [ oneOf ] +/auth/user: + get: + summary: Login and/or Get Current User Info + tags: + - authentication + x-codeSamples: + $ref: "../codeSamples/authentication.yaml#/~1auth~1user/get" + responses: + '200': + schema: + oneOf: + - $ref: ../responses/authentication/CurrentUserLoginResponse.yaml + - $ref: '#/components/schemas/TwoFactorRequired' + '401': + $ref: ../responses/MissingCredentialsError.yaml + + +rendered file with x-feature enabled no features enabled: +/auth/user: + get: + summary: Login and/or Get Current User Info + tags: + - authentication + x-codeSamples: + $ref: "../codeSamples/authentication.yaml#/~1auth~1user/get" + responses: + '200': + $ref: ../responses/authentication/CurrentUserLoginResponse.yaml + '401': + $ref: ../responses/MissingCredentialsError.yaml + */ + + + +/** @type {import('@redocly/openapi-cli').CustomRulesConfig} */ +const decorators = { + oas3: { + 'fallback-replace': ({ available_features }) => { + if (!Array.isArray(available_features)) { + throw new Error('available_features must be an array'); + } + return { + any: { + leave(obj, _ctx) { + if (obj['x-feature']) { + const feature = obj['x-feature']; + valid_feature_configuration(feature); // throws if not valid + + if (feature['required-features'].every(f => available_features.includes(f))) { + mergeDeep(obj, feature['desired']); + } else { + mergeDeep(obj, feature['fallback']); + } + + delete obj['x-feature']; + } + } + }, + } + }, + 'remove-fallback-components': () => {} // not implemented should remove files / components that are not used + } +} + +function valid_feature_configuration(obj) { + if (!obj['desired']) { + throw new Error('x-feature: desired is required'); + } + if (!obj['fallback']) { + throw new Error('x-feature: fallback is required'); + } + if (!obj['required-features'] || !Array.isArray(obj['required-features'])) { + throw new Error('x-feature: required-features is required and must be an array'); + } +} + +/** + * Simple object check. + * @param item + * @returns {boolean} + */ +function isObject(item) { + return (item && typeof item === 'object' && !Array.isArray(item)); +} + +/** + * Source: https://stackoverflow.com/questions/27936772/how-to-deep-merge-instead-of-shallow-merge + * Deep merge two objects. + * @param target + * @param ...sources + */ +function mergeDeep(target, ...sources) { + if (!sources.length) return target; + const source = sources.shift(); + + if (isObject(target) && isObject(source)) { + for (const key in source) { + if (isObject(source[key])) { + if (!target[key]) Object.assign(target, { [key]: {} }); + mergeDeep(target[key], source[key]); + } else { + Object.assign(target, { [key]: source[key] }); + } + } + } + + return mergeDeep(target, ...sources); +} + +module.exports = { + id, + decorators, +}; \ No newline at end of file From e43a875e64fc295ae5bada20ad099cbfdc5c9d8c Mon Sep 17 00:00:00 2001 From: kubectl Date: Sat, 27 Jul 2024 22:31:48 +0200 Subject: [PATCH 2/3] feat: adjust .redocly.yaml to use new plugin --- .redocly.yaml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.redocly.yaml b/.redocly.yaml index 1b33b184..6642d076 100644 --- a/.redocly.yaml +++ b/.redocly.yaml @@ -7,6 +7,9 @@ apis: root: openapi/openapi.yaml plugins: - - './.github/redocly-plugins/remove-internal.js' + - "./.github/redocly-plugins/remove-internal.js" + - "./.github/redocly-plugins/x-feature.js" decorators: - remove-internal/remove-internal: error \ No newline at end of file + remove-internal/remove-internal: error + x-feature/fallback-replace: + available_features: [oneOf] From 8c044995c1a169e00d72b8d8ba4ec926315df6ff Mon Sep 17 00:00:00 2001 From: kubectl Date: Sat, 27 Jul 2024 22:32:21 +0200 Subject: [PATCH 3/3] test: add proposed changes from https://github.com/vrchatapi/specification/pull/352 to test --- .../authentication/CurrentUserLoginResponse.yaml | 11 +++++++++-- openapi/components/schemas/TwoFactorRequired.yaml | 13 +++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 openapi/components/schemas/TwoFactorRequired.yaml diff --git a/openapi/components/responses/authentication/CurrentUserLoginResponse.yaml b/openapi/components/responses/authentication/CurrentUserLoginResponse.yaml index ce21bac9..70808205 100644 --- a/openapi/components/responses/authentication/CurrentUserLoginResponse.yaml +++ b/openapi/components/responses/authentication/CurrentUserLoginResponse.yaml @@ -2,10 +2,17 @@ description: OK content: application/json: schema: - $ref: ../../schemas/CurrentUser.yaml + x-feature: + required-features: [oneOf] + desired: + oneOf: + - $ref: ../../schemas/CurrentUser.yaml + - $ref: ../../schemas/TwoFactorRequired.yaml + fallback: + $ref: ../../schemas/CurrentUser.yaml headers: Set-Cookie: schema: type: string - example: 'auth=authcookie_00000000-0000-0000-0000-000000000000; Expires=Tue, 01 Jan 2030 00:00:00 GMT; Path=/; HttpOnly' + example: "auth=authcookie_00000000-0000-0000-0000-000000000000; Expires=Tue, 01 Jan 2030 00:00:00 GMT; Path=/; HttpOnly" description: Successful authentication returns an `auth` cookie. diff --git a/openapi/components/schemas/TwoFactorRequired.yaml b/openapi/components/schemas/TwoFactorRequired.yaml new file mode 100644 index 00000000..04b1364c --- /dev/null +++ b/openapi/components/schemas/TwoFactorRequired.yaml @@ -0,0 +1,13 @@ +title: TwoFactorRequired +type: object +properties: + requiresTwoFactorAuth: + type: array + items: + type: string + enum: + - totp + - otp + - emailOtp +required: + - requiresTwoFactorAuth