diff --git a/README.md b/README.md index 6e18ce2..42bdc88 100644 --- a/README.md +++ b/README.md @@ -148,10 +148,10 @@ function main(req, res) { } ``` -``` -curl -g -i -X PUT 'http://localhost:8100/functions/pipeline?steps[0]=namespace/function0&steps[1]=namespace/function1' \ - -H 'content-type: application/json' - -d '{"x": 1}' +``` bash +$ curl -g -i -X PUT 'http://localhost:8100/functions/pipeline' \ + -H 'content-type: application/json' \ + -d '{"steps": [{"namespace":"namespace", "id":"function0"}, {"namespace":"namespace", "id": "function1"}], "payload":{"x":1}}' ``` Considering the curl above, the pipeline result would be like this: diff --git a/lib/domain/schemas.js b/lib/domain/schemas.js index 220a797..08b1f04 100644 --- a/lib/domain/schemas.js +++ b/lib/domain/schemas.js @@ -173,6 +173,40 @@ const functionItem = { links: functionsItemLinks.concat(functionLinks), }; +const functionPipeline = { + $schema: 'http://json-schema.org/draft-04/hyper-schema#', + type: 'object', + title: 'Pipeline', + properties: { + steps: { + type: 'array', + title: 'Steps', + readOnly: true, + items: { + type: 'object', + properties: { + namespace: { + type: 'string', + title: 'Namespace', + readOnly: true, + }, + id: { + type: 'string', + title: 'ID', + readOnly: true, + }, + }, + }, + }, + payload: { + type: 'object', + title: 'Payload', + readOnly: true, + }, + }, + required: ['steps'], +}; + const functionEnv = { $schema: 'http://json-schema.org/draft-04/hyper-schema#', type: 'string', @@ -202,4 +236,5 @@ exports.root = root; exports['functions/item'] = functionItem; exports['functions/env'] = functionEnv; exports['functions/list'] = functions; +exports['functions/pipeline'] = functionPipeline; exports['namespaces/item'] = namespaceItem; diff --git a/lib/http/routers/FunctionsRouter.js b/lib/http/routers/FunctionsRouter.js index b15281e..420aef7 100644 --- a/lib/http/routers/FunctionsRouter.js +++ b/lib/http/routers/FunctionsRouter.js @@ -291,26 +291,26 @@ router.all('/:namespace/:id/run', bodyParser.json({ limit: bodyParserLimit }), a router.put('/pipeline', bodyParser.json({ limit: bodyParserLimit }), async (req, res, next) => { const memoryStorage = req.app.get('memoryStorage'); const sandbox = req.app.get('sandbox'); + const validationResult = new Validator().validate(req.body, schemas['functions/pipeline']); - let { steps } = req.query; + if (!validationResult.valid) { + const error = 'Invalid pipeline configuration'; + const details = validationResult.errors.map(e => e.toString()); + + res.status(400).json({ error, details }); + return; + } + + const steps = req.body.steps; + req.body = req.body.payload; const span = req.span.tracer().startSpan('run pipeline', { childOf: req.span, tags: { - 'function.steps': Array.isArray(steps) ? steps.join(', ') : '', + 'function.steps': Array.isArray(steps) ? JSON.stringify(steps) : '', }, }); - if (!steps) { - const err = new Error('Pass step by querystring is required'); - reportError(span, err); - res.status(400).json({ error: err.message }); - return; - } - steps = steps.map((step) => { - const [namespace, id] = step.split('/', 2); - return { namespace, id }; - }); const metric = new Metric('pipeline-run'); @@ -342,7 +342,7 @@ router.put('/pipeline', bodyParser.json({ limit: bodyParserLimit }), async (req, res.json(result.body); } catch (err) { reportError(span, err); - RecordOtelError(err) + RecordOtelError(err); const status = err.statusCode || 500; metric.observePipelineRun(status); res.status(status).json({ error: err.message }); diff --git a/test/unit/http/routers/FunctionsRouter.test.js b/test/unit/http/routers/FunctionsRouter.test.js index a7b4335..ca05604 100644 --- a/test/unit/http/routers/FunctionsRouter.test.js +++ b/test/unit/http/routers/FunctionsRouter.test.js @@ -313,7 +313,8 @@ describe('PUT /functions/pipeline', () => { request(routes) .put('/functions/pipeline') .expect(400, { - error: 'Pass step by querystring is required', + error: 'Invalid pipeline configuration', + details: ['instance requires property "steps"'], }, done); }); }); @@ -321,7 +322,13 @@ describe('PUT /functions/pipeline', () => { describe('when step does not exists', () => { it('should return a not found request', (done) => { request(routes) - .put('/functions/pipeline?steps[0]=backstage/not-found') + .put('/functions/pipeline') + .send({ steps: [ + { + namespace: 'backstage', + id: 'not-found', + }, + ] }) .expect(404, { error: 'Code \'backstage/not-found\' is not found', }, done); @@ -331,8 +338,22 @@ describe('PUT /functions/pipeline', () => { describe('when step use two steps', () => { it('should return a result', (done) => { request(routes) - .put('/functions/pipeline?steps[0]=backstage/step1&steps[1]=backstage/step2') - .send({ x: 1 }) + .put('/functions/pipeline') + .send({ + steps: [ + { + namespace: 'backstage', + id: 'step1', + }, + { + namespace: 'backstage', + id: 'step2', + }, + ], + payload: { + x: 1, + }, + }) .expect(200, { x: 200 }, done); }); });