From 7ba6e5a31b810db27967ba0e6f1e4f5bbcdcec57 Mon Sep 17 00:00:00 2001 From: Max Kurapov Date: Thu, 14 Mar 2024 12:51:46 +0100 Subject: [PATCH 1/4] feat(open-payments): export OpenAPI specs --- packages/open-payments/package.json | 8 +- .../open-payments/src/client/grant.test.ts | 8 +- .../src/client/incoming-payment.test.ts | 8 +- packages/open-payments/src/client/index.ts | 27 +++--- .../src/client/outgoing-payment.test.ts | 8 +- .../open-payments/src/client/quote.test.ts | 8 +- .../open-payments/src/client/token.test.ts | 8 +- .../src/client/wallet-address.test.ts | 8 +- packages/open-payments/src/index.ts | 6 ++ .../src/openapi/auth-server.yaml | 1 - .../open-payments/src/openapi/index.test.ts | 90 +++++++++++++++++++ packages/open-payments/src/openapi/index.ts | 16 ++++ .../src/openapi/resource-server.yaml | 1 - .../open-payments/src/openapi/schemas.yaml | 1 - .../src/openapi/specs/auth-server.yaml | 1 + .../src/openapi/specs/resource-server.yaml | 1 + .../src/openapi/specs/schemas.yaml | 1 + .../openapi/specs/wallet-address-server.yaml | 1 + .../src/openapi/wallet-address-server.yaml | 1 - 19 files changed, 149 insertions(+), 54 deletions(-) delete mode 120000 packages/open-payments/src/openapi/auth-server.yaml create mode 100644 packages/open-payments/src/openapi/index.test.ts create mode 100644 packages/open-payments/src/openapi/index.ts delete mode 120000 packages/open-payments/src/openapi/resource-server.yaml delete mode 120000 packages/open-payments/src/openapi/schemas.yaml create mode 120000 packages/open-payments/src/openapi/specs/auth-server.yaml create mode 120000 packages/open-payments/src/openapi/specs/resource-server.yaml create mode 120000 packages/open-payments/src/openapi/specs/schemas.yaml create mode 120000 packages/open-payments/src/openapi/specs/wallet-address-server.yaml delete mode 120000 packages/open-payments/src/openapi/wallet-address-server.yaml diff --git a/packages/open-payments/package.json b/packages/open-payments/package.json index b5133a34..8dc47e27 100644 --- a/packages/open-payments/package.json +++ b/packages/open-payments/package.json @@ -21,10 +21,10 @@ "build:deps": "pnpm --filter openapi build && pnpm --filter http-signature-utils build", "build": "pnpm build:deps && pnpm clean && tsc --build tsconfig.json && pnpm copy-files", "clean": "rm -fr dist/", - "copy-files": "cp ./src/openapi/*.yaml ./dist/openapi/", - "generate:auth-server-types": "openapi-typescript src/openapi/auth-server.yaml --output src/openapi/generated/auth-server-types.ts", - "generate:resource-server-types": "openapi-typescript src/openapi/resource-server.yaml --output src/openapi/generated/resource-server-types.ts", - "generate:wallet-address-server-types": "openapi-typescript src/openapi/wallet-address-server.yaml --output src/openapi/generated/wallet-address-server-types.ts", + "copy-files": "mkdir ./dist/openapi/specs/ && cp ./src/openapi/specs/*.yaml ./dist/openapi/specs/", + "generate:auth-server-types": "openapi-typescript src/openapi/specs/auth-server.yaml --output src/openapi/generated/auth-server-types.ts", + "generate:resource-server-types": "openapi-typescript src/openapi/resospecs/resource-server.yaml --output src/openapi/generated/resource-server-types.ts", + "generate:wallet-address-server-types": "openapi-typescript src/openapi/specs/wallet-address-server.yaml --output src/openapi/generated/wallet-address-server-types.ts", "generate:types": "pnpm generate:auth-server-types && pnpm generate:resource-server-types && pnpm generate:wallet-address-server-types", "prepack": "pnpm build", "test": "jest --passWithNoTests" diff --git a/packages/open-payments/src/client/grant.test.ts b/packages/open-payments/src/client/grant.test.ts index 952cd648..5ab5924a 100644 --- a/packages/open-payments/src/client/grant.test.ts +++ b/packages/open-payments/src/client/grant.test.ts @@ -1,10 +1,10 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { createGrantRoutes } from './grant' -import { OpenAPI, HttpMethod, createOpenAPI } from '@interledger/openapi' -import path from 'path' +import { OpenAPI, HttpMethod } from '@interledger/openapi' import { createTestDeps, mockGrantRequest } from '../test/helpers' import * as requestors from './requests' import { v4 as uuid } from 'uuid' +import { getAuthServerOpenAPI } from '../openapi' jest.mock('./requests', () => ({ ...jest.requireActual('./requests.ts'), @@ -17,9 +17,7 @@ describe('grant', (): void => { let openApi: OpenAPI beforeAll(async () => { - openApi = await createOpenAPI( - path.resolve(__dirname, '../openapi/auth-server.yaml') - ) + openApi = await getAuthServerOpenAPI() }) const deps = createTestDeps() diff --git a/packages/open-payments/src/client/incoming-payment.test.ts b/packages/open-payments/src/client/incoming-payment.test.ts index 39c70e8f..c758f942 100644 --- a/packages/open-payments/src/client/incoming-payment.test.ts +++ b/packages/open-payments/src/client/incoming-payment.test.ts @@ -10,7 +10,7 @@ import { createUnauthenticatedIncomingPaymentRoutes, getPublicIncomingPayment } from './incoming-payment' -import { OpenAPI, HttpMethod, createOpenAPI } from '@interledger/openapi' +import { OpenAPI, HttpMethod } from '@interledger/openapi' import { createTestDeps, mockIncomingPayment, @@ -20,12 +20,12 @@ import { mockPublicIncomingPayment } from '../test/helpers' import nock from 'nock' -import path from 'path' import { v4 as uuid } from 'uuid' import * as requestors from './requests' import { getRSPath } from '../types' import { OpenPaymentsClientError } from './error' import assert from 'assert' +import { getResourceServerOpenAPI } from '../openapi' jest.mock('./requests', () => { return { @@ -39,9 +39,7 @@ describe('incoming-payment', (): void => { let openApi: OpenAPI beforeAll(async () => { - openApi = await createOpenAPI( - path.resolve(__dirname, '../openapi/resource-server.yaml') - ) + openApi = await getResourceServerOpenAPI() }) const deps = createTestDeps() diff --git a/packages/open-payments/src/client/index.ts b/packages/open-payments/src/client/index.ts index be96908e..ff125059 100644 --- a/packages/open-payments/src/client/index.ts +++ b/packages/open-payments/src/client/index.ts @@ -1,6 +1,6 @@ import { loadKey } from '@interledger/http-signature-utils' import fs from 'fs' -import { createOpenAPI, OpenAPI } from '@interledger/openapi' +import { OpenAPI } from '@interledger/openapi' import path from 'path' import createLogger, { LevelWithSilent, Logger } from 'pino' import config from '../config' @@ -29,6 +29,11 @@ import { createTokenRoutes, TokenRoutes } from './token' import { createQuoteRoutes, QuoteRoutes } from './quote' import { KeyLike, KeyObject, createPrivateKey } from 'crypto' import { OpenPaymentsClientError } from './error' +import { + getResourceServerOpenAPI, + getWalletAddressServerOpenAPI, + getAuthServerOpenAPI +} from '../openapi' export * from './error' export interface BaseDeps { @@ -140,12 +145,8 @@ const createUnauthenticatedDeps = async ({ args?.requestTimeoutMs ?? config.DEFAULT_REQUEST_TIMEOUT_MS }) - const walletAddressServerOpenApi = await createOpenAPI( - path.resolve(__dirname, '../openapi/wallet-address-server.yaml') - ) - const resourceServerOpenApi = await createOpenAPI( - path.resolve(__dirname, '../openapi/resource-server.yaml') - ) + const walletAddressServerOpenApi = await getWalletAddressServerOpenAPI() + const resourceServerOpenApi = await getResourceServerOpenAPI() return { axiosInstance, @@ -199,15 +200,9 @@ const createAuthenticatedClientDeps = async ({ }) } - const walletAddressServerOpenApi = await createOpenAPI( - path.resolve(__dirname, '../openapi/wallet-address-server.yaml') - ) - const resourceServerOpenApi = await createOpenAPI( - path.resolve(__dirname, '../openapi/resource-server.yaml') - ) - const authServerOpenApi = await createOpenAPI( - path.resolve(__dirname, '../openapi/auth-server.yaml') - ) + const walletAddressServerOpenApi = await getWalletAddressServerOpenAPI() + const resourceServerOpenApi = await getResourceServerOpenAPI() + const authServerOpenApi = await getAuthServerOpenAPI() return { axiosInstance, diff --git a/packages/open-payments/src/client/outgoing-payment.test.ts b/packages/open-payments/src/client/outgoing-payment.test.ts index 9ca0805e..fbe7d113 100644 --- a/packages/open-payments/src/client/outgoing-payment.test.ts +++ b/packages/open-payments/src/client/outgoing-payment.test.ts @@ -5,7 +5,7 @@ import { listOutgoingPayments, validateOutgoingPayment } from './outgoing-payment' -import { OpenAPI, HttpMethod, createOpenAPI } from '@interledger/openapi' +import { OpenAPI, HttpMethod } from '@interledger/openapi' import { mockOutgoingPayment, mockOpenApiResponseValidators, @@ -13,11 +13,11 @@ import { createTestDeps } from '../test/helpers' import nock from 'nock' -import path from 'path' import { v4 as uuid } from 'uuid' import * as requestors from './requests' import { OpenPaymentsClientError } from './error' import assert from 'assert' +import { getResourceServerOpenAPI } from '../openapi' jest.mock('./requests', () => { return { @@ -31,9 +31,7 @@ describe('outgoing-payment', (): void => { let openApi: OpenAPI beforeAll(async () => { - openApi = await createOpenAPI( - path.resolve(__dirname, '../openapi/resource-server.yaml') - ) + openApi = await getResourceServerOpenAPI() }) const deps = createTestDeps() diff --git a/packages/open-payments/src/client/quote.test.ts b/packages/open-payments/src/client/quote.test.ts index 80e08f6e..eba394a8 100644 --- a/packages/open-payments/src/client/quote.test.ts +++ b/packages/open-payments/src/client/quote.test.ts @@ -1,6 +1,5 @@ import { createQuoteRoutes, getQuote, createQuote } from './quote' -import { OpenAPI, HttpMethod, createOpenAPI } from '@interledger/openapi' -import path from 'path' +import { OpenAPI, HttpMethod } from '@interledger/openapi' import { createTestDeps, mockOpenApiResponseValidators, @@ -9,6 +8,7 @@ import { import nock from 'nock' import * as requestors from './requests' import { getRSPath } from '../types' +import { getResourceServerOpenAPI } from '../openapi' jest.mock('./requests', () => { return { @@ -21,9 +21,7 @@ describe('quote', (): void => { let openApi: OpenAPI beforeAll(async () => { - openApi = await createOpenAPI( - path.resolve(__dirname, '../openapi/resource-server.yaml') - ) + openApi = await getResourceServerOpenAPI() }) const deps = createTestDeps() diff --git a/packages/open-payments/src/client/token.test.ts b/packages/open-payments/src/client/token.test.ts index a1aac98b..c56deb4a 100644 --- a/packages/open-payments/src/client/token.test.ts +++ b/packages/open-payments/src/client/token.test.ts @@ -1,7 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { createTokenRoutes, revokeToken, rotateToken } from './token' -import { OpenAPI, HttpMethod, createOpenAPI } from '@interledger/openapi' -import path from 'path' +import { OpenAPI, HttpMethod } from '@interledger/openapi' import nock from 'nock' import { createTestDeps, @@ -9,6 +8,7 @@ import { mockOpenApiResponseValidators } from '../test/helpers' import * as requestors from './requests' +import { getAuthServerOpenAPI } from '../openapi' jest.mock('./requests', () => { return { @@ -22,9 +22,7 @@ describe('token', (): void => { let openApi: OpenAPI beforeAll(async () => { - openApi = await createOpenAPI( - path.resolve(__dirname, '../openapi/auth-server.yaml') - ) + openApi = await getAuthServerOpenAPI() }) const deps = createTestDeps() diff --git a/packages/open-payments/src/client/wallet-address.test.ts b/packages/open-payments/src/client/wallet-address.test.ts index 66fbcb91..76c6d622 100644 --- a/packages/open-payments/src/client/wallet-address.test.ts +++ b/packages/open-payments/src/client/wallet-address.test.ts @@ -1,6 +1,5 @@ import { createWalletAddressRoutes } from './wallet-address' -import { OpenAPI, HttpMethod, createOpenAPI } from '@interledger/openapi' -import path from 'path' +import { OpenAPI, HttpMethod } from '@interledger/openapi' import { createTestDeps, mockDIDDocument, @@ -8,6 +7,7 @@ import { mockWalletAddress } from '../test/helpers' import * as requestors from './requests' +import { getWalletAddressServerOpenAPI } from '../openapi' jest.mock('./requests', () => { return { @@ -21,9 +21,7 @@ describe('wallet-address', (): void => { let openApi: OpenAPI beforeAll(async () => { - openApi = await createOpenAPI( - path.resolve(__dirname, '../openapi/resource-server.yaml') - ) + openApi = await getWalletAddressServerOpenAPI() }) const deps = createTestDeps() diff --git a/packages/open-payments/src/index.ts b/packages/open-payments/src/index.ts index c49d958c..194c8628 100644 --- a/packages/open-payments/src/index.ts +++ b/packages/open-payments/src/index.ts @@ -30,6 +30,12 @@ export { OpenPaymentsClientError } from './client' +export { + getAuthServerOpenAPI, + getResourceServerOpenAPI, + getWalletAddressServerOpenAPI +} from './openapi' + export { mockWalletAddress, mockIncomingPayment, diff --git a/packages/open-payments/src/openapi/auth-server.yaml b/packages/open-payments/src/openapi/auth-server.yaml deleted file mode 120000 index 40147645..00000000 --- a/packages/open-payments/src/openapi/auth-server.yaml +++ /dev/null @@ -1 +0,0 @@ -../../../../openapi/auth-server.yaml \ No newline at end of file diff --git a/packages/open-payments/src/openapi/index.test.ts b/packages/open-payments/src/openapi/index.test.ts new file mode 100644 index 00000000..f234b899 --- /dev/null +++ b/packages/open-payments/src/openapi/index.test.ts @@ -0,0 +1,90 @@ +import { + getResourceServerOpenAPI, + getAuthServerOpenAPI, + getWalletAddressServerOpenAPI +} from '.' + +describe('OpenAPI', (): void => { + describe('getResourceServerOpenAPI', () => { + test('properly generates API paths', async () => { + const openApi = await getResourceServerOpenAPI() + + expect(openApi).toBeDefined() + expect(Object.keys(openApi.paths)).toEqual( + expect.arrayContaining([ + '/incoming-payments', + '/outgoing-payments', + '/quotes', + '/incoming-payments/{id}', + '/incoming-payments/{id}/complete', + '/outgoing-payments/{id}', + '/quotes/{id}' + ]) + ) + }) + + test('properly references $ref to external ./schemas.yaml', async () => { + const openApi = await getResourceServerOpenAPI() + + expect( + Object.keys( + openApi.paths?.['/incoming-payments']?.['post']?.['requestBody']?.[ + 'content' + ]['application/json']['schema']['properties']['incomingAmount'][ + 'properties' + ] + ).sort() + ).toEqual(['assetCode', 'assetScale', 'value'].sort()) + }) + }) + + describe('getAuthServerOpenAPI', () => { + test('properly generates API paths', async () => { + const openApi = await getAuthServerOpenAPI() + + expect(openApi).toBeDefined() + expect(Object.keys(openApi.paths)).toEqual( + expect.arrayContaining(['/', '/continue/{id}', '/token/{id}']) + ) + }) + + test('properly references $ref to external ./schemas.yaml', async () => { + const openApi = await getAuthServerOpenAPI() + + expect( + Object.keys( + openApi.paths?.['/']?.['post']?.['requestBody']?.['content'][ + 'application/json' + ]['schema']['properties']['access_token']['properties']['access'][ + 'items' + ]['oneOf'][1]['properties']['limits']['properties']['debitAmount'][ + 'properties' + ] + ).sort() + ).toEqual(['assetCode', 'assetScale', 'value'].sort()) + }) + }) + + describe('getWalletAddressServerOpenAPI', () => { + test('properly generates API paths', async () => { + const openApi = await getWalletAddressServerOpenAPI() + + expect(openApi).toBeDefined() + expect(Object.keys(openApi.paths)).toEqual( + expect.arrayContaining(['/', '/jwks.json', '/did.json']) + ) + }) + + test('properly references $ref to external ./schemas.yaml', async () => { + const openApi = await getWalletAddressServerOpenAPI() + + const getWalletAddressResponse = + openApi.paths?.['/']?.['get']?.['responses']['200']['content'][ + 'application/json' + ]['schema']['properties'] + + expect(getWalletAddressResponse['assetCode'].type).toBe('string') + expect(getWalletAddressResponse['assetScale'].type).toBe('integer') + }) + }) +}) diff --git a/packages/open-payments/src/openapi/index.ts b/packages/open-payments/src/openapi/index.ts new file mode 100644 index 00000000..af3d7b4a --- /dev/null +++ b/packages/open-payments/src/openapi/index.ts @@ -0,0 +1,16 @@ +import { createOpenAPI } from '@interledger/openapi' +import path from 'path' + +export async function getResourceServerOpenAPI() { + return createOpenAPI(path.resolve(__dirname, './specs/resource-server.yaml')) +} + +export async function getWalletAddressServerOpenAPI() { + return createOpenAPI( + path.resolve(__dirname, './specs/wallet-address-server.yaml') + ) +} + +export async function getAuthServerOpenAPI() { + return createOpenAPI(path.resolve(__dirname, './specs/auth-server.yaml')) +} diff --git a/packages/open-payments/src/openapi/resource-server.yaml b/packages/open-payments/src/openapi/resource-server.yaml deleted file mode 120000 index 999fb84f..00000000 --- a/packages/open-payments/src/openapi/resource-server.yaml +++ /dev/null @@ -1 +0,0 @@ -../../../../openapi/resource-server.yaml \ No newline at end of file diff --git a/packages/open-payments/src/openapi/schemas.yaml b/packages/open-payments/src/openapi/schemas.yaml deleted file mode 120000 index 0677d364..00000000 --- a/packages/open-payments/src/openapi/schemas.yaml +++ /dev/null @@ -1 +0,0 @@ -../../../../openapi/schemas.yaml \ No newline at end of file diff --git a/packages/open-payments/src/openapi/specs/auth-server.yaml b/packages/open-payments/src/openapi/specs/auth-server.yaml new file mode 120000 index 00000000..21dea685 --- /dev/null +++ b/packages/open-payments/src/openapi/specs/auth-server.yaml @@ -0,0 +1 @@ +../../../../../openapi/auth-server.yaml \ No newline at end of file diff --git a/packages/open-payments/src/openapi/specs/resource-server.yaml b/packages/open-payments/src/openapi/specs/resource-server.yaml new file mode 120000 index 00000000..d086cf27 --- /dev/null +++ b/packages/open-payments/src/openapi/specs/resource-server.yaml @@ -0,0 +1 @@ +../../../../../openapi/resource-server.yaml \ No newline at end of file diff --git a/packages/open-payments/src/openapi/specs/schemas.yaml b/packages/open-payments/src/openapi/specs/schemas.yaml new file mode 120000 index 00000000..aa9cf9fc --- /dev/null +++ b/packages/open-payments/src/openapi/specs/schemas.yaml @@ -0,0 +1 @@ +../../../../../openapi/schemas.yaml \ No newline at end of file diff --git a/packages/open-payments/src/openapi/specs/wallet-address-server.yaml b/packages/open-payments/src/openapi/specs/wallet-address-server.yaml new file mode 120000 index 00000000..eba3c861 --- /dev/null +++ b/packages/open-payments/src/openapi/specs/wallet-address-server.yaml @@ -0,0 +1 @@ +../../../../../openapi/wallet-address-server.yaml \ No newline at end of file diff --git a/packages/open-payments/src/openapi/wallet-address-server.yaml b/packages/open-payments/src/openapi/wallet-address-server.yaml deleted file mode 120000 index 145b96aa..00000000 --- a/packages/open-payments/src/openapi/wallet-address-server.yaml +++ /dev/null @@ -1 +0,0 @@ -../../../../openapi/wallet-address-server.yaml \ No newline at end of file From a2bdf4c513985cdb5a57966c91295fe0761b16a1 Mon Sep 17 00:00:00 2001 From: Max Kurapov Date: Thu, 14 Mar 2024 12:51:53 +0100 Subject: [PATCH 2/4] chore: add changelog --- .changeset/mean-penguins-cheat.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/mean-penguins-cheat.md diff --git a/.changeset/mean-penguins-cheat.md b/.changeset/mean-penguins-cheat.md new file mode 100644 index 00000000..bc7fb6cc --- /dev/null +++ b/.changeset/mean-penguins-cheat.md @@ -0,0 +1,5 @@ +--- +'@interledger/open-payments': minor +--- + +Add and export functions that allow fetching the OpenAPI objects for the Open Payments specs From ff7be59e08e8ca48bea5db31559b05263f3957cd Mon Sep 17 00:00:00 2001 From: Max Kurapov Date: Thu, 14 Mar 2024 13:10:01 +0100 Subject: [PATCH 3/4] chore: fix generate types command --- packages/open-payments/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/open-payments/package.json b/packages/open-payments/package.json index 8dc47e27..fea9b293 100644 --- a/packages/open-payments/package.json +++ b/packages/open-payments/package.json @@ -23,7 +23,7 @@ "clean": "rm -fr dist/", "copy-files": "mkdir ./dist/openapi/specs/ && cp ./src/openapi/specs/*.yaml ./dist/openapi/specs/", "generate:auth-server-types": "openapi-typescript src/openapi/specs/auth-server.yaml --output src/openapi/generated/auth-server-types.ts", - "generate:resource-server-types": "openapi-typescript src/openapi/resospecs/resource-server.yaml --output src/openapi/generated/resource-server-types.ts", + "generate:resource-server-types": "openapi-typescript src/openapi/specs/resource-server.yaml --output src/openapi/generated/resource-server-types.ts", "generate:wallet-address-server-types": "openapi-typescript src/openapi/specs/wallet-address-server.yaml --output src/openapi/generated/wallet-address-server-types.ts", "generate:types": "pnpm generate:auth-server-types && pnpm generate:resource-server-types && pnpm generate:wallet-address-server-types", "prepack": "pnpm build", From 083719fcc4a1ea6de5dcb02c8b6d7868f6c054a2 Mon Sep 17 00:00:00 2001 From: Max Kurapov Date: Thu, 14 Mar 2024 13:22:16 +0100 Subject: [PATCH 4/4] chore: add jsdoc --- packages/open-payments/src/openapi/index.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/packages/open-payments/src/openapi/index.ts b/packages/open-payments/src/openapi/index.ts index af3d7b4a..6c918c5f 100644 --- a/packages/open-payments/src/openapi/index.ts +++ b/packages/open-payments/src/openapi/index.ts @@ -1,16 +1,31 @@ import { createOpenAPI } from '@interledger/openapi' import path from 'path' +/** + * Returns the OpenAPI object for the Open Payments Resource Server OpenAPI spec. + * This object allows validating requests and responses against the spec. + * See more: https://github.com/interledger/open-payments/blob/main/packages/openapi/README.md + */ export async function getResourceServerOpenAPI() { return createOpenAPI(path.resolve(__dirname, './specs/resource-server.yaml')) } +/** + * Returns the OpenAPI object for the Open Payments Wallet Address Server OpenAPI spec. + * This object allows validating requests and responses against the spec. + * See more: https://github.com/interledger/open-payments/blob/main/packages/openapi/README.md + */ export async function getWalletAddressServerOpenAPI() { return createOpenAPI( path.resolve(__dirname, './specs/wallet-address-server.yaml') ) } +/** + * Returns the OpenAPI object for the Open Payments Auth Server OpenAPI spec. + * This object allows validating requests and responses against the spec. + * See more: https://github.com/interledger/open-payments/blob/main/packages/openapi/README.md + */ export async function getAuthServerOpenAPI() { return createOpenAPI(path.resolve(__dirname, './specs/auth-server.yaml')) }