Skip to content

updated joi, express #159

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,4 @@ typings/

# webstorm files
.idea/
globalConfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"type: "object",
"properties": {
"name": { "type": "string" },
"age": { "type": "number" }
},
"required": true,
"additionalProperties": false
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"type": "object",
"properties": {
"name": { "type": "string" },
"age": { "type": "number" }
},
"required": true,
"additionalProperties": false
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"type": "object",
"properties": {
"name": {"type": "string", "nullable": false},
"age": {"type": "integer"}
},
"required": ["foo"],
"additionalProperties": false
}
24 changes: 24 additions & 0 deletions infra/__tests__/validate/auxiliary-files/yamls/broken.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
services:
firstService:
image: some-image:master
labels:
- "codefresh"
ports:
- 80:80
isBroken: true
environment:
NODE_ENV: development

secondService:
image: some-image2:master
labels:
- "codefresh"
ports:
- 90:90
environment:
NODE_ENV: production

brokenPart:
part1:
- hereIsMistake:
mistake
9 changes: 9 additions & 0 deletions infra/__tests__/validate/auxiliary-files/yamls/correct.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
services:
firstService:
image: first-image:master
labels:
- "codefresh"
ports:
- 80:80
environment:
NODE_ENV: development
81 changes: 81 additions & 0 deletions infra/__tests__/validate/validate.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
const { MongoMemoryServer } = require('mongodb-memory-server');
const mongoClient = require('../../mongo');
const { Joi, createSchema } = require('../../validation');
const { ValidationError } = require('joi');
const { readFileSync } = require('fs');
const { ObjectId } = require('mongodb');

describe('testing validation module', () => {
it('ObjectId', async () => {
const dbName = 'users';
const mongods = [await MongoMemoryServer.create({
instance: {
port: 27407,
dbName,
},
binary: { version: '4.2.0' },
}), await MongoMemoryServer.create({
instance: {
port: 27607,
dbName,
},
binary: { version: '6.0.14' },
})];

// eslint-disable-next-line no-restricted-syntax
for (const mongod of mongods) {
const uri = mongod.getUri();
// eslint-disable-next-line no-await-in-loop
await mongoClient.init({
mongo: {
uri,
dbName,
},
});
const users = mongoClient.collection('users');
const mockUser = { name: 'John' };
// eslint-disable-next-line no-await-in-loop
const id = (await users.insertOne(mockUser)).insertedId.toString();
expect(() => Joi.assert(id, Joi.objectId())).not.toThrow();
}
expect(() => Joi.assert('someRandomId', Joi.objectId())).toThrow(ValidationError);
expect(() => Joi.assert(12345678, Joi.objectId())).toThrow(ValidationError);
});

it('customYamlSchema', () => {
const correctDoc = readFileSync('infra/__tests__/validate/auxiliary-files/yamls/correct.yaml', { encoding: 'utf8' });
const brokenDoc = readFileSync('infra/__tests__/validate/auxiliary-files/yamls/broken.yaml', { encoding: 'utf8' });
expect(() => Joi.assert(correctDoc, Joi.yaml())).not.toThrow();
expect(() => Joi.assert(brokenDoc, Joi.yaml())).toThrow(ValidationError);
});

it('jsonSchemaString', () => {
const validSchema = readFileSync('infra/__tests__/validate/auxiliary-files/jsons/validJsonSchema.json', { encoding: 'utf8' });
const invalidSchema = readFileSync('infra/__tests__/validate/auxiliary-files/jsons/invalidJsonSchema.json', { encoding: 'utf8' });
const brokenJson = readFileSync('infra/__tests__/validate/auxiliary-files/jsons/brokenJson.json', { encoding: 'utf8' });
expect(() => Joi.assert(validSchema, Joi.jsonSchemaString())).not.toThrow();
expect(() => Joi.assert(invalidSchema, Joi.jsonSchemaString())).toThrow(ValidationError);
expect(() => Joi.assert(brokenJson, Joi.jsonSchemaString())).toThrow(ValidationError);
});

it('createSchema', async () => {
const schema = createSchema({
id: Joi.objectId().required(),
name: Joi.string().required(),
}, 'type', { argo: { spec: Joi.object({ testField: Joi.boolean() }).required() } });
const invalidValue = { id: '123' };
const validValue = { id: '663cdd877065d5748d788886', name: 'Somename', type: 'argo', spec: { testField: false } };
const validateCheck = async (value) => {
try {
const res = await schema.validate(value);
return { value: res };
} catch (error) {
throw error.message;
}
};
await expect(validateCheck(invalidValue)).rejects.toEqual('must be a valid ObjectId');
await expect(validateCheck(validValue)).resolves.toEqual({ value: { ...validValue, id: new ObjectId(validValue.id) } });
await expect(schema.validateField('id', invalidValue.id)).rejects.toEqual(new Error('must be a valid ObjectId'));
await expect(schema.validateField('id', validValue.id).value).toEqual(new ObjectId(validValue.id));
});
});
8 changes: 3 additions & 5 deletions infra/express.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@

const Promise = require('bluebird');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please return

const express = require('express');
const compression = require('compression');
const bodyParser = require('body-parser');
const methodOverride = require('method-override');
const cookieParser = require('cookie-parser');
const morgan = require('morgan');
const monitor = require('@codefresh-io/cf-monitor');
const { newDomainMiddleware } = require('@codefresh-io/http-infra');
const { openapi } = require('@codefresh-io/cf-openapi');
const CFError = require('cf-errors');
const cookieParser = require('cookie-parser');

class Express {
constructor() {
Expand Down Expand Up @@ -59,9 +57,9 @@ class Express {
app.use(compression());

openapi.endpoints().registerUtilityMiddleware(app);
app.use(bodyParser.json());
app.use(express.json());

app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.urlencoded({ extended: true }));
app.use(methodOverride());

if (this.config.httpLogger) {
Expand Down
27 changes: 27 additions & 0 deletions infra/validation-extensions/joi-objectid-extension.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
const { ObjectId } = require('mongodb');

const joiObjectId = {
type: 'objectId',
messages: { objectId: 'must be a valid ObjectId' },
coerce(value) {
if (!value) {
return { value };
}

// Convert string to object ID
if (typeof value === 'string' && value.match(/^[0-9a-fA-F]{24}$/)) {
return { value: new ObjectId(value) };
}

return { value };
},
validate(value, helpers) {
if (!(value instanceof ObjectId)) {
const errors = helpers.error('objectId');
return { value, errors };
}
return { value };
},
};

module.exports = joiObjectId;
54 changes: 32 additions & 22 deletions infra/validation.js
Original file line number Diff line number Diff line change
@@ -1,50 +1,59 @@
const _ = require('lodash');
const Promise = require('bluebird');
const Joi = require('joi').extend(require('@wegolook/joi-objectid'));
const Joi = require('joi');
const YAML = require('js-yaml');
const Ajv = require('ajv');

Joi.objectId = Joi.extend(require('./validation-extensions/joi-objectid-extension')).objectId;

const customYamlJoi = Joi.extend((joi) => ({ // eslint-disable-line
name: 'yaml',
language: {},
pre(value, state, options) {
type: 'yaml',
base: Joi.string(),
prepare(value, helpers) {
try {
YAML.safeLoad(value);
return value;
return { value };
} catch (err) {
return this.createError('yaml', { v: value }, state, options);
return { errors: helpers.error('yaml', { v: value }) };
}
},
rules: [],
}));
Joi.yaml = customYamlJoi.yaml;

const customJsonSchemaStringJoi = Joi.extend((joi) => ({ // eslint-disable-line
base: Joi.string(),
name: 'jsonSchemaString',
language: { pre: 'is not a valid JSON Schema: {{err}}' },
pre(value, state, options) {
type: 'jsonSchemaString',
messages: { pre: 'is not a valid JSON Schema: {{err}}' },
prepare(value, helpers) {
try {
const ajv = new Ajv(); // options can be passed, e.g. {allErrors: true}
const ajv = new Ajv({ validateSchema: true }); // options can be passed, e.g. {allErrors: true}
ajv.compile(JSON.parse(value));
return value;
return { value };
} catch (err) {
return this.createError('jsonSchemaString.pre', { v: value, err: err.toString().replace('Error: schema is invalid: ', '') }, state, options); // eslint-disable-line max-len
return { errors: helpers.error('jsonSchemaString.pre', { v: value, err: err.toString().replace('Error: schema is invalid: ', '') }) }; // eslint-disable-line max-len
}
},
rules: [],
}));
Joi.jsonSchemaString = customJsonSchemaStringJoi.jsonSchemaString;

const throwValidationError = (error) => {
const validationErrorMessage = _.get(error, 'details[0].message');
return Promise.reject(validationErrorMessage ? new Error(validationErrorMessage) : error);
};


function validateField(field, val, options = { optional: false }) {
if (val === undefined && _.get(options, 'optional')) {
return Promise.resolve();
}

return Joi.reach(this, field).validate(val, { language: { key: `"${field}" ` } });
const extractedSchema = this.extract(field);
const { error, value } = extractedSchema.validate(val, { messages: { key: `"${field}" ` } });
if (error) {
return throwValidationError(error, field);
}
return { error, value };
}


function validateFields(partialObject, options) {
return Promise.reduce(Object.keys(partialObject), (ret, field) => this.validateField(field, partialObject[field], options), 0);
}
Expand Down Expand Up @@ -73,9 +82,11 @@ function _wrapValidate(validate) {
return function (schema) { // eslint-disable-line
return Promise.resolve()
.then(validate.bind(this, schema))
.catch((err) => {
const validationErrorMessage = _.get(err, 'details[0].message');
return Promise.reject(validationErrorMessage ? new Error(validationErrorMessage) : err);
.then(({ error, value }) => {
if (error) {
return throwValidationError(error);
}
return Promise.resolve(value);
});
};
}
Expand All @@ -92,8 +103,7 @@ function _createSchema(baseSchema, discriminator, childSchemas) {
.when(discriminator, { is: discriminatorVal, then: valueSchema });
});
});

return Joi.object(baseSchema)
.keys({ [discriminator]: Joi.valid(Object.keys(childSchemas)).required() })
.keys({ [discriminator]: Joi.valid(...Object.keys(childSchemas)).required() })
.keys(extendedSchema);
}
16 changes: 6 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@codefresh-io/service-base",
"version": "5.0.4",
"version": "6.0.0",
"main": "index.js",
"description": "",
"bin": {
Expand Down Expand Up @@ -38,17 +38,15 @@
"@codefresh-io/eventbus": "^2.0.0",
"@codefresh-io/http-infra": "^1.8.15",
"@codefresh-io/internal-service-config": "^1.0.3",
"@wegolook/joi-objectid": "^2.4.0",
"ajv": "^6.10.0",
"bluebird": "^3.5.3",
"body-parser": "^1.19.2",
"cf-errors": "^0.1.15",
"cf-logs": "^1.1.26",
"chai": "4.3.10",
"compression": "^1.7.4",
"cookie-parser": "^1.4.4",
"express": "^4.17.3",
"joi": "^13.0.2",
"cookie-parser": "^1.4.6",
"express": "^4.19.2",
"joi": "^17.13.0",
"js-yaml": "^3.13.1",
"lodash": "4.17.21",
"method-override": "^3.0.0",
Expand All @@ -57,9 +55,7 @@
"node-uuid": "^1.4.8",
"proxyquire": "^1.8.0",
"queue": "^4.2.1",
"redis": "^3.1.0",
"request": "2.88.2",
"request-promise": "4.2.6"
"redis": "^3.1.0"
},
"devDependencies": {
"@shelf/jest-mongodb": "^4.2.0",
Expand All @@ -70,6 +66,6 @@
"eslint-plugin-mocha": "^4.12.1",
"jest": "^29.7.0",
"mocha": "^8.2.1",
"mongodb-memory-server": "^9.1.6"
"mongodb-memory-server": "^9.2.0"
}
}
Loading