diff --git a/.changeset/four-pigs-beam.md b/.changeset/four-pigs-beam.md new file mode 100644 index 0000000..7885674 --- /dev/null +++ b/.changeset/four-pigs-beam.md @@ -0,0 +1,5 @@ +--- +"@eventcatalog/generator-openapi": minor +--- + +feat(plugin): added new optional extention x-eventcatalog-message-action diff --git a/src/index.ts b/src/index.ts index 033a272..2888d64 100644 --- a/src/index.ts +++ b/src/index.ts @@ -164,13 +164,15 @@ export default async (_: any, options: Props) => { const processMessagesForOpenAPISpec = async (pathToSpec: string, document: OpenAPI.Document) => { const operations = await getOperationsByType(pathToSpec); const version = document.info.version; - let receives = []; + let receives = [], + sends = []; // Go through all messages for (const operation of operations) { const { requestBodiesAndResponses, ...message } = await buildMessage(pathToSpec, document, operation); let messageMarkdown = message.markdown; const messageType = operation.type; + const messageAction = operation.action; console.log(chalk.blue(`Processing message: ${message.name} (v${version})`)); @@ -197,11 +199,18 @@ const processMessagesForOpenAPISpec = async (pathToSpec: string, document: OpenA // Write the message to the catalog await writeMessage({ ...message, markdown: messageMarkdown }, { path: message.name }); - // messages will always be messages the service receives - receives.push({ - id: message.id, - version: message.version, - }); + // If the message send or recieved by the service? + if (messageAction === 'sends') { + sends.push({ + id: message.id, + version: message.version, + }); + } else { + receives.push({ + id: message.id, + version: message.version, + }); + } // Does the message have a request body or responses? if (requestBodiesAndResponses?.requestBody) { @@ -234,7 +243,7 @@ const processMessagesForOpenAPISpec = async (pathToSpec: string, document: OpenA console.log(chalk.yellow(` - Use operationIds to give better unique names for EventCatalog`)); } } - return { receives, sends: [] }; + return { receives, sends }; }; const getParsedSpecFile = (service: Service, document: OpenAPI.Document) => { diff --git a/src/test/openapi-files/petstore.yml b/src/test/openapi-files/petstore.yml index 1036af6..ec9f2e6 100644 --- a/src/test/openapi-files/petstore.yml +++ b/src/test/openapi-files/petstore.yml @@ -121,6 +121,28 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' + /pets/{petId}/vaccinated: + post: + summary: Notify that a pet has been vaccinated + operationId: petVaccinated + tags: + - pets + x-eventcatalog-message-action: sends # This is a sends operation + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Vaccination' + required: true + responses: + '200': + description: Notification that the pet has been vaccinated successfully + default: + description: unexpected error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' components: schemas: Pet: @@ -153,6 +175,18 @@ components: adopterName: type: string description: Name of the person who adopted the pet + Vaccination: + type: object + required: + - petId + - vaccine + properties: + petId: + type: integer + format: int64 + vaccine: + type: string + description: Name of the vaccine administered Error: type: object required: diff --git a/src/test/plugin.test.ts b/src/test/plugin.test.ts index a50502f..b0f4a4b 100644 --- a/src/test/plugin.test.ts +++ b/src/test/plugin.test.ts @@ -578,38 +578,51 @@ describe('OpenAPI EventCatalog Plugin', () => { ); }); - it('messages marked as "events" using the custom `x-ec-message-type` header in an OpenAPI are documented in EventCatalog as events ', async () => { - const { getEvent } = utils(catalogDir); + describe('OpenAPI eventcatalog extensions', () => { + it('messages marked as "events" using the custom `x-eventcatalog-message-type` header in an OpenAPI are documented in EventCatalog as events ', async () => { + const { getEvent } = utils(catalogDir); - await plugin(config, { services: [{ path: join(openAPIExamples, 'petstore.yml'), id: 'swagger-petstore' }] }); + await plugin(config, { services: [{ path: join(openAPIExamples, 'petstore.yml'), id: 'swagger-petstore' }] }); - const event = await getEvent('petAdopted'); + const event = await getEvent('petAdopted'); - expect(event).toEqual( - expect.objectContaining({ - id: 'petAdopted', - name: 'petAdopted', - version: '1.0.0', - summary: 'Notify that a pet has been adopted', - }) - ); - }); + expect(event).toEqual( + expect.objectContaining({ + id: 'petAdopted', + name: 'petAdopted', + version: '1.0.0', + summary: 'Notify that a pet has been adopted', + }) + ); + }); - it('messages marked as "commands" using the custom `x-ec-message-type` header in an OpenAPI are documented in EventCatalog as commands ', async () => { - const { getCommand } = utils(catalogDir); + it('messages marked as "commands" using the custom `x-eventcatalog-message-type` header in an OpenAPI are documented in EventCatalog as commands ', async () => { + const { getCommand } = utils(catalogDir); - await plugin(config, { services: [{ path: join(openAPIExamples, 'petstore.yml'), id: 'swagger-petstore' }] }); + await plugin(config, { services: [{ path: join(openAPIExamples, 'petstore.yml'), id: 'swagger-petstore' }] }); - const event = await getCommand('createPets'); + const event = await getCommand('createPets'); - expect(event).toEqual( - expect.objectContaining({ - id: 'createPets', - name: 'createPets', - version: '1.0.0', - summary: 'Create a pet', - }) - ); + expect(event).toEqual( + expect.objectContaining({ + id: 'createPets', + name: 'createPets', + version: '1.0.0', + summary: 'Create a pet', + }) + ); + }); + + it('messages marked as "sends" using the custom `x-eventcatalog-message-action` header in an OpenAPI are mapped against the service as messages the service sends ', async () => { + const { getService } = utils(catalogDir); + + await plugin(config, { services: [{ path: join(openAPIExamples, 'petstore.yml'), id: 'swagger-petstore' }] }); + + const service = await getService('swagger-petstore'); + + expect(service.sends).toHaveLength(1); + expect(service.sends).toEqual([{ id: 'petVaccinated', version: '1.0.0' }]); + }); }); it('when the message already exists in EventCatalog but the versions do not match, the existing message is versioned', async () => { diff --git a/src/types.ts b/src/types.ts index 3dfc011..3bf9280 100644 --- a/src/types.ts +++ b/src/types.ts @@ -18,6 +18,7 @@ export type Operation = { summary?: string; description?: string; type: string; + action: string; externalDocs?: OpenAPIV3_1.ExternalDocumentationObject; tags: string[]; }; diff --git a/src/utils/openapi.ts b/src/utils/openapi.ts index 1529ce1..7f2c71c 100644 --- a/src/utils/openapi.ts +++ b/src/utils/openapi.ts @@ -76,6 +76,7 @@ export async function getOperationsByType(openApiPath: string) { // Check if the x-eventcatalog-message-type field is set const messageType = openAPIOperation['x-eventcatalog-message-type'] || DEFAULT_MESSAGE_TYPE; + const messageAction = openAPIOperation['x-eventcatalog-message-action'] === 'sends' ? 'sends' : 'receives'; const operation = { path: path, @@ -83,6 +84,7 @@ export async function getOperationsByType(openApiPath: string) { operationId: openAPIOperation.operationId, externalDocs: openAPIOperation.externalDocs, type: messageType, + action: messageAction, description: openAPIOperation.description, summary: openAPIOperation.summary, tags: openAPIOperation.tags || [],