Skip to content

Commit 1174892

Browse files
committed
feat(sdk): add structured error hierarchy with AuthenticationError
- Add BaseError, AuthenticationError, APIError, ConfigurationError classes - Replace generic Error with AuthenticationError for tokenProvider failures
1 parent 323e44d commit 1174892

10 files changed

Lines changed: 457 additions & 95 deletions

File tree

ATTRIBUTIONS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37887,7 +37887,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
3788737887

3788837888
## zod
3788937889

37890-
**Version:** 4.0.5
37890+
**Version:** 4.0.17
3789137891
**License:** MIT
3789237892

3789337893
```

package-lock.json

Lines changed: 7 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/sdk/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@
3939
"author": "Aignostics",
4040
"license": "MIT",
4141
"dependencies": {
42-
"axios": "^1.11.0"
42+
"axios": "^1.11.0",
43+
"zod": "^4.0.17"
4344
},
4445
"engines": {
4546
"node": ">=18.0.0"

packages/sdk/src/errors.test.ts

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import { describe, it, expect } from 'vitest';
2+
import { BaseError, AuthenticationError, APIError, ConfigurationError } from './errors.js';
3+
4+
describe('Error Classes', () => {
5+
describe('BaseError', () => {
6+
it('should create a BaseError with all properties', () => {
7+
const context = { userId: '123', action: 'test' };
8+
const error = new BaseError('Test message', 'UNEXPECTED_ERROR', { context });
9+
10+
expect(error).toBeInstanceOf(Error);
11+
expect(error).toBeInstanceOf(BaseError);
12+
expect(error.message).toBe('Test message');
13+
expect(error.code).toBe('UNEXPECTED_ERROR');
14+
expect(error.context).toEqual(context);
15+
expect(error.name).toBe('BaseError');
16+
});
17+
18+
it('should create a BaseError without context', () => {
19+
const error = new BaseError('Test message', 'UNEXPECTED_ERROR');
20+
21+
expect(error.message).toBe('Test message');
22+
expect(error.code).toBe('UNEXPECTED_ERROR');
23+
expect(error.context).toBeUndefined();
24+
});
25+
26+
it('should maintain proper stack trace', () => {
27+
const error = new BaseError('Test message', 'UNEXPECTED_ERROR');
28+
expect(error.stack).toBeDefined();
29+
expect(error.stack).toContain('BaseError');
30+
});
31+
});
32+
33+
describe('AuthenticationError', () => {
34+
it('should create an AuthenticationError with proper inheritance', () => {
35+
const error = new AuthenticationError('Invalid token');
36+
37+
expect(error).toBeInstanceOf(Error);
38+
expect(error).toBeInstanceOf(BaseError);
39+
expect(error).toBeInstanceOf(AuthenticationError);
40+
expect(error.message).toBe('Invalid token');
41+
expect(error.code).toBe('AUTHENTICATION_ERROR');
42+
expect(error.name).toBe('AuthenticationError');
43+
});
44+
45+
it('should create an AuthenticationError with context', () => {
46+
const context = { tokenType: 'Bearer', expiry: '2024-01-01' };
47+
const error = new AuthenticationError('Token expired', { context });
48+
49+
expect(error.message).toBe('Token expired');
50+
expect(error.context).toEqual(context);
51+
});
52+
});
53+
54+
describe('APIError', () => {
55+
it('should create an APIError with status code', () => {
56+
const error = new APIError('Server error', { statusCode: 500 });
57+
58+
expect(error).toBeInstanceOf(Error);
59+
expect(error).toBeInstanceOf(BaseError);
60+
expect(error).toBeInstanceOf(APIError);
61+
expect(error.message).toBe('Server error');
62+
expect(error.code).toBe('API_ERROR');
63+
expect(error.statusCode).toBe(500);
64+
expect(error.name).toBe('APIError');
65+
});
66+
67+
it('should create an APIError without status code', () => {
68+
const error = new APIError('Network error');
69+
70+
expect(error.message).toBe('Network error');
71+
expect(error.statusCode).toBeUndefined();
72+
});
73+
74+
it('should create an APIError with context and status code', () => {
75+
const context = { endpoint: '/api/test', method: 'GET' };
76+
const error = new APIError('Bad request', { context, statusCode: 400 });
77+
78+
expect(error.statusCode).toBe(400);
79+
expect(error.context).toEqual(context);
80+
});
81+
});
82+
83+
describe('ConfigurationError', () => {
84+
it('should create a ConfigurationError with proper inheritance', () => {
85+
const error = new ConfigurationError('Missing API key');
86+
87+
expect(error).toBeInstanceOf(Error);
88+
expect(error).toBeInstanceOf(BaseError);
89+
expect(error).toBeInstanceOf(ConfigurationError);
90+
expect(error.message).toBe('Missing API key');
91+
expect(error.code).toBe('CONFIGURATION_ERROR');
92+
expect(error.name).toBe('ConfigurationError');
93+
});
94+
95+
it('should create a ConfigurationError with context', () => {
96+
const context = { configFile: 'config.json', requiredFields: ['apiKey'] };
97+
const error = new ConfigurationError('Invalid configuration', { context });
98+
99+
expect(error.message).toBe('Invalid configuration');
100+
expect(error.context).toEqual(context);
101+
});
102+
});
103+
104+
describe('Error serialization', () => {
105+
it('should serialize errors properly', () => {
106+
const context = { test: 'value' };
107+
const error = new AuthenticationError('Test error', { context });
108+
109+
// Test that the error can be converted to JSON (useful for logging)
110+
const serialized = JSON.stringify({
111+
name: error.name,
112+
message: error.message,
113+
code: error.code,
114+
context: error.context,
115+
});
116+
117+
const parsed = JSON.parse(serialized) as {
118+
name: string;
119+
message: string;
120+
code: string;
121+
context: Record<string, unknown>;
122+
};
123+
expect(parsed.name).toBe('AuthenticationError');
124+
expect(parsed.message).toBe('Test error');
125+
expect(parsed.code).toBe('AUTHENTICATION_ERROR');
126+
expect(parsed.context).toEqual(context);
127+
});
128+
});
129+
});

packages/sdk/src/errors.ts

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
export type ErrorCode =
2+
| 'AUTHENTICATION_ERROR'
3+
| 'API_ERROR'
4+
| 'CONFIGURATION_ERROR'
5+
| 'UNEXPECTED_ERROR';
6+
7+
interface BaseErrorOptions {
8+
context?: Record<string, unknown>;
9+
originalError?: unknown;
10+
}
11+
/**
12+
* Base error class for all SDK-related errors
13+
*
14+
* This class serves as the foundation for all custom error types in the SDK,
15+
* providing consistent error handling and additional context information.
16+
*/
17+
export class BaseError extends Error {
18+
/**
19+
* Error code for programmatic error handling
20+
*/
21+
public readonly code: ErrorCode;
22+
23+
/**
24+
* Additional context or metadata related to the error
25+
*/
26+
public readonly context?: Record<string, unknown>;
27+
28+
/**
29+
* Original error that caused this error, if applicable
30+
*/
31+
public readonly originalError?: unknown;
32+
33+
/**
34+
* Creates a new BaseError instance
35+
*
36+
* @param message - Human-readable error message
37+
* @param code - Error code for programmatic handling
38+
* @param context - Additional error context or metadata
39+
*/
40+
constructor(message: string, code: ErrorCode, { context, originalError }: BaseErrorOptions = {}) {
41+
super(message);
42+
this.name = this.constructor.name;
43+
this.code = code;
44+
this.context = context;
45+
this.originalError = originalError;
46+
47+
// Maintains proper stack trace for where our error was thrown (Node.js only)
48+
if (Error.captureStackTrace) {
49+
Error.captureStackTrace(this, this.constructor);
50+
}
51+
}
52+
}
53+
54+
/**
55+
* Authentication-related error
56+
*
57+
* Thrown when authentication fails or when no valid authentication
58+
* credentials are available for API requests.
59+
*/
60+
export class AuthenticationError extends BaseError {
61+
/**
62+
* Creates a new AuthenticationError instance
63+
*
64+
* @param message - Human-readable error message describing the authentication issue
65+
* @param options - Additional options about the authentication failure
66+
* @param options.context - Additional context about the authentication failure
67+
* @param options.originalError - Original error that caused this authentication error
68+
*/
69+
constructor(message: string, options: BaseErrorOptions = {}) {
70+
super(message, 'AUTHENTICATION_ERROR', options);
71+
}
72+
}
73+
74+
/**
75+
* API-related error
76+
*
77+
* Thrown when API requests fail due to server errors, network issues,
78+
* or invalid responses from the Aignostics Platform API.
79+
*/
80+
export class APIError extends BaseError {
81+
/**
82+
* HTTP status code from the failed API request
83+
*/
84+
public readonly statusCode?: number;
85+
86+
/**
87+
* Creates a new APIError instance
88+
*
89+
* @param message - Human-readable error message describing the API issue
90+
* @param statusCode - HTTP status code from the failed request
91+
* @param context - Additional context about the API failure
92+
*/
93+
constructor(message: string, options: BaseErrorOptions & { statusCode?: number } = {}) {
94+
super(message, 'API_ERROR', options);
95+
this.statusCode = options.statusCode;
96+
}
97+
}
98+
99+
/**
100+
* Configuration-related error
101+
*
102+
* Thrown when SDK configuration is invalid or missing required parameters.
103+
*/
104+
export class ConfigurationError extends BaseError {
105+
/**
106+
* Creates a new ConfigurationError instance
107+
*
108+
* @param message - Human-readable error message describing the configuration issue
109+
* @param context - Additional context about the configuration problem
110+
*/
111+
constructor(message: string, options: BaseErrorOptions = {}) {
112+
super(message, 'CONFIGURATION_ERROR', options);
113+
}
114+
}
115+
116+
export class UnexpectedError extends BaseError {
117+
/**
118+
* Creates a new UnexpectedError instance
119+
*
120+
* @param message - Human-readable error message describing the unexpected issue
121+
* @param context - Additional context about the unexpected error
122+
*/
123+
constructor(message: string, options: BaseErrorOptions = {}) {
124+
super(message, 'UNEXPECTED_ERROR', options);
125+
}
126+
}

packages/sdk/src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ import { PlatformSDKHttp } from './platform-sdk.js';
33
// Export generated API types and client when available
44
export * from './generated/index.js';
55

6+
// Export error classes
7+
export { BaseError, AuthenticationError, APIError, ConfigurationError } from './errors.js';
8+
69
// Export main SDK and types
710
export {
811
PlatformSDKHttp,

0 commit comments

Comments
 (0)