Skip to content
This repository was archived by the owner on Mar 17, 2025. It is now read-only.

Add migrate command to convert JSON rules to Bolt language #93

Open
wants to merge 41 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
a1afc0b
Stub for JSON to Bolt decoder.
Nov 17, 2015
8291d63
Build basic Bolt expressions from JSON.
Nov 19, 2015
302ddd8
Handle simple type validations.
Nov 20, 2015
cf8e5d4
Able to parse JSON with comments.
Nov 20, 2015
cd7be36
Merge branch 'master' into koss-migration
Nov 21, 2015
36e0ee0
Checkpoint - passing.
Nov 21, 2015
d42c5bf
Tests - some failing - decoded samples.
Nov 21, 2015
6fe9d73
Update compare tool to check decoding.
Nov 21, 2015
b1b047f
Support index() in decoder.
Nov 23, 2015
67a7989
Allow decode to convert right-associative to left-associative boolean…
Nov 23, 2015
623976f
Merge branch 'master' into koss-migration
Nov 23, 2015
c6e0851
Merge branch 'master' into koss-migration
Nov 23, 2015
fe1f79a
Merge branch 'master' into koss-migration
Nov 24, 2015
fe16414
Failing tests for decoding.
Nov 24, 2015
53bb110
Merge branch 'master' into koss-migration
Nov 25, 2015
391ae73
Additional string method precedence tests.
Nov 25, 2015
cc2edb7
Basic nest paths generated.
Nov 25, 2015
46916b3
Fix bug in sibling path.
Nov 26, 2015
898523d
Initial AST matching.
Nov 26, 2015
6c8468e
More matching tests.
Nov 26, 2015
eac5c5e
And and or expression flattened to one level instead of binary.
Nov 26, 2015
f80c7ee
Simple equivalence.
Nov 27, 2015
cfcb359
Commutative operators matching.
Nov 30, 2015
7b16e57
Parametric expression matching.
Nov 30, 2015
84194e0
Permutation helper.
Dec 1, 2015
8f00ddb
Checkpoint - re-write rules.
Dec 1, 2015
a0f1827
Permutation over a collection.
Dec 5, 2015
0d714cf
Require free variable in boolean expression patterns.
Dec 13, 2015
5cca0e1
Add expression transformation rules to decoder.
Dec 13, 2015
4878149
Meaning of root is prior(root).
Dec 13, 2015
861c4e5
Fix rule typo.
Dec 13, 2015
64f9fc8
Merge branch 'master' into koss-migration
Dec 13, 2015
3e6899c
Simplify not exists rule.
Dec 15, 2015
0892ec3
Map hasChild to null check.
Dec 16, 2015
84d9a5a
Checkpoint - rewrite function.
Dec 19, 2015
c4622d1
MultiFunctor for Rewriters
Dec 19, 2015
3bf87e4
Fix expression simplification hanging bug.
Dec 20, 2015
6ba0dde
Fix unary op replacement bug.
Dec 20, 2015
9bc9ab7
Re-write rule tests.
Dec 20, 2015
23725b1
Fix decoding of OR exp beneath AND exp.
Dec 21, 2015
e7e163e
Iterator interface.
Dec 21, 2015
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
148 changes: 105 additions & 43 deletions bin/firebase-bolt
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,16 @@ var pkg = require('../package.json');

var VERSION_STRING = "Firebase Bolt v" + pkg.version;

var commandName = 'bolt';

var opts = {
boolean: ['version', 'help'],
string: ['output'],
alias: {
'v': 'version',
'f': 'functions',
'h': 'help',
'o': 'output',
'v': 'version',
},
unknown: function(flag) {
if (flag[0] == '-') {
Expand All @@ -42,6 +45,33 @@ var opts = {
}
};

var commands = {
migrate: function(args) {
if (args._.length != 2) {
log("Missing JSON file name to migrate to bolt file.");
process.exit(1);
}
var functionsFileName = getInputFileName(args.functions, 'bolt');
var functions;
if (functionsFileName !== undefined) {
readInputFile(functionsFileName, 'Bolt', function(boltText) {
try {
var symbols = bolt.parse(boltText);
functions = symbols.functions;
} catch (e) {
log(e.message, e.line, e.column);
process.exit(1);
}
});
}
var inputFileName = getInputFileName(args._[1], 'json');
var outputFileName = getOutputFileName(args.output, inputFileName, 'bolt');
readInputFile(inputFileName, 'JSON', function(jsonData) {
writeOutputFile(outputFileName, bolt.decodeRules(jsonData, functions));
});
}
};

function main() {
var args = parseArgs(process.argv.slice(2), opts);

Expand All @@ -60,73 +90,105 @@ function main() {
usage(0);
}

// command file
if (args._.length >= 1 && commands[args._[0]] !== undefined) {
commandName += '-' + args._[0];
commands[args._[0]](args);
return;
}

if (args._.length > 1) {
log("Can only compile a single file.");
usage(1);
}

// Read Bolt file from stdin
if (args._.length === 0) {
if (process.stdin.isTTY) {
log("Type in a Bolt file terminated by CTRL-D.");
}
readFile(process.stdin, function(data) {
if (args.output !== undefined) {
writeTranslation(util.ensureExtension(args.output, 'json'), data);
} else {
console.log(translateRules(data));
}
});
return;
var inputFileName = getInputFileName(args._[0], 'bolt');
var outputFileName = getOutputFileName(args.output, inputFileName, 'json');
readInputFile(inputFileName, 'Bolt', function(boltData) {
writeOutputFile(outputFileName, translateRules(boltData));
});
}

function getInputFileName(name, extension) {
// Read file from stdin
if (name === undefined) {
return undefined;
}
return util.ensureExtension(name, extension);
}

// Read Bolt file and write json file.
var inFile = util.ensureExtension(args._[0], bolt.FILE_EXTENSION);
var outFile;
if (args.output) {
outFile = util.ensureExtension(args.output, 'json');
function getOutputFileName(name, inputFileName, extension) {
if (name === undefined) {
if (inputFileName === undefined) {
return undefined;
}
name = util.replaceExtension(inputFileName, extension);
} else {
outFile = util.replaceExtension(inFile, 'json');
name = util.ensureExtension(name, extension);
}
if (inFile === outFile) {
log("Cannot overwrite input file: " + inFile);
log("(Did you mean '" + util.replaceExtension(inFile, 'bolt') + "'?)");
if (name === inputFileName) {
log("Cannot overwrite input file: " + inputFileName);
log("(Did you mean '" +
util.replaceExtension(name, extension == 'json' ? 'bolt' : 'json') +
"'?)");
process.exit(1);
}
fs.readFile(inFile, 'utf8', function(err, data) {
return name;
}

function readInputFile(name, kind, callback) {
if (name === undefined) {
if (process.stdin.isTTY) {
log("Type in a " + kind + " file terminated by CTRL-D.");
}
readStream(process.stdin, callback);
return;
}

fs.readFile(name, 'utf8', function(err, data) {
if (err) {
log("Could not read file: " + inFile);
log("Could not read file: " + name);
process.exit(1);
}
writeTranslation(outFile, data);
callback(data);
});
}

function writeTranslation(outFile, data) {
log("Generating " + outFile + "...");
fs.writeFile(outFile, translateRules(data) + '\n', 'utf8', function(err2) {
if (err2) {
log("Could not write file: " + outFile);
process.exit(1);
}
});
function writeOutputFile(name, data) {
if (name === undefined) {
console.log(data + '\n');
} else {
log("Generating " + name + "...");
fs.writeFile(name, data + '\n', 'utf8', function(err) {
if (err) {
log("Could not write file: " + outputFile);
process.exit(1);
}
});
}
}

function usage(code) {
var cmdName = process.argv[1].split('/').slice(-1);
console.error("Translate Firebase Bolt file into JSON rules format.\n");
console.error("Translate Firebase Bolt file into JSON rules format");
console.error(" (or use migrate sub-command to generate a Bolt file equivalent\n" +
" of a Firebase JSON rule set).\n");

console.error(" Usage: " + cmdName + " [options] [file]\n");
console.error(" Usage: " + cmdName + " [options] [file[.bolt]]");
console.error(" " + cmdName + " [options] migrate [file[.json]]\n");

console.error(" Examples: " + cmdName + " myapp.bolt --output rules.json");
console.error(" " + cmdName + " < myapp.bolt > rules.json");
console.error(" " + cmdName + " myapp");
console.error(" (myapp.bolt => myapp.json)\n");
console.error(" Examples: " + cmdName + " rules.bolt --output rules.json");
console.error(" " + cmdName + " < rules.bolt > rules.json");
console.error(" " + cmdName + " rules");
console.error(" (rules.bolt => rules.json)");
console.error(" " + cmdName + " migrate [--functions file] rules.json");
console.error(" (rules.json => rules.bolt)\n");

console.error(" Options:\n");
console.error(util.formatColumns(4, [
["-f --functions file", "Include file of top level Bolt functions (for migrate)."],
["-h --help", "Display this helpful message."],
["-o --output file", "Output to file.json."],
["-o --output file", "Output to file."],
["-v --version", "Display Firebase Bolt version."],
[]
]).join('\n'));
Expand All @@ -136,7 +198,7 @@ function usage(code) {

main();

function readFile(f, callback) {
function readStream(f, callback) {
var input = "";

f.setEncoding('utf8');
Expand Down Expand Up @@ -172,7 +234,7 @@ function translateRules(input) {
}

function log(message, line, column) {
var parts = ['bolt'];
var parts = [commandName];
if (line) {
util.extendArray(parts, [line, column]);
}
Expand Down
12 changes: 10 additions & 2 deletions gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,16 @@ var TS_SOURCES = ['src/*.ts',
'src/test/*.ts'];

// Subset of tests required for 'gulp test'.
var TEST_FILES = ['lib/test/generator-test.js', 'lib/test/parser-test.js',
'lib/test/ast-test.js', 'lib/test/util-test.js', 'lib/test/cli-test.js'];
var TEST_FILES = [
'lib/test/ast-test.js',
'lib/test/cli-test.js',
'lib/test/decoder-test.js',
'lib/test/generator-test.js',
'lib/test/matcher-test.js',
'lib/test/parser-test.js',
'lib/test/permutation-test.js',
'lib/test/util-test.js'
];

// Ignore ts-compile errors while watching (but not in normal builds).
var watching = false;
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
"minimist": "^1.2.0",
"mocha": "^2.2.5",
"node-uuid": "^1.4.3",
"promise": "^7.0.4"
"promise": "^7.0.4",
"strip-json-comments": "^2.0.0"
}
}
8 changes: 4 additions & 4 deletions samples/chat.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
".validate": "newData.val().length > 0 && newData.val().length <= 32"
},
"creator": {
".validate": "newData.isString() && (auth != null && newData.val() == auth.uid)"
".validate": "newData.isString() && auth != null && newData.val() == auth.uid"
},
"members": {
"$key2": {
Expand All @@ -21,7 +21,7 @@
"$other": {
".validate": "false"
},
".write": "data.val() == null && (auth != null && $key2 == auth.uid)"
".write": "data.val() == null && auth != null && $key2 == auth.uid"
}
},
"$other": {
Expand All @@ -37,15 +37,15 @@
"$postid": {
".validate": "newData.hasChildren(['from', 'message', 'created'])",
"from": {
".validate": "newData.isString() && (auth != null && newData.val() == auth.uid)"
".validate": "newData.isString() && auth != null && newData.val() == auth.uid"
},
"message": {
".validate": "newData.isString() && newData.val().length > 0 && newData.val().length <= 140"
},
"$other": {
".validate": "false"
},
".write": "data.val() == null && (root.child('rooms').child($roomid).child('members').child(auth.uid).val() != null && !(root.child('rooms').child($roomid).child('members').child(auth.uid).child('isBanned').val() == true))",
".write": "data.val() == null && root.child('rooms').child($roomid).child('members').child(auth.uid).val() != null && !(root.child('rooms').child($roomid).child('members').child(auth.uid).child('isBanned').val() == true)",
"created": {
".validate": "newData.isNumber() && newData.val() == (data.val() == null ? now : data.val())"
}
Expand Down
6 changes: 6 additions & 0 deletions samples/decoded.bolt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Bolt file auto-generated from JSON file.
path /users/$uid {
read() = auth != null;
write() = auth != null && auth.uid == $uid;
}
path /users/$uid/name is String;
13 changes: 13 additions & 0 deletions samples/decoded.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"rules": {
"users": {
"$uid": {
".read": "auth != null",
".write": "auth != null && auth.uid == $uid",
"name": {
".validate": "newData.isString()"
}
}
}
}
}
4 changes: 2 additions & 2 deletions samples/mail.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"$userid": {
"inbox": {
"$msg": {
".validate": "newData.hasChildren(['from', 'to', 'message']) && data.val() == null && (auth != null && auth.uid == newData.child('from').val())",
".validate": "newData.hasChildren(['from', 'to', 'message']) && data.val() == null && auth != null && auth.uid == newData.child('from').val()",
"from": {
".validate": "newData.isString()"
},
Expand All @@ -23,7 +23,7 @@
},
"outbox": {
"$msg": {
".validate": "newData.hasChildren(['from', 'to', 'message']) && data.val() == null && (auth != null && auth.uid == newData.child('from').val())",
".validate": "newData.hasChildren(['from', 'to', 'message']) && data.val() == null && auth != null && auth.uid == newData.child('from').val()",
"from": {
".validate": "newData.isString()"
},
Expand Down
2 changes: 1 addition & 1 deletion samples/user-security.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"$message_id": {
".validate": "newData.hasChildren(['user', 'message', 'timestamp']) && data.val() == null",
"user": {
".validate": "newData.isString() && (auth != null && auth.uid == newData.val())"
".validate": "newData.isString() && auth != null && auth.uid == newData.val()"
},
"message": {
".validate": "newData.isString() && newData.val().length > 0 && newData.val().length < 50"
Expand Down
Loading