Skip to content

Commit 5e2d9c2

Browse files
committed
refactor: improve error handling and messaging across various modules
1 parent b9b5c73 commit 5e2d9c2

File tree

9 files changed

+71
-25
lines changed

9 files changed

+71
-25
lines changed

lib/contentstack.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ import { getContentstackEndpoint } from '@contentstack/utils'
122122
* const client = contentstack.client({ logHandler: (level, data) => {
123123
if (level === 'error' && data) {
124124
const title = [data.name, data.message].filter((a) => a).join(' - ')
125-
console.error(`[error] ${title}`)
125+
console.error(`An error occurred due to ${title}. Review the details and try again.`)
126126
return
127127
}
128128
console.log(`[${level}] ${data}`)

lib/contentstackClient.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,15 @@ export default function contentstackClient ({ http }) {
2626
* const client = contentstack.client()
2727
*
2828
* client.login({ email: <emailid>, password: <password> })
29-
* .then(() => console.log('Logged in successfully'))
29+
* .then(() => console.log('Login successful.'))
3030
*
3131
* @example
3232
* client.login({ email: <emailid>, password: <password>, tfa_token: <tfa_token> })
33-
* .then(() => console.log('Logged in successfully'))
33+
* .then(() => console.log('Login successful.'))
3434
*
3535
* @example
3636
* client.login({ email: <emailid>, password: <password>, mfaSecret: <mfa_secret> })
37-
* .then(() => console.log('Logged in successfully'))
37+
* .then(() => console.log('Login successful.'))
3838
*/
3939
function login (requestBody = {}, params = {}) {
4040
http.defaults.versioningStrategy = 'path'
@@ -210,7 +210,7 @@ export default function contentstackClient ({ http }) {
210210
* const client = contentstack.client()
211211
*
212212
* client.oauth({ appId: <appId>, clientId: <clientId>, redirectUri: <redirectUri>, clientSecret: <clientSecret>, responseType: <responseType>, scope: <scope> })
213-
* .then(() => console.log('Logged in successfully'))
213+
* .then(() => console.log('Login successful.'))
214214
*
215215
*/
216216
function oauth (params = {}) {

lib/core/Util.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { platform, release } from 'os'
2+
import { ERROR_MESSAGES } from './errorMessages'
23

34
const HOST_REGEX = /^(?!(?:(?:https?|ftp):\/\/|internal|localhost|(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)))(?:[\w-]+\.contentstack\.(?:io|com)(?::[^\/\s:]+)?|[\w-]+(?:\.[\w-]+)*(?::[^\/\s:]+)?)(?![\/?#])$/ // eslint-disable-line
45

@@ -218,7 +219,7 @@ const isAllowedHost = (hostname) => {
218219

219220
export const validateAndSanitizeConfig = (config) => {
220221
if (!config?.url || typeof config?.url !== 'string') {
221-
throw new Error('Invalid request configuration: missing or invalid URL')
222+
throw new Error(ERROR_MESSAGES.INVALID_URL_CONFIG)
222223
}
223224

224225
// Validate the URL to prevent SSRF attacks

lib/core/concurrency-queue.js

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import Axios from 'axios'
22
import OAuthHandler from './oauthHandler'
33
import { validateAndSanitizeConfig } from './Util'
4+
import { ERROR_MESSAGES } from './errorMessages'
45

56
const defaultConfig = {
67
maxRequests: 5,
@@ -45,23 +46,23 @@ const defaultConfig = {
4546
*/
4647
export function ConcurrencyQueue ({ axios, config }) {
4748
if (!axios) {
48-
throw Error('Axios instance is not present')
49+
throw Error(ERROR_MESSAGES.AXIOS_INSTANCE_MISSING)
4950
}
5051

5152
if (config) {
5253
if (config.maxRequests && config.maxRequests <= 0) {
53-
throw Error('Concurrency Manager Error: minimum concurrent requests is 1')
54+
throw Error(ERROR_MESSAGES.MIN_CONCURRENT_REQUESTS)
5455
} else if (config.retryLimit && config.retryLimit <= 0) {
55-
throw Error('Retry Policy Error: minimum retry limit is 1')
56+
throw Error(ERROR_MESSAGES.MIN_RETRY_LIMIT)
5657
} else if (config.retryDelay && config.retryDelay < 300) {
57-
throw Error('Retry Policy Error: minimum retry delay for requests is 300')
58+
throw Error(ERROR_MESSAGES.MIN_RETRY_DELAY)
5859
}
5960
// Validate network retry configuration
6061
if (config.maxNetworkRetries && config.maxNetworkRetries < 0) {
6162
throw Error('Network Retry Policy Error: maxNetworkRetries cannot be negative')
6263
}
6364
if (config.networkRetryDelay && config.networkRetryDelay < 50) {
64-
throw Error('Network Retry Policy Error: minimum network retry delay is 50ms')
65+
throw Error(ERROR_MESSAGES.MIN_NETWORK_RETRY_DELAY)
6566
}
6667
}
6768

lib/core/contentstackHTTPClient.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import clonedeep from 'lodash/cloneDeep'
33
import Qs from 'qs'
44
import { ConcurrencyQueue } from './concurrency-queue'
55
import { isHost } from './Util'
6+
import { ERROR_MESSAGES } from './errorMessages'
67

78
export default function contentstackHttpClient (options) {
89
const defaultConfig = {
@@ -11,7 +12,7 @@ export default function contentstackHttpClient (options) {
1112
logHandler: (level, data) => {
1213
if (level === 'error' && data) {
1314
const title = [data.name, data.message].filter((a) => a).join(' - ')
14-
console.error(`[error] ${title}`)
15+
console.error(ERROR_MESSAGES.ERROR_WITH_TITLE(title))
1516
return
1617
}
1718
console.log(`[${level}] ${data}`)

lib/core/errorMessages.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/**
2+
* Centralized error messages for the Contentstack Management SDK.
3+
* All user-facing error messages should be defined here for consistency and maintainability.
4+
*/
5+
6+
export const ERROR_MESSAGES = {
7+
// Asset errors
8+
ASSET_URL_REQUIRED: 'Asset URL is required. Provide a valid asset URL and try again.',
9+
INVALID_UPLOAD_FORMAT: 'Invalid upload format. Provide a valid file path or Buffer and try again.',
10+
11+
// OAuth errors
12+
OAUTH_BASE_URL_NOT_SET: 'OAuth base URL is not configured. Set the OAuth base URL and try again.',
13+
NO_REFRESH_TOKEN: 'No refresh token available. Authenticate first and try again.',
14+
ACCESS_TOKEN_REQUIRED: 'Access token is required. Provide a valid access token and try again.',
15+
REFRESH_TOKEN_REQUIRED: 'Refresh token is required. Provide a valid refresh token and try again.',
16+
ORGANIZATION_UID_REQUIRED: 'Organization UID is required. Provide a valid organization UID and try again.',
17+
USER_UID_REQUIRED: 'User UID is required. Provide a valid user UID and try again.',
18+
TOKEN_EXPIRY_REQUIRED: 'Token expiry time is required. Provide a valid expiry time and try again.',
19+
AUTH_CODE_NOT_FOUND: 'Authorization code not found in redirect URL. Verify the redirect URL and try again.',
20+
NO_USER_AUTHORIZATIONS: 'No authorizations found for the current user. Verify user permissions and try again.',
21+
NO_APP_AUTHORIZATIONS: 'No authorizations found for the app. Verify app configuration and try again.',
22+
23+
// Concurrency queue errors
24+
AXIOS_INSTANCE_MISSING: 'Axios instance is not present. Initialize the HTTP client and try again.',
25+
MIN_CONCURRENT_REQUESTS: 'Concurrency Manager Error: Minimum concurrent requests must be at least 1.',
26+
MIN_RETRY_LIMIT: 'Retry Policy Error: Minimum retry limit must be at least 1.',
27+
MIN_RETRY_DELAY: 'Retry Policy Error: Minimum retry delay must be at least 300ms.',
28+
MIN_NETWORK_RETRY_DELAY: 'Network Retry Policy Error: Minimum network retry delay must be at least 50ms.',
29+
30+
// Request configuration errors
31+
INVALID_URL_CONFIG: 'Invalid request configuration: URL is missing or invalid. Provide a valid URL and try again.',
32+
33+
// General errors
34+
ERROR_WITH_TITLE: (title) => `An error occurred due to ${title}. Review the details and try again.`,
35+
36+
// Content type errors
37+
PARAMETER_NAME_REQUIRED: 'Parameter name is required. Provide a valid parameter name and try again.'
38+
}
39+
40+
export default ERROR_MESSAGES

lib/core/oauthHandler.js

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import errorFormatter from './contentstackError'
2+
import { ERROR_MESSAGES } from './errorMessages'
23

34
/**
45
* @description OAuthHandler class to handle OAuth authorization and token management
@@ -91,7 +92,7 @@ export default class OAuthHandler {
9192
async authorize () {
9293
try {
9394
if (!this.OAuthBaseURL) {
94-
throw new Error('OAuthBaseURL is not set')
95+
throw new Error(ERROR_MESSAGES.OAUTH_BASE_URL_NOT_SET)
9596
}
9697
const baseUrl = `${this.OAuthBaseURL}/#!/apps/${this.appId}/authorize`
9798
const authUrl = new URL(baseUrl)
@@ -171,7 +172,7 @@ export default class OAuthHandler {
171172
const refreshToken = providedRefreshToken || this.axiosInstance.oauth.refreshToken
172173

173174
if (!refreshToken) {
174-
throw new Error('No refresh token available. Please authenticate first.')
175+
throw new Error(ERROR_MESSAGES.NO_REFRESH_TOKEN)
175176
}
176177

177178
const body = new URLSearchParams({
@@ -308,7 +309,7 @@ export default class OAuthHandler {
308309
*/
309310
setAccessToken (token) {
310311
if (!token) {
311-
throw new Error('Access token is required')
312+
throw new Error(ERROR_MESSAGES.ACCESS_TOKEN_REQUIRED)
312313
}
313314
this.axiosInstance.oauth.accessToken = token
314315
}
@@ -327,7 +328,7 @@ export default class OAuthHandler {
327328
*/
328329
setRefreshToken (token) {
329330
if (!token) {
330-
throw new Error('Refresh token is required')
331+
throw new Error(ERROR_MESSAGES.REFRESH_TOKEN_REQUIRED)
331332
}
332333
this.axiosInstance.oauth.refreshToken = token
333334
}
@@ -346,7 +347,7 @@ export default class OAuthHandler {
346347
*/
347348
setOrganizationUID (organizationUID) {
348349
if (!organizationUID) {
349-
throw new Error('Organization UID is required')
350+
throw new Error(ERROR_MESSAGES.ORGANIZATION_UID_REQUIRED)
350351
}
351352
this.axiosInstance.oauth.organizationUID = organizationUID
352353
}
@@ -365,7 +366,7 @@ export default class OAuthHandler {
365366
*/
366367
setUserUID (userUID) {
367368
if (!userUID) {
368-
throw new Error('User UID is required')
369+
throw new Error(ERROR_MESSAGES.USER_UID_REQUIRED)
369370
}
370371
this.axiosInstance.oauth.userUID = userUID
371372
}
@@ -384,7 +385,7 @@ export default class OAuthHandler {
384385
*/
385386
setTokenExpiryTime (expiryTime) {
386387
if (!expiryTime) {
387-
throw new Error('Token expiry time is required')
388+
throw new Error(ERROR_MESSAGES.TOKEN_EXPIRY_REQUIRED)
388389
}
389390
this.axiosInstance.oauth.tokenExpiryTime = expiryTime
390391
}
@@ -414,7 +415,7 @@ export default class OAuthHandler {
414415
errorFormatter(error)
415416
}
416417
} else {
417-
throw new Error('Authorization code not found in redirect URL.')
418+
throw new Error(ERROR_MESSAGES.AUTH_CODE_NOT_FOUND)
418419
}
419420
}
420421

@@ -443,11 +444,11 @@ export default class OAuthHandler {
443444
const userUid = this.axiosInstance.oauth.userUID
444445
const currentUserAuthorization = data?.data?.filter((element) => element.user.uid === userUid) || []
445446
if (currentUserAuthorization.length === 0) {
446-
throw new Error('No authorizations found for current user!')
447+
throw new Error(ERROR_MESSAGES.NO_USER_AUTHORIZATIONS)
447448
}
448449
return currentUserAuthorization[0].authorization_uid // filter authorizations by current logged in user
449450
} else {
450-
throw new Error('No authorizations found for the app!')
451+
throw new Error(ERROR_MESSAGES.NO_APP_AUTHORIZATIONS)
451452
}
452453
} catch (error) {
453454
errorFormatter(error)

lib/stack/asset/index.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
unpublish } from '../../entity'
1111
import { Folder } from './folders'
1212
import error from '../../core/contentstackError'
13+
import { ERROR_MESSAGES } from '../../core/errorMessages'
1314
import FormData from 'form-data'
1415
import { createReadStream } from 'fs'
1516

@@ -289,7 +290,7 @@ export function Asset (http, data = {}) {
289290
} || { responseType }
290291
const requestUrl = url || this.url
291292
if (!requestUrl || requestUrl === undefined) {
292-
throw new Error('Asset URL can not be empty')
293+
throw new Error(ERROR_MESSAGES.ASSET_URL_REQUIRED)
293294
}
294295
return http.get(requestUrl, headers)
295296
} catch (err) {
@@ -338,7 +339,7 @@ export function createFormData (data) {
338339
formData.append('asset[upload]', uploadStream)
339340
}
340341
} else {
341-
throw new Error('Invalid upload format. Must be a file path or Buffer.')
342+
throw new Error(ERROR_MESSAGES.INVALID_UPLOAD_FORMAT)
342343
}
343344
return formData
344345
}

lib/stack/contentType/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
} from '../../entity'
1111
import { Entry } from './entry/index'
1212
import error from '../../core/contentstackError'
13+
import { ERROR_MESSAGES } from '../../core/errorMessages'
1314

1415
import FormData from 'form-data'
1516
import { createReadStream } from 'fs'
@@ -214,7 +215,7 @@ export function ContentType (http, data = {}) {
214215
*/
215216
this.generateUid = (name) => {
216217
if (!name) {
217-
throw new TypeError('Expected parameter name')
218+
throw new TypeError(ERROR_MESSAGES.PARAMETER_NAME_REQUIRED)
218219
}
219220
return name.replace(/[^A-Z0-9]+/gi, '_').toLowerCase()
220221
}

0 commit comments

Comments
 (0)