Skip to content

Commit

Permalink
ULMS-3167 Updated api-clients
Browse files Browse the repository at this point in the history
  • Loading branch information
alexkonst committed Aug 1, 2024
1 parent 640c635 commit bff76b6
Show file tree
Hide file tree
Showing 15 changed files with 429 additions and 185 deletions.
52 changes: 19 additions & 33 deletions src/basic-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,18 @@ const parseParameter = (key, value) => {
return `${key}=${value}`
}

const responseTransformer = (_) => _.data
async function handleResponse() {
throw new Error('Method `handleResponse` not implemented.')
}

class BasicClient {
constructor(baseUrl, httpClient, tokenProvider) {
this.baseUrl = baseUrl
this.customHeaders = {}
this.httpClient = httpClient
this.tokenProvider = tokenProvider
this.labels = {}
this.trackEvent = undefined
this.handleResponse = handleResponse
}

url(endpoint, parameters) {
Expand All @@ -44,7 +46,7 @@ class BasicClient {

// eslint-disable-next-line no-restricted-syntax
for (const [key, value] of Object.entries(parameters)) {
if (value !== undefined) {
if (value !== undefined && value !== null) {
urlParameters += urlParameters
? `&${parseParameter(key, value)}`
: parseParameter(key, value)
Expand All @@ -59,38 +61,22 @@ class BasicClient {
return `${this.baseUrl}${endpoint}`
}

static headers(token, labels = {}, headers = {}) {
static headers(token, headers = {}) {
return {
...headers,
authorization: `Bearer ${token}`,
'content-type': 'application/json',
...labels,
}
}

setHeaders(headers) {
this.customHeaders = headers
}

setLabels(labels) {
const { app_audience, app_label, app_version, scope } = labels // eslint-disable-line camelcase

this.labels = {
...(app_audience !== undefined && { 'ulms-app-audience': app_audience }), // eslint-disable-line camelcase
...(app_label !== undefined && { 'ulms-app-label': app_label }), // eslint-disable-line camelcase
...(app_version !== undefined && { 'ulms-app-version': app_version }), // eslint-disable-line camelcase
...(scope !== undefined && { 'ulms-scope': scope }),
}
}

setTrackEventFunction(trackEvent) {
this.trackEvent = trackEvent
}

clearLabels() {
this.labels = {}
}

async get(url, options = {}) {
const { retry: retryEnabled } = options

Expand All @@ -99,13 +85,13 @@ class BasicClient {
const token = await this.tokenProvider.getToken()
const headers = {
...options.headers,
...BasicClient.headers(token, this.labels, this.customHeaders),
...BasicClient.headers(token, this.customHeaders),
}
const requestOptions = { ...options, headers }

return this.httpClient
.get(url, requestOptions)
.then(responseTransformer)
.then(this.handleResponse)
}

return retry(task, onRetry)
Expand All @@ -114,31 +100,31 @@ class BasicClient {
const token = await this.tokenProvider.getToken()
const headers = {
...options.headers,
...BasicClient.headers(token, this.labels, this.customHeaders),
...BasicClient.headers(token, this.customHeaders),
}
const requestOptions = { ...options, headers }

return this.httpClient.get(url, requestOptions).then(responseTransformer)
return this.httpClient.get(url, requestOptions).then(this.handleResponse)
}

async put(url, data, options = {}) {
const token = await this.tokenProvider.getToken()
const headers = {
...options.headers,
...BasicClient.headers(token, this.labels, this.customHeaders),
...BasicClient.headers(token, this.customHeaders),
}
const requestOptions = { ...options, headers }

return this.httpClient
.put(url, data, requestOptions)
.then(responseTransformer)
.then(this.handleResponse)
}

async post(url, data, options = {}) {
const token = await this.tokenProvider.getToken()
const headers = {
...options.headers,
...BasicClient.headers(token, this.labels, this.customHeaders),
...BasicClient.headers(token, this.customHeaders),
}
const requestOptions = { ...options, headers }

Expand All @@ -151,7 +137,7 @@ class BasicClient {
: undefined
const result = this.httpClient
.post(url, data, requestOptions)
.then(responseTransformer)
.then(this.handleResponse)

result.catch((error) => {
const responseEnd = Date.now()
Expand All @@ -174,31 +160,31 @@ class BasicClient {

return this.httpClient
.post(url, data, requestOptions)
.then(responseTransformer)
.then(this.handleResponse)
}

async patch(url, data, options = {}) {
const token = await this.tokenProvider.getToken()
const headers = {
...options.headers,
...BasicClient.headers(token, this.labels, this.customHeaders),
...BasicClient.headers(token, this.customHeaders),
}
const requestOptions = { ...options, headers }

return this.httpClient
.patch(url, data, requestOptions)
.then(responseTransformer)
.then(this.handleResponse)
}

async delete(url, options = {}) {
const token = await this.tokenProvider.getToken()
const headers = {
...options.headers,
...BasicClient.headers(token, this.labels, this.customHeaders),
...BasicClient.headers(token, this.customHeaders),
}
const requestOptions = { ...options, headers }

return this.httpClient.delete(url, requestOptions).then(responseTransformer)
return this.httpClient.delete(url, requestOptions).then(this.handleResponse)
}
}

Expand Down
171 changes: 154 additions & 17 deletions src/error.js
Original file line number Diff line number Diff line change
@@ -1,30 +1,167 @@
/* eslint-disable max-classes-per-file, unicorn/prevent-abbreviations */
export class ApiError extends Error {

// old api error kinds
// todo: remove after migration to new error kinds (on backend)
const apiErrorKindOldMap = {
ACCESS_DENIED: 'access_denied',
CAPACITY_EXCEEDED: 'capacity_exceeded',
TOXIC_COMMENT: 'toxic_comment',
TOXIC_COMMENT_CLASSIFIER_REQUEST_FAILED:
'toxic_comment_classifier_request_failed',
}
const apiErrorKindMap = {
INTERNAL_FAILURE: 'internal_failure',
SYSTEM_ACCESS_DENIED: 'system_access_denied',
ULMS_INVALID_COMMENT: 'ulms_invalid_comment',
ULMS_SERVER_CAPACITY_EXCEEDED: 'ulms_server_capacity_exceeded',
ULMS_TOXIC_COMMENT: 'ulms_toxic_comment',
}
// todo: remove after migration to new error kinds (on backend)
const transformApiErrorKindMap = {
[apiErrorKindOldMap.ACCESS_DENIED]: apiErrorKindMap.SYSTEM_ACCESS_DENIED,
[apiErrorKindOldMap.CAPACITY_EXCEEDED]:
apiErrorKindMap.ULMS_SERVER_CAPACITY_EXCEEDED,
[apiErrorKindOldMap.TOXIC_COMMENT]: apiErrorKindMap.ULMS_TOXIC_COMMENT,
[apiErrorKindOldMap.TOXIC_COMMENT_CLASSIFIER_REQUEST_FAILED]:
apiErrorKindMap.ULMS_INVALID_COMMENT,
}

const decodeErrorKindMap = {
JSON_PARSE_ERROR: 'json_parse_error',
}

export class UlmsError extends Error {
constructor(message, options) {
super(message, options)

const { is_transient: isTransient } = options || {}
const { isTransient } = options

this.name = 'ApiError'
this.isTransient = isTransient
this.name = 'UlmsError'
}

static get apiErrorKinds() {
return apiErrorKindMap
}

static get decodeErrorKinds() {
return decodeErrorKindMap
}

/**
* Factory method for creating error instance
* @param payload {{ kind?: string, is_transient?: boolean }}
* @returns {ApiError}
* @param payload {{ kind?: string, isTransient?: boolean }}
* @returns {UlmsError}
*/
static fromPayload(payload) {
const { kind, is_transient: isTransient } = payload
const { kind, isTransient } = payload
// todo: remove after migration to new error kinds (on backend), use kind instead
const transformedKind = transformApiErrorKindMap[kind] || kind

return new ApiError(kind, { isTransient })
return new UlmsError(transformedKind, { isTransient })
}

get kind() {
return this.message
}
}

// // Error kind matching example
// try {
// const result = await ulmsClient.createEvent(...)
// } catch (error) {
// switch (error.kind) {
// case UlmsError.apiErrorKinds.ACCESS_DENIED: {
// // show ACCESS_DENIED screen
// break
// }
// case UlmsError.apiErrorKinds.TOXIC_COMMENT: {
// // show TOXIC_COMMENT screen
// break
// }
// case UlmsError.decodeErrorKinds.JSON_PARSE_ERROR: {
// // show JSON_PARSE_ERROR screen
// break
// }
// default: {
// // default
// sentry.captureException(error)
// // show default screen
// }
// }
// }

export class PresenceError extends Error {
constructor(message, options) {
super(message, options)

const { isTransient } = options

this.isTransient = isTransient
this.name = 'PresenceError'
}

static get apiErrorKinds() {
return apiErrorKindMap
}

static get decodeErrorKinds() {
return decodeErrorKindMap
}

/**
* Factory method for creating error instance
* @param payload {{ kind?: string, isTransient?: boolean }}
* @returns {PresenceError}
*/
static fromPayload(payload) {
const { kind, isTransient } = payload
// todo: remove after migration to new error kinds (on backend), use kind instead
const transformedKind = transformApiErrorKindMap[kind] || kind

return new PresenceError(transformedKind, { isTransient })
}

get kind() {
return this.message
}
}

export class FVSError extends Error {
constructor(...arguments_) {
super(...arguments_)

this.isTransient = false
this.name = 'FVSError'
}

static get decodeErrorKinds() {
return decodeErrorKindMap
}
}

export class TenantError extends Error {
constructor(...arguments_) {
super(...arguments_)

this.isTransient = false
this.name = 'TenantError'
}

static get decodeErrorKinds() {
return decodeErrorKindMap
}
}

export class NetworkError extends Error {
constructor(...arguments_) {
super(...arguments_)

this.isTransient = true
this.name = 'NetworkError'
}
}

export class MQTTClientError extends Error {
constructor(...args) {
super(...args)
Expand Down Expand Up @@ -53,11 +190,11 @@ export class TimeoutError extends Error {
}
}

export class PresenceError extends Error {
export class PresenceWsError extends Error {
constructor(...args) {
super(...args)

this.name = 'PresenceError'
this.name = 'PresenceWsError'
}

static get recoverableTypes() {
Expand Down Expand Up @@ -90,26 +227,26 @@ export class PresenceError extends Error {

static fromType(type) {
const errorType =
PresenceError.recoverableTypes[type] ||
PresenceError.unrecoverableTypes[type] ||
PresenceError.unrecoverableTypes.UNKNOWN_ERROR
PresenceWsError.recoverableTypes[type] ||
PresenceWsError.unrecoverableTypes[type] ||
PresenceWsError.unrecoverableTypes.UNKNOWN_ERROR

return new PresenceError(errorType)
return new PresenceWsError(errorType)
}

static isReplacedError(error) {
if (!(error instanceof PresenceError)) return false
if (!(error instanceof PresenceWsError)) return false

const { message } = error

return (
message === PresenceError.unrecoverableTypes.REPLACED ||
message === PresenceError.unrecoverableTypes.SESSION_REPLACED
message === PresenceWsError.unrecoverableTypes.REPLACED ||
message === PresenceWsError.unrecoverableTypes.SESSION_REPLACED
)
}

isRecoverable() {
return !!PresenceError.recoverableTypes[this.message]
return !!PresenceWsError.recoverableTypes[this.message]
}
}

Expand Down
Loading

0 comments on commit bff76b6

Please sign in to comment.