Skip to content
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
22 changes: 16 additions & 6 deletions packages/client/src/adapters/standard/rpc-link-codec.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ import * as ErrorModule from '../../error'
import { StandardRPCJsonSerializer } from './rpc-json-serializer'
import { StandardRPCLinkCodec } from './rpc-link-codec'
import { StandardRPCSerializer } from './rpc-serializer'
import * as UtilsModule from './utils'

const ORPCError = ErrorModule.ORPCError
const isORPCErrorStatusSpy = vi.spyOn(ErrorModule, 'isORPCErrorStatus')
const mergeStandardHeadersSpy = vi.spyOn(StandardServer, 'mergeStandardHeaders')
const getMalformedResponseErrorCodeSpy = vi.spyOn(UtilsModule, 'getMalformedResponseErrorCode')

beforeEach(() => {
vi.clearAllMocks()
Expand Down Expand Up @@ -177,26 +179,34 @@ describe('standardRPCLinkCodec', () => {
expect(deserializeSpy).toBeCalledWith({ meta: 123 })
})

it('error: Invalid RPC error response format.', async () => {
it('error: Malformed Response Error', async () => {
const error = new ORPCError('TEST', {
data: {
message: 'hello world',
},
})

const serialized = serializer.serialize({
...error.toJSON(),
code: undefined,
}) as any
const serialized = serializer.serialize({ something: 'value' }) as any

getMalformedResponseErrorCodeSpy.mockReturnValueOnce('__MOCKED_CODE__')

await expect(codec.decode({
status: 403,
headers: {},
body: () => Promise.resolve(serialized),
})).rejects.toThrow('MALFORMED_ORPC_ERROR_RESPONSE')
})).rejects.toSatisfy((e) => {
expect(e).toBeInstanceOf(ORPCError)
expect(e.code).toEqual('__MOCKED_CODE__')
expect(e.data).toEqual({ something: 'value' })

return true
})

expect(deserializeSpy).toBeCalledTimes(1)
expect(deserializeSpy).toBeCalledWith(serialized)

expect(getMalformedResponseErrorCodeSpy).toBeCalledTimes(1)
expect(getMalformedResponseErrorCodeSpy).toBeCalledWith(403)
})
})
})
4 changes: 2 additions & 2 deletions packages/client/src/adapters/standard/rpc-link-codec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { StandardLinkCodec } from './types'
import { isAsyncIteratorObject, stringifyJSON, value, type Value } from '@orpc/shared'
import { mergeStandardHeaders, type StandardHeaders, type StandardLazyResponse, type StandardRequest } from '@orpc/standard-server'
import { isORPCErrorStatus, ORPCError } from '../../error'
import { toHttpPath } from './utils'
import { getMalformedResponseErrorCode, toHttpPath } from './utils'

export interface StandardRPCLinkCodecOptions<T extends ClientContext> {
/**
Expand Down Expand Up @@ -147,7 +147,7 @@ export class StandardRPCLinkCodec<T extends ClientContext> implements StandardLi
throw ORPCError.fromJSON(deserialized)
}

throw new ORPCError('MALFORMED_ORPC_ERROR_RESPONSE', {
throw new ORPCError(getMalformedResponseErrorCode(response.status), {
status: response.status,
data: deserialized,
})
Expand Down
8 changes: 7 additions & 1 deletion packages/client/src/adapters/standard/utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import { toHttpPath } from './utils'
import { getMalformedResponseErrorCode, toHttpPath } from './utils'

it('convertPathToHttpPath', () => {
expect(toHttpPath(['ping'])).toEqual('/ping')
expect(toHttpPath(['nested', 'ping'])).toEqual('/nested/ping')
expect(toHttpPath(['nested/', 'ping'])).toEqual('/nested%2F/ping')
})

it('getMalformedResponseErrorCode', () => {
expect(getMalformedResponseErrorCode(400)).toEqual('BAD_REQUEST')
expect(getMalformedResponseErrorCode(401)).toEqual('UNAUTHORIZED')
expect(getMalformedResponseErrorCode(433)).toEqual('MALFORMED_ORPC_ERROR_RESPONSE')
})
5 changes: 5 additions & 0 deletions packages/client/src/adapters/standard/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import type { HTTPPath } from '../../types'
import { COMMON_ORPC_ERROR_DEFS } from '../../error'

export function toHttpPath(path: readonly string[]): HTTPPath {
return `/${path.map(encodeURIComponent).join('/')}`
}

export function getMalformedResponseErrorCode(status: number): string {
return Object.entries(COMMON_ORPC_ERROR_DEFS).find(([, def]) => def.status === status)?.[0] ?? 'MALFORMED_ORPC_ERROR_RESPONSE'
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as ClientModule from '@orpc/client'
import * as ClientStandardModule from '@orpc/client/standard'
import * as StandardServer from '@orpc/standard-server'
import { oc } from '../../../../contract/src/builder'
import { StandardBracketNotationSerializer } from './bracket-notation'
Expand All @@ -9,6 +10,7 @@ import { StandardOpenAPISerializer } from './openapi-serializer'
const ORPCError = ClientModule.ORPCError
const isORPCErrorStatusSpy = vi.spyOn(ClientModule, 'isORPCErrorStatus')
const mergeStandardHeadersSpy = vi.spyOn(StandardServer, 'mergeStandardHeaders')
const getMalformedResponseErrorCodeSpy = vi.spyOn(ClientStandardModule, 'getMalformedResponseErrorCode')

beforeEach(() => {
vi.clearAllMocks()
Expand Down Expand Up @@ -329,13 +331,15 @@ describe('standardOpenapiLinkCodecOptions', () => {
return true
})

getMalformedResponseErrorCodeSpy.mockReturnValueOnce('__MOCKED_CODE__')

await expect(codec.decode({
headers: { 'x-custom': 'value' },
body: async () => ({ something: 'data' }),
status: 409,
}, { context: {}, signal }, ['ping'])).rejects.toSatisfy((error: any) => {
expect(error).toBeInstanceOf(ORPCError)
expect(error.code).toEqual('MALFORMED_ORPC_ERROR_RESPONSE')
expect(error.code).toEqual('__MOCKED_CODE__')
expect(error.status).toBe(409)
expect(error.data).toEqual({ something: 'data' })

Expand All @@ -345,6 +349,9 @@ describe('standardOpenapiLinkCodecOptions', () => {
expect(isORPCErrorStatusSpy).toHaveBeenCalledTimes(2)
expect(isORPCErrorStatusSpy).toHaveBeenCalledWith(501)
expect(isORPCErrorStatusSpy).toHaveBeenCalledWith(409)

expect(getMalformedResponseErrorCodeSpy).toHaveBeenCalledTimes(1)
expect(getMalformedResponseErrorCodeSpy).toHaveBeenCalledWith(409)
})

it('throw if not found a procedure', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { StandardLinkCodec } from '@orpc/client/standard'
import type { AnyContractProcedure, AnyContractRouter } from '@orpc/contract'
import type { StandardOpenAPISerializer } from './openapi-serializer'
import { type ClientContext, type ClientOptions, type HTTPPath, isORPCErrorStatus } from '@orpc/client'
import { toHttpPath } from '@orpc/client/standard'
import { getMalformedResponseErrorCode, toHttpPath } from '@orpc/client/standard'
import { fallbackContractConfig, isContractProcedure, ORPCError } from '@orpc/contract'
import { get, isObject, value, type Value } from '@orpc/shared'
import { mergeStandardHeaders, type StandardHeaders, type StandardLazyResponse, type StandardRequest } from '@orpc/standard-server'
Expand Down Expand Up @@ -216,7 +216,7 @@ export class StandardOpenapiLinkCodec<T extends ClientContext> implements Standa
throw ORPCError.fromJSON(deserialized)
}

throw new ORPCError('MALFORMED_ORPC_ERROR_RESPONSE', {
throw new ORPCError(getMalformedResponseErrorCode(response.status), {
status: response.status,
data: deserialized,
})
Expand Down