mirror of
https://github.com/zulip/zulip.git
synced 2025-11-02 13:03:29 +00:00
Add a schema checking concept to the web app.
This starts the concept of a schema checker, similar to zerver/lib/validator.py on the server. We can use this to validate incoming data. Our server should filter most of our incoming data, but it's useful to have client-side checking to defend against things like upgrade regressions (i.e. what if we change the name of the field on the server side without updating all client uses).
This commit is contained in:
@@ -10,6 +10,7 @@
|
|||||||
"$": false,
|
"$": false,
|
||||||
"_": false,
|
"_": false,
|
||||||
"run_test": false,
|
"run_test": false,
|
||||||
|
"schema": false,
|
||||||
"jQuery": false,
|
"jQuery": false,
|
||||||
"Spinner": false,
|
"Spinner": false,
|
||||||
"Handlebars": false,
|
"Handlebars": false,
|
||||||
|
|||||||
59
frontend_tests/node_tests/schema.js
Normal file
59
frontend_tests/node_tests/schema.js
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
zrequire('schema');
|
||||||
|
|
||||||
|
run_test('basics', () => {
|
||||||
|
assert.equal(schema.check_string('x', 'fred'), undefined);
|
||||||
|
assert.equal(schema.check_string('x', [1,2]), 'x is not a string');
|
||||||
|
|
||||||
|
const fields = {
|
||||||
|
foo: schema.check_string,
|
||||||
|
bar: schema.check_string,
|
||||||
|
};
|
||||||
|
|
||||||
|
const check_rec = (val) => {
|
||||||
|
return schema.check_record('my_rec', val, fields);
|
||||||
|
};
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
check_rec({foo: 'apple', bar: 'banana'}),
|
||||||
|
undefined
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
check_rec('bogus'),
|
||||||
|
'my_rec is not a record'
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
check_rec({foo: 'apple'}),
|
||||||
|
'in my_rec bar is missing'
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
check_rec({}),
|
||||||
|
'in my_rec bar is missing, foo is missing'
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
check_rec({foo: 'apple', bar: 42}),
|
||||||
|
'in my_rec bar is not a string'
|
||||||
|
);
|
||||||
|
|
||||||
|
const check_array = (val) => {
|
||||||
|
return schema.check_array('lst', val, schema.check_string);
|
||||||
|
};
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
check_array(['foo', 'bar']),
|
||||||
|
undefined
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
check_array('foo'),
|
||||||
|
'lst is not an array'
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
check_array(['foo', 3]),
|
||||||
|
'in lst we found an item where item is not a string'
|
||||||
|
);
|
||||||
|
});
|
||||||
65
static/js/schema.js
Normal file
65
static/js/schema.js
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
var schema = (function () {
|
||||||
|
|
||||||
|
var exports = {};
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
These runtime schema validators are defensive and
|
||||||
|
should always succeed, so we don't necessarily want
|
||||||
|
to translate these. These are very similar to server
|
||||||
|
side validators in zerver/lib/validator.py.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
exports.check_string = function (var_name, val) {
|
||||||
|
if (!_.isString(val)) {
|
||||||
|
return var_name + ' is not a string';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.check_record = function (var_name, val, fields) {
|
||||||
|
if (!_.isObject(val)) {
|
||||||
|
return var_name + ' is not a record';
|
||||||
|
}
|
||||||
|
|
||||||
|
var field_results = _.map(fields, function (f, field_name) {
|
||||||
|
if (val[field_name] === undefined) {
|
||||||
|
return field_name + ' is missing';
|
||||||
|
}
|
||||||
|
return f(field_name, val[field_name]);
|
||||||
|
});
|
||||||
|
|
||||||
|
var msg = _.filter(field_results).sort().join(', ');
|
||||||
|
|
||||||
|
if (msg) {
|
||||||
|
return 'in ' + var_name + ' ' + msg;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.check_array = function (var_name, val, checker) {
|
||||||
|
if (!_.isArray(val)) {
|
||||||
|
return var_name + ' is not an array';
|
||||||
|
}
|
||||||
|
|
||||||
|
var msg;
|
||||||
|
|
||||||
|
_.find(val, function (item) {
|
||||||
|
var res = checker('item', item);
|
||||||
|
|
||||||
|
if (res) {
|
||||||
|
msg = res;
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (msg) {
|
||||||
|
return 'in ' + var_name + ' we found an item where ' + msg;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return exports;
|
||||||
|
|
||||||
|
}());
|
||||||
|
if (typeof module !== 'undefined') {
|
||||||
|
module.exports = schema;
|
||||||
|
}
|
||||||
@@ -54,6 +54,7 @@ enforce_fully_covered = {
|
|||||||
'static/js/reactions.js',
|
'static/js/reactions.js',
|
||||||
'static/js/recent_senders.js',
|
'static/js/recent_senders.js',
|
||||||
'static/js/rtl.js',
|
'static/js/rtl.js',
|
||||||
|
'static/js/schema.js',
|
||||||
'static/js/scroll_util.js',
|
'static/js/scroll_util.js',
|
||||||
'static/js/search_suggestion.js',
|
'static/js/search_suggestion.js',
|
||||||
# Removed because we're migrating code from uncovered other settings pages to here.
|
# Removed because we're migrating code from uncovered other settings pages to here.
|
||||||
|
|||||||
@@ -929,6 +929,7 @@ JS_SPECS = {
|
|||||||
'templates/compiled.js',
|
'templates/compiled.js',
|
||||||
'js/feature_flags.js',
|
'js/feature_flags.js',
|
||||||
'js/loading.js',
|
'js/loading.js',
|
||||||
|
'js/schema.js',
|
||||||
'js/util.js',
|
'js/util.js',
|
||||||
'js/keydown_util.js',
|
'js/keydown_util.js',
|
||||||
'js/lightbox_canvas.js',
|
'js/lightbox_canvas.js',
|
||||||
|
|||||||
Reference in New Issue
Block a user