diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 7cdabd8..b7cb647 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -27,9 +27,7 @@ jobs: runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v3 - - uses: denoland/setup-deno@v1 - with: - deno-version: vx.x.x - + - name: Install tsc + run: npm install - name: Run xo - run: deno run --allow-read --allow-write --allow-net --allow-sys --allow-env npm:xo --reporter unix src/* test.ts + run: npx xo --reporter unix src/* test.ts diff --git a/.xo-config.json b/.xo-config.json index 224f9cd..84f1ca2 100644 --- a/.xo-config.json +++ b/.xo-config.json @@ -22,6 +22,7 @@ }], "n/file-extension-in-import": "off", "promise-function-async": "off", + "default-case": "off", "prefer-promise-reject-errors": "off", "eslint-disable-next-line prefer-promise-reject-errors": "off", "unicorn/explicit-length-check": "off", diff --git a/package.json b/package.json index 71649ac..1d5f595 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "./ws-client.ts": "./src/ws-client.ts" }, "devDependencies": { - "typescript": "^5.4.5", - "xo": "^0.58.0" + "typescript": "^5.5.4", + "xo": "^0.59.3" } } diff --git a/src/rpcmessage.ts b/src/rpcmessage.ts index cfc2cc2..813c599 100644 --- a/src/rpcmessage.ts +++ b/src/rpcmessage.ts @@ -11,6 +11,56 @@ const KeyParams = 1; const KeyResult = 2; const KeyError = 3; +export const ERROR_CODE = 1; +export const ERROR_MESSAGE = 2; +export const ERROR_DATA = 3; + +enum ErrorCode { + InvalidRequest = 1, + MethodNotFound = 2, + InvalidParams = 3, + InternalError = 4, + ParseError = 5, + MethodCallTimeout = 6, + MethodCallCancelled = 7, + MethodCallException = 8, + Unknown = 9, + LoginRequired = 10, + UserIDRequired = 11, + NotImplemented = 12, +} + +type ErrorMap = IMap<{ + [ERROR_CODE]: Int; + [ERROR_MESSAGE]?: string; + [ERROR_DATA]?: RpcValue; +}>; + +class RpcError extends Error { + constructor(private readonly err_info: ErrorMap) { + super(err_info.value[ERROR_MESSAGE]); + } + + data() { + return this.err_info.value[ERROR_DATA]; + } +} + +export class ProtocolError extends Error {} + +export class InvalidRequest extends RpcError {} +export class MethodNotFound extends RpcError {} +export class InvalidParams extends RpcError {} +export class InternalError extends RpcError {} +export class ParseError extends RpcError {} +export class MethodCallTimeout extends RpcError {} +export class MethodCallCancelled extends RpcError {} +export class MethodCallException extends RpcError {} +export class Unknown extends RpcError {} +export class LoginRequired extends RpcError {} +export class UserIDRequired extends RpcError {} +export class NotImplemented extends RpcError {} + class RpcMessage { value: IMap; meta: MetaMap; @@ -87,10 +137,40 @@ class RpcMessage { resultOrError() { if (this.value.value[KeyError] !== undefined) { - return new RpcError(this.value.value[KeyError] as ErrorMap); + if (!(this.value.value[KeyError] instanceof IMap)) { + return new ProtocolError('Response had an error, but this error was not a map'); + } + + const error_map = this.value.value[KeyError]; + if (error_map.value[ERROR_CODE] === undefined) { + return new ProtocolError('Response had an error, but this error did not contain at least an error code'); + } + + const ErrorType = (() => { + switch ((this.value.value[KeyError] as ErrorMap).value[ERROR_CODE].value) { + case ErrorCode.InvalidRequest: return InvalidRequest; + case ErrorCode.MethodNotFound: return MethodNotFound; + case ErrorCode.InvalidParams: return InvalidParams; + case ErrorCode.InternalError: return InternalError; + case ErrorCode.ParseError: return ParseError; + case ErrorCode.MethodCallTimeout: return MethodCallTimeout; + case ErrorCode.MethodCallCancelled: return MethodCallCancelled; + case ErrorCode.MethodCallException: return MethodCallException; + case ErrorCode.Unknown: return Unknown; + case ErrorCode.LoginRequired: return LoginRequired; + case ErrorCode.UserIDRequired: return UserIDRequired; + case ErrorCode.NotImplemented: return NotImplemented; + } + })(); + + return new ErrorType(this.value.value[KeyError] as ErrorMap); + } + + if (this.value.value[KeyResult] !== undefined) { + return this.value.value[KeyResult]; } - return this.value.value[KeyResult]; + return new ProtocolError('Response included neither result nor error'); } setResult(result: RpcValue) { @@ -110,46 +190,6 @@ class RpcMessage { } } -export const ERROR_CODE = 1; -export const ERROR_MESSAGE = 2; -export const ERROR_DATA = 3; - -enum ErrorCode { - InvalidRequest = 1, - MethodNotFound = 2, - InvalidParams = 3, - InternalError = 4, - ParseError = 5, - MethodCallTimeout = 6, - MethodCallCancelled = 7, - MethodCallException = 8, - Unknown = 9, - LoginRequired = 10, - UserIDRequired = 11, - NotImplemented = 12, -} - -type ErrorMap = IMap<{ - [ERROR_CODE]: Int; - [ERROR_MESSAGE]?: string; - [ERROR_DATA]?: RpcValue; -}>; - -class RpcError { - constructor(private readonly err_info: ErrorMap) {} - code() { - return this.err_info.value[ERROR_CODE]; - } - - message() { - return this.err_info.value[ERROR_MESSAGE]; - } - - data() { - return this.err_info.value[ERROR_DATA]; - } -} - -export type RpcResponse = T | RpcError; +export type RpcResponse = T | Error; export {RpcMessage, RpcError, ErrorCode}; diff --git a/src/ws-client.ts b/src/ws-client.ts index fb95191..4c9f685 100644 --- a/src/ws-client.ts +++ b/src/ws-client.ts @@ -1,6 +1,6 @@ import {ChainPackReader, ChainpackProtocolType, ChainPackWriter} from './chainpack.ts'; import {type CponReader, CponProtocolType, toCpon} from './cpon.ts'; -import {ERROR_MESSAGE, ErrorCode, ERROR_CODE, RpcError, RpcMessage, type RpcResponse} from './rpcmessage.ts'; +import {ERROR_MESSAGE, ErrorCode, ERROR_CODE, RpcError, RpcMessage, type RpcResponse, MethodCallTimeout} from './rpcmessage.ts'; import {type RpcValue, type Null, Int, IMap, ShvMap} from './rpcvalue.ts'; const DEFAULT_TIMEOUT = 5000; @@ -194,7 +194,7 @@ class WsClient { const promise = new Promise(resolve => { this.rpcHandlers[rq_id] = {resolve, timeout_handle: self.setTimeout(() => { - resolve(new RpcError(new IMap({ + resolve(new MethodCallTimeout(new IMap({ [ERROR_CODE]: new Int(ErrorCode.MethodCallTimeout), [ERROR_MESSAGE]: `Shv call timeout after: ${this.timeout} msec.`, })));