Skip to content

Commit

Permalink
parent 3eea097
Browse files Browse the repository at this point in the history
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
yanguoyu committed Mar 20, 2023
1 parent 1d9d118 commit b03435a
Show file tree
Hide file tree
Showing 9 changed files with 260 additions and 34 deletions.
74 changes: 42 additions & 32 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

119 changes: 119 additions & 0 deletions packages/models/__tests__/contract/contract.ts
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,
)
})
})
})
3 changes: 2 additions & 1 deletion packages/models/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@
"ioredis": "5.3.1",
"lodash": "4.17.21",
"reflect-metadata": "0.1.13",
"tslib": "2.5.0"
"tslib": "2.5.0",
"undici": "5.20.0"
},
"devDependencies": {
"@jest/globals": "29.5.0",
Expand Down
69 changes: 69 additions & 0 deletions packages/models/src/contract/contract.ts
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']>
}
}
}
3 changes: 2 additions & 1 deletion packages/models/src/contract/index.ts
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'
19 changes: 19 additions & 0 deletions packages/models/src/contract/interface.ts
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'
5 changes: 5 additions & 0 deletions packages/models/src/exceptions/contract.ts
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`)
}
}
1 change: 1 addition & 0 deletions packages/models/src/exceptions/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './store'
export * from './contract'
Loading

0 comments on commit b03435a

Please sign in to comment.