Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Error-annotation #132

Merged
merged 1 commit into from
Apr 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions src/__tests__/operate/operate-integration.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { RESTError } from 'lib'
import { LosslessNumber } from 'lossless-json'

import { OperateApiClient } from '../../operate'
Expand Down Expand Up @@ -51,3 +52,47 @@ test('getJSONVariablesforProcess works', async () => {
const res = await c.getJSONVariablesforProcess(p.processInstanceKey)
expect(res.foo).toBe('bar')
})

test('test error type', async () => {
const c = new OperateApiClient()
const zeebe = new ZeebeGrpcClient()
await zeebe.deployResource({
processFilename: 'src/__tests__/testdata/Operate-StraightThrough.bpmn',
})
const p = await zeebe.createProcessInstanceWithResult({
bpmnProcessId: 'operate-straightthrough',
variables: {
foo: 'bar',
},
})
await new Promise((res) => setTimeout(() => res(null), 5000))
/**
* Here we request a process instance that doesn't exist.
* Understanding that this is the issue requires a bit of gymnastics by the consumer.
* This call may fail due to lack of permissions, a network error (including misconfiguration), or the process instance not existing.
* To rule out the other issues and focus on the process instance not existing, we need to catch the error and check the response body.
* (e.response?.body as string).includes('404') will return true if the response body contains the string '404'.
* This is a bit of a hack, but it's the best we can do without a more specific error type.
* Do we really want to expose consumers to this?
*/
const res = await c
.getProcessInstance(`${p.processInstanceKey}1`)
.catch((e: RESTError) => {
// console.log(e.code)
// `ERR_NON_2XX_3XX_RESPONSE`

// console.log(e.message)
// `Response code 404 (Not Found) (request to http://localhost:8081/v1/process-instances/22517998149629301)`
// Note: The request url has been enhanced into the message by a hook.

// console.log(e.response?.body)
// `{"status":404,"message":"No process instances found for key 22517998149629301 ","instance":"76807bf1-d877-4f8e-bd0d-6d953b1799e5","type":"Requested resource not found"}`

// console.log(typeof e.response?.body)
// `string`

expect((e.response?.body as string).includes('404')).toBe(true)
return false
})
expect(res).toBe(false)
})
45 changes: 25 additions & 20 deletions src/admin/lib/AdminApiClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,19 @@ import {
RequireConfiguration,
constructOAuthProvider,
createUserAgentString,
gotBeforeErrorHook,
gotErrorHandler,
} from '../../lib'
import { IOAuthProvider } from '../../oauth'

import * as Dto from './AdminDto'

const debug = d('camunda:adminconsole')

/**
* This class provides methods to interact with the Camunda Admin API.
* @throws {RESTError} An error that may occur during API operations.
*/
export class AdminApiClient {
private userAgentString: string
private oAuthProvider: IOAuthProvider
Expand Down Expand Up @@ -44,25 +50,9 @@ export class AdminApiClient {
https: {
certificateAuthority,
},
handlers: [
(options, next) => {
if (Object.isFrozen(options.context)) {
options.context = { ...options.context }
}
Error.captureStackTrace(options.context)
return next(options)
},
],
handlers: [gotErrorHandler],
hooks: {
beforeError: [
(error) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
;(error as any).source = (error as any).options.context.stack.split(
'\n'
)
return error
},
],
beforeError: [gotBeforeErrorHook],
},
})
debug('prefixUrl', `${baseUrl}/clusters`)
Expand All @@ -82,6 +72,7 @@ export class AdminApiClient {
/**
*
* @description Get an array of the current API clients for this cluster. See [the API Documentation](https://console.cloud.camunda.io/customer-api/openapi/docs/#/default/GetClients) for more details.
* @throws {RESTError}
* @param clusterUuid - The cluster UUID
*
*/
Expand All @@ -94,7 +85,7 @@ export class AdminApiClient {

/**
* @description Create a new API client for a cluster. See [the API Documentation](https://console.cloud.camunda.io/customer-api/openapi/docs/#/default/CreateClient) for more details.
* @returns
* @throws {RESTError}
*/
async createClient(req: {
clusterUuid: string
Expand All @@ -117,6 +108,7 @@ export class AdminApiClient {
* @description Get the details of an API client. See [the API Documentation](https://console.cloud.camunda.io/customer-api/openapi/docs/#/default/GetClient) for more details.
* @param clusterUuid
* @param clientId
* @throws {RESTError}
* @returns
*/
async getClient(
Expand All @@ -133,6 +125,7 @@ export class AdminApiClient {
* @description See [the API Documentation](https://console.cloud.camunda.io/customer-api/openapi/docs/#/default/DeleteClient) for more details.
* @param clusterUuid
* @param clientId
* @throws {RESTError}
*/
async deleteClient(clusterUuid: string, clientId: string): Promise<null> {
const headers = await this.getHeaders()
Expand All @@ -146,6 +139,7 @@ export class AdminApiClient {
/**
*
* @description Return an array of clusters. See [the API Documentation](https://console.cloud.camunda.io/customer-api/openapi/docs/#/default/GetClusters) for more details.
* @throws {RESTError}
*/
async getClusters(): Promise<Dto.Cluster[]> {
const headers = await this.getHeaders()
Expand All @@ -157,6 +151,7 @@ export class AdminApiClient {
/**
*
* @description Create a new cluster. See [the API Documentation](https://console.cloud.camunda.io/customer-api/openapi/docs/#/default/CreateCluster) for more details.
* @throws {RESTError}
*/
async createCluster(
createClusterRequest: Dto.CreateClusterBody
Expand All @@ -173,6 +168,7 @@ export class AdminApiClient {
/**
*
* @description Retrieve the metadata for a cluster. See [the API Documentation](https://console.cloud.camunda.io/customer-api/openapi/docs/#/default/GetCluster) for more details.
* @throws {RESTError}
*
*/
async getCluster(clusterUuid: string): Promise<Dto.Cluster> {
Expand All @@ -185,6 +181,7 @@ export class AdminApiClient {
/**
*
* @description Delete a cluster. See [the API Documentation](https://console.cloud.camunda.io/customer-api/openapi/docs/#/default/DeleteCluster) for more details.
* @throws {RESTError}
*
*/
async deleteCluster(clusterUuid: string): Promise<null> {
Expand All @@ -199,6 +196,7 @@ export class AdminApiClient {
/**
*
* @description Retrieve the available parameters for cluster creation. See [the API Documentation](https://console.cloud.camunda.io/customer-api/openapi/docs/#/default/GetParameters) for more details.
* @throws {RESTError}
*/
async getParameters(): Promise<Dto.Parameters> {
const headers = await this.getHeaders()
Expand All @@ -210,6 +208,7 @@ export class AdminApiClient {
/**
*
* @description Retrieve the connector secrets. See [the API Documentation](https://console.cloud.camunda.io/customer-api/openapi/docs/#/default/GetSecrets) for more details.
* @throws {RESTError}
*/
async getSecrets(clusterUuid: string): Promise<{ [key: string]: string }> {
const headers = await this.getHeaders()
Expand All @@ -221,6 +220,7 @@ export class AdminApiClient {
/**
*
* @description Create a new connector secret. See [the API Documentation](https://console.cloud.camunda.io/customer-api/openapi/docs/#/default/CreateSecret) for more details.
* @throws {RESTError}
*/
async createSecret({
clusterUuid,
Expand All @@ -242,6 +242,7 @@ export class AdminApiClient {
/**
*
* @description Delete a connector secret. See [the API Documentation](https://console.cloud.camunda.io/customer-api/openapi/docs/#/default/DeleteSecret) for more details.
* @throws {RESTError}
*/
async deleteSecret(clusterUuid: string, secretName: string): Promise<null> {
const headers = await this.getHeaders()
Expand All @@ -255,6 +256,7 @@ export class AdminApiClient {
/**
*
* @description Add one or more IPs to the whitelist for the cluster. See [the API Documentation](https://console.cloud.camunda.io/customer-api/openapi/docs/#/default/UpdateIpWhitelist) for more details.
* @throws {RESTError}
* @param ipwhitelist
* @returns
*/
Expand All @@ -266,7 +268,7 @@ export class AdminApiClient {
ip: string
},
]
) {
): Promise<null> {
const headers = await this.getHeaders()
return this.rest
.put(`${clusterUuid}/ipwhitelist`, {
Expand All @@ -281,6 +283,7 @@ export class AdminApiClient {
/**
*
* @description Retrieve a list of members and pending invites for your organisation. See the [API Documentation]() for more details.
* @throws {RESTError}
*/
async getUsers(): Promise<Dto.Member[]> {
const headers = await this.getHeaders()
Expand All @@ -294,6 +297,7 @@ export class AdminApiClient {
/**
*
* @description Add a member. See the [API Documentation]() for more details.
* @throws {RESTError}
*
*/
async createMember(
Expand All @@ -312,6 +316,7 @@ export class AdminApiClient {
/**
*
* @description Delete a member from your organization. See the [API Documentation]() for more details.
* @throws {RESTError}
*
*/
async deleteMember(email: string): Promise<null> {
Expand Down
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import * as Optimize from './optimize'
import * as Tasklist from './tasklist'
import * as Zeebe from './zeebe'

export { RESTError } from './lib'

export const Dto = { ChildDto, BigIntValue, Int64String, LosslessDto }

export { Admin, Auth, Camunda8, Modeler, Operate, Optimize, Tasklist, Zeebe }
13 changes: 13 additions & 0 deletions src/lib/GotErrors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import * as Got from 'got'

export type RESTError =
| Got.HTTPError
| Got.RequestError
| Got.ReadError
| Got.ParseError
| Got.HTTPError
| Got.TimeoutError
| Got.CancelError
| Got.CacheError
| Got.MaxRedirectsError
| Got.UnsupportedProtocolError
16 changes: 16 additions & 0 deletions src/lib/GotHooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export const gotErrorHandler = (options, next) => {
if (Object.isFrozen(options.context)) {
options.context = { ...options.context }
}
Error.captureStackTrace(options.context)

return next(options)
}

export const gotBeforeErrorHook = (error) => {
const { request } = error
// eslint-disable-next-line @typescript-eslint/no-explicit-any
;(error as any).source = (error as any).options.context.stack.split('\n')
error.message += ` (request to ${request?.options.url.href})`
return error
}
2 changes: 2 additions & 0 deletions src/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ export * from './CreateUserAgentString'
export * from './Delay'
export * from './EnvironmentSetup'
export { packageVersion } from './GetPackageVersion'
export * from './GotErrors'
export * from './GotHooks'
export * from './LosslessJsonParser'
export { RequireConfiguration } from './RequireConfiguration'
export * from './SuppressZeebeLogging'
Expand Down
Loading
Loading