-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
author yanguoyu <[email protected]> 1670485818 +0800 committer yanguoyu <[email protected]> 1679301169 +0800 gpgsig -----BEGIN PGP SIGNATURE----- iQGzBAABCAAdFiEEV9lGWxNFVh7dkSjRu4XTbIob1vwFAmQYGjEACgkQu4XTbIob 1vw0UAwApY+vpeL9oPOeZzVSdxSZFwm1MP/DTjq8XLAAIN4PLbSJ/gOLp/qL6hG6 DyJLBjTi9c6hCvLk+7qJxBjD1ea4mF0o0Cct4W7bR34fJVs81q68cU7kSv7a3aFs DVYqT5rs6DLp742bT1lkw8HGlzkxioQY4IoB3xMX6E8dArs5b5FY2m+WcNQia3Ey 1GD1WW6tmaCfahYYY190gk7slA2awpMxEJOE45Kpv8MnY5Ve6gUIe2Yi8Qntvu5G gB+Dz5XiCopMBKWMv+Vjf9NwzHoc/QlxeOAAMQbBekzBokuo0V62EXwnEPBWx1o4 5+RzP62FqpCSiVXF04jw+0WG/9HRK3QY2zxqDLnSlZ9J9XegoKrO5nsgc1WC8/h/ ZZSrTuVh2J80FbQiSE+hKbAKOivF76Zs3Rl/TEkKPB50SAFEzdwtQGlHFO+ndXuQ 1+Z1mM8N1TPPrst7ghTYqjK5QQ5MfapwpqW8gUIS+a08/UVPEOnVh3TyITNCE1Zs 2RpfdR0/ =F0oW -----END PGP SIGNATURE----- feat: Implement of contract. fix: Upload lock file. fix: Fix types after rebase develop fix: upgrade undici version fix: Remove method to payload.value
- Loading branch information
Showing
9 changed files
with
260 additions
and
34 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
import { describe, it, expect, jest } from '@jest/globals' | ||
import { ProviderKey, PROTOCOL, Status, ActorURI, MessagePayload } from '../../src' | ||
import type { ContractMethod } from '../../src/contract' | ||
import { REMOTE_CALL_PATTERN } from '../../src/contract' | ||
import { ParamsMissException } from '../../src/exceptions' | ||
|
||
const ref = { | ||
name: '', | ||
protocol: '', | ||
path: '', | ||
uri: 'contract', | ||
} | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
const requestMock = jest.fn<() => Promise<{ body: { json: () => any } }>>() | ||
jest.mock('undici', () => ({ | ||
request: () => requestMock(), | ||
})) | ||
const callMock = jest.fn() | ||
jest.mock('../../src/actor/actor', () => { | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
const originalModule = jest.requireActual('../../src/actor/actor') as any | ||
class Actor { | ||
call(to: ActorURI, payload: MessagePayload, timeout?: number) { | ||
return callMock(to, payload, timeout) | ||
} | ||
} | ||
return { | ||
__esModule: true, | ||
...originalModule, | ||
Actor, | ||
} | ||
}) | ||
import { Contract } from '../../src/contract/contract' | ||
Reflect.defineMetadata(ProviderKey.Actor, { ref }, Contract) | ||
|
||
describe('test contract', () => { | ||
describe('test run', () => { | ||
const contract = new Contract(undefined) | ||
it('call remote params miss', async () => { | ||
await expect(contract.run(`114.0.1.1:8008/get_a`, null)).rejects.toThrow(new ParamsMissException()) | ||
}) | ||
it('call remote params miss', async () => { | ||
await expect(contract.run(`114.0.1.1:8008/get_a`, { pattern: 'get_a' })).rejects.toThrow( | ||
new ParamsMissException(), | ||
) | ||
}) | ||
it('call remote ok', async () => { | ||
requestMock.mockResolvedValueOnce({ | ||
body: { | ||
json() { | ||
return 10 | ||
}, | ||
}, | ||
}) | ||
const getA = 'get_a' | ||
const res = await contract.run(`114.0.1.1:8008/get_a`, { | ||
pattern: getA, | ||
value: { params: 10, method: 'remote_method' }, | ||
}) | ||
expect(res.status).toBe(Status.ok) | ||
expect(res.message?.pattern).toBe(getA) | ||
expect(res.message?.value).toBe(10) | ||
}) | ||
it('call remote exception', async () => { | ||
requestMock.mockRejectedValueOnce({ | ||
body: { | ||
json() { | ||
return 10 | ||
}, | ||
}, | ||
}) | ||
const getA = 'get_a' | ||
const res = await contract.run(`114.0.1.1:8008/get_a`, { pattern: getA, value: { method: 'remote_method' } }) | ||
expect(res.status).toBe(Status.error) | ||
expect(res.message).toBeNull() | ||
}) | ||
it('call local', async () => { | ||
await contract.run(`${PROTOCOL.LOCAL}/get`, null) | ||
expect(callMock).toBeCalledWith(`${PROTOCOL.LOCAL}/get`, null, undefined) | ||
}) | ||
}) | ||
|
||
describe('test link', () => { | ||
it('link and call remote', async () => { | ||
const contract = new Contract(undefined) | ||
const res = contract.link<{ a: ContractMethod<number, number> }>(['a']) | ||
requestMock.mockResolvedValueOnce({ | ||
body: { | ||
json() { | ||
return 20 | ||
}, | ||
}, | ||
}) | ||
const result = await res.a('114.0.0.1://', 10) | ||
expect(result.status).toBe(Status.ok) | ||
expect(result.message?.pattern).toBe(REMOTE_CALL_PATTERN) | ||
expect(result.message?.value).toBe(20) | ||
}) | ||
it('link and call local', async () => { | ||
const contract = new Contract(undefined) | ||
const res = contract.link<{ a: ContractMethod<number, number> }>(['a']) | ||
requestMock.mockResolvedValueOnce({ | ||
body: { | ||
json() { | ||
return 20 | ||
}, | ||
}, | ||
}) | ||
const getA = 'get_a' | ||
await res.a(`${PROTOCOL.LOCAL}://`, 10, getA) | ||
expect(callMock).toBeCalledWith( | ||
`${PROTOCOL.LOCAL}://`, | ||
{ pattern: getA, value: { params: 10, method: 'a' } }, | ||
undefined, | ||
) | ||
}) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import { request } from 'undici' | ||
import { ActorURI, CallResponse, MessagePayload } from '../actor' | ||
import { ParamsMissException as MethodMissException } from '../exceptions' | ||
import { Store } from '../store' | ||
import { PROTOCOL, Status } from '../utils' | ||
import { REMOTE_CALL_PATTERN } from './interface' | ||
import type { ChainStorage, GetState, StorageSchema } from '../store' | ||
import type { ContractMethod, ContractMethodInterface, ContractPayload } from './interface' | ||
|
||
export class Contract< | ||
StorageT extends ChainStorage, | ||
StructSchema extends StorageSchema<GetState<StorageT>> = Record<string, never>, | ||
Option = never, | ||
> extends Store<StorageT, StructSchema, Option> { | ||
async run(to: ActorURI, payload: MessagePayload<ContractPayload>): Promise<CallResponse<MessagePayload>> { | ||
if (to.startsWith(PROTOCOL.LOCAL)) { | ||
// call local method | ||
return this.call(to, payload) | ||
} | ||
if (!payload || !payload.value?.method) throw new MethodMissException() | ||
// call remote service | ||
try { | ||
const res = await request(to, { | ||
method: 'POST', | ||
body: JSON.stringify({ | ||
id: 0, | ||
jsonrpc: '2.0', | ||
method: payload.value?.method, | ||
params: payload.value.params, | ||
}), | ||
headers: { | ||
'content-type': 'application/json', | ||
}, | ||
}) | ||
return { | ||
status: Status.ok, | ||
message: { | ||
pattern: payload.pattern, | ||
value: res.body.json(), | ||
}, | ||
} | ||
} catch (error) { | ||
return { | ||
status: Status.error, | ||
message: null, | ||
} | ||
} | ||
} | ||
|
||
// link others contracts methods, it may be a service | ||
link<T extends ContractMethodInterface>( | ||
contractAPIs: string[], | ||
): Contract<StorageT> & { | ||
[P in keyof T]: (to: ActorURI, param: T[P]['params'], pattern?: string) => Promise<T[P]['result']> | ||
} { | ||
const attacthMethods: Record<ActorURI, PropertyDescriptor> = {} | ||
contractAPIs.forEach((name) => { | ||
attacthMethods[name] = { | ||
value: (to: ActorURI, p: ContractMethod['params'], pattern?: string) => | ||
this.run(to, { pattern: pattern || REMOTE_CALL_PATTERN, value: { method: name, params: p } }), | ||
enumerable: true, | ||
} | ||
}) | ||
Object.defineProperties(this, attacthMethods) | ||
return this as unknown as InstanceType<typeof Contract<StorageT>> & { | ||
[P in keyof T]: (to: ActorURI, param: T[P]['params'], pattern?: string) => Promise<T[P]['result']> | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
// TODO: https://github.com/ckb-js/kuai/issues/3 | ||
export * from './contract' | ||
export * from './interface' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import type { ActorURI, CallResponse, MessagePayload } from '../actor' | ||
|
||
type ContractParams = string | number | object | ||
|
||
export type ContractPayload = { | ||
method?: string | ||
params?: ContractParams | ||
} | ||
|
||
export interface ContractMethod<P = ContractParams, Result = unknown> { | ||
params?: P | ||
result: CallResponse<MessagePayload<Result>> | ||
} | ||
|
||
export interface ContractMethodInterface { | ||
[key: ActorURI]: ContractMethod | ||
} | ||
|
||
export const REMOTE_CALL_PATTERN = 'remote_call' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
export class ParamsMissException extends Error { | ||
constructor() { | ||
super(`Remote call should deliver method name`) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
export * from './store' | ||
export * from './contract' |
Oops, something went wrong.