Skip to content

Commit

Permalink
feat(sdk)!: add proper tags to ao Message and verify proper process t…
Browse files Browse the repository at this point in the history
…ags. Allow passing data #216
  • Loading branch information
TillaTheHun0 committed Dec 12, 2023
1 parent 6931691 commit f10aa61
Show file tree
Hide file tree
Showing 7 changed files with 115 additions and 73 deletions.
5 changes: 3 additions & 2 deletions sdk/src/lib/message/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { uploadMessageWith } from './upload-message.js'
*
* @typedef SendMessageArgs
* @property {string} process
* @property {string | ArrayBuffer} [data]
* @property {string} [anchor]
* @property {{ name: string, value: string }[]} [tags]
* @property {any} signer
Expand All @@ -25,8 +26,8 @@ export function messageWith (env) {
const verifyProcess = verifyProcessWith(env)
const uploadMessage = uploadMessageWith(env)

return ({ process, tags, anchor, signer }) => {
return of({ id: process, tags, anchor, signer })
return ({ process, data, tags, anchor, signer }) => {
return of({ id: process, data, tags, anchor, signer })
.chain(verifyProcess)
.chain(uploadMessage)
.map((ctx) => ctx.messageId) // the id of the data item
Expand Down
48 changes: 33 additions & 15 deletions sdk/src/lib/message/upload-message.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { fromPromise, of } from 'hyper-async'
import { Resolved, fromPromise, of } from 'hyper-async'
import { z } from 'zod'
import { __, assoc, concat, defaultTo, propEq, reject } from 'ramda'
import { __, always, append, assoc, concat, defaultTo, ifElse, pipe, prop, propEq, reject } from 'ramda'

import { deployMessageSchema, signerSchema } from '../../dal.js'

Expand Down Expand Up @@ -32,21 +32,12 @@ const tagSchema = z.array(z.object({
* @returns { BuildTags }
*/
function buildTagsWith () {
function removeTagsByName (name) {
return (tags) => reject(propEq(name, 'name'), tags)
}

return (ctx) => {
return of(ctx.tags)
.map(defaultTo([]))
/**
* Remove any reserved tags, so that the sdk
* can properly set them
*/
.map(removeTagsByName('ao-type'))
.map(concat(__, [
{ name: 'Data-Protocol', value: 'ao' },
{ name: 'ao-type', value: 'message' },
{ name: 'Type', value: 'Message' },
{ name: 'SDK', value: 'ao' }
]))
.map(tagSchema.parse)
Expand All @@ -61,11 +52,38 @@ function buildTagsWith () {
*
* @returns { BuildData }
*/
function buildDataWith () {
function buildDataWith ({ logger }) {
function removeTagsByName (name) {
return (tags) => reject(propEq(name, 'name'), tags)
}

return (ctx) => {
return of(ctx)
.map(() => Math.random().toString().slice(-4))
.map(assoc('data', __, ctx))
.chain(ifElse(
always(ctx.data),
/**
* data is provided as input, so do nothing
*/
() => Resolved(ctx),
/**
* Just generate a random value for data
*/
() => Resolved(Math.random().toString().slice(-4))
.map(assoc('data', __, ctx))
/**
* Since we generate the data value, we know it's Content-Type,
* so set it on the tags
*/
.map(
(ctx) => pipe(
prop('tags'),
removeTagsByName('Content-Type'),
append({ name: 'Content-Type', value: 'text/plain' }),
assoc('tags', __, ctx)
)(ctx)
)
.map(logger.tap('added pseudo-random string as message "data"'))
))
}
}

Expand Down
11 changes: 8 additions & 3 deletions sdk/src/lib/message/upload-message.test.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { describe, test } from 'node:test'
import * as assert from 'node:assert'

import { createLogger } from '../../logger.js'
import { uploadMessageWith } from './upload-message.js'

const logger = createLogger('message')

describe('upload-message', () => {
test('add the tags, sign, and upload the message, and return the messageId', async () => {
const uploadMessage = uploadMessageWith({
Expand All @@ -12,14 +15,16 @@ describe('upload-message', () => {
assert.deepStrictEqual(tags, [
{ name: 'foo', value: 'bar' },
{ name: 'Data-Protocol', value: 'ao' },
{ name: 'ao-type', value: 'message' },
{ name: 'SDK', value: 'ao' }
{ name: 'Type', value: 'Message' },
{ name: 'SDK', value: 'ao' },
{ name: 'Content-Type', value: 'text/plain' }
])
assert.equal(anchor, 'idempotent-123')
assert.ok(signer)

return { messageId: 'data-item-123' }
}
},
logger
})

await uploadMessage({
Expand Down
26 changes: 11 additions & 15 deletions sdk/src/lib/message/verify-process.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Rejected, Resolved, fromPromise, of } from 'hyper-async'
import { anyPass, equals, includes, isNotNil, path } from 'ramda'
import { isNotNil, prop } from 'ramda'

import { loadProcessMetaSchema } from '../../dal.js'
import { parseTags } from '../utils.js'
import { eqOrIncludes, parseTags } from '../utils.js'

/**
* @typedef Env5
Expand All @@ -14,25 +14,21 @@ import { parseTags } from '../utils.js'
* @param {Env5} env
* @returns {any} VerifyTags
*/
function verfiyTagsWith ({ loadProcessMeta }) {
const checkTag = (name, pred) => tags => pred(tags[name])
function verifyProcessTagsWith ({ loadProcessMeta }) {
const checkTag = (name, pred, err) => tags => pred(tags[name])
? Resolved(tags)
: Rejected(`Tag '${name}' of value '${tags[name]}' was not valid on contract source`)
: Rejected(`Tag '${name}': ${err}`)

loadProcessMeta = fromPromise(loadProcessMetaSchema.implement(loadProcessMeta))

return (id) => {
return of(id)
.chain(loadProcessMeta)
.map(path(['tags']))
.map(prop('tags'))
.map(parseTags)
/**
* The process could implement multiple Data-Protocols,
* so check in the case of a single value or an array of values
*/
.chain(checkTag('Data-Protocol', anyPass([equals('ao'), includes('ao')])))
.chain(checkTag('ao-type', equals('process')))
.chain(checkTag('Contract-Src', isNotNil))
.chain(checkTag('Data-Protocol', eqOrIncludes('ao'), 'value \'ao\' was not found on process'))
.chain(checkTag('Type', eqOrIncludes('Process'), 'value \'Process\' was not found on process'))
.chain(checkTag('Module', isNotNil, 'was not found on process'))
}
}

Expand All @@ -58,10 +54,10 @@ function verfiyTagsWith ({ loadProcessMeta }) {
* @returns {VerifyProcess}
*/
export function verifyProcessWith (env) {
const verfiyTags = verfiyTagsWith(env)
const verifyProcess = verifyProcessTagsWith(env)
return (ctx) => {
return of(ctx.id)
.chain(verfiyTags)
.chain(verifyProcess)
.map(() => ctx)
}
}
94 changes: 58 additions & 36 deletions sdk/src/lib/message/verify-process.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,10 @@ describe('verify-process', () => {
loadProcessMeta: async (_id) =>
({
tags: [
{ name: 'Contract-Src', value: 'foobar' },
{ name: 'Data-Protocol', value: 'ao' },
// Implements multiple Data-Protocols
{ name: 'Data-Protocol', value: 'foo' },
{ name: 'ao-type', value: 'process' }
{ name: 'Data-Protocol', value: 'Data-Protocol' },
{ name: 'Type', value: 'Process' },
{ name: 'Module', value: 'module-123' }
]
})
})
Expand All @@ -25,47 +24,70 @@ describe('verify-process', () => {
.catch(() => assert('unreachable. Should have succeeded'))
})

test('throw if the Contract-Src tag is not provided', async () => {
const verifyProcess = verifyProcessWith({
loadProcessMeta: async (_id) =>
({
tags: [
{ name: 'Data-Protocol', value: 'ao' },
{ name: 'ao-type', value: 'process' }
]
describe('throw if required tag is invalid on process', () => {
test('Data-Protocol', async () => {
const verifyProcess = verifyProcessWith({
loadProcessMeta: async (_id) =>
({
tags: [
{ name: 'Data-Protocol', value: 'not_ao' },
{ name: 'Data-Protocol', value: 'Data-Protocol' },
{ name: 'Type', value: 'Process' },
{ name: 'Module', value: 'module-123' }
]
})
})

await verifyProcess({ id: PROCESS }).toPromise()
.then(assert.fail)
.catch(err => {
assert.equal(
err,
"Tag 'Data-Protocol': value 'ao' was not found on process"
)
})
})

await verifyProcess({ id: PROCESS }).toPromise()
.catch(assert.ok)
})
test('Type', async () => {
const verifyProcess = verifyProcessWith({
loadProcessMeta: async (_id) =>
({
tags: [
{ name: 'Data-Protocol', value: 'ao' },
{ name: 'Type', value: 'Not_process' }
]
})
})

test('throw if the Data-Protocol tag is not provided', async () => {
const verifyProcess = verifyProcessWith({
loadProcessMeta: async (_id) =>
({
tags: [
{ name: 'Contract-Src', value: 'foobar' },
{ name: 'ao-type', value: 'process' }
]
await verifyProcess({ id: PROCESS }).toPromise()
.then(assert.fail)
.catch(err => {
assert.equal(
err,
"Tag 'Type': value 'Process' was not found on process"
)
})
})

await verifyProcess({ id: PROCESS }).toPromise()
.catch(assert.ok)
})
test('Module', async () => {
const verifyProcess = verifyProcessWith({
loadProcessMeta: async (_id) =>
({
tags: [
{ name: 'Data-Protocol', value: 'ao' },
{ name: 'Type', value: 'Process' }
]
})
})

test('throw if multiple tags not provided', async () => {
const verifyProcess = verifyProcessWith({
loadProcessMeta: async (_id) =>
({
tags: [
{ name: 'Data-Protocol', value: 'ao' }
]
await verifyProcess({ id: PROCESS }).toPromise()
.then(assert.fail)
.catch(err => {
assert.equal(
err,
"Tag 'Module': was not found on process"
)
})
})

await verifyProcess({ id: PROCESS }).toPromise()
.catch(assert.ok)
})
})
2 changes: 1 addition & 1 deletion sdk/src/lib/spawn/upload-process.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ function buildDataWith ({ logger }) {
assoc('tags', __, ctx)
)(ctx)
)
.map(logger.tap('added pseudo-random data as payload for contract at "data"'))
.map(logger.tap('added pseudo-random string as process "data"'))
))
}
}
Expand Down
2 changes: 1 addition & 1 deletion sdk/src/lib/spawn/verify-inputs.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { verifyInputsWith } from './verify-inputs.js'

const MODULE = 'zVkjFCALjk4xxuCilddKS8ShZ-9HdeqeuYQOgMgWucro'

const logger = createLogger('createContract')
const logger = createLogger('spawn')

describe('verify-input', () => {
test('verify source tags and verify signer', async () => {
Expand Down

0 comments on commit f10aa61

Please sign in to comment.