diff --git a/README.md b/README.md index fa6c0d8..a79a6db 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # ![Bolt Icon](docs/images/flash.png) Bolt Compiler -[![Build Status](https://travis-ci.org/firebase/bolt.svg?branch=master)](https://travis-ci.org/firebase/bolt) +[![Build Status](https://travis-ci.org/FirebaseExtended/bolt.svg?branch=master)](https://travis-ci.org/FirebaseExtended/bolt) [![NPM Version](https://badge.fury.io/js/firebase-bolt.svg)](https://npmjs.org/package/firebase-bolt) [![NPM Downloads](http://img.shields.io/npm/dm/firebase-bolt.svg)](https://npmjs.org/package/firebase-bolt) @@ -11,7 +11,7 @@ using with production applications. Otherwise, we'd love to have feedback from early adopters. You can email questions to firebase-talk@googlegroups.com using "Bolt" in the subject line, or post bugs -on our [Issue Tracker](https://github.com/firebase/bolt/issues). +on our [Issue Tracker](https://github.com/FirebaseExtended/bolt/issues). # Language Definition diff --git a/docs/language.md b/docs/language.md index 6c377bf..45c5fe4 100644 --- a/docs/language.md +++ b/docs/language.md @@ -31,6 +31,7 @@ A (user-defined) type statement describes a value that can be stored in the Fire type MyType [extends BaseType] { property1: Type, property2: Type, + $wildcardProp: Type ... validate() { } @@ -55,6 +56,12 @@ to use any other character in a property name, you can enclose them in quotes (n that Firebase allows any character in a path *except* for `.`, `$`, `#`, `[`, `[`, `/`, or control characters). +When a type statement contains a wildcard property, i.e. a property starting with the +`$`-character, the value is allowed to have an arbitrary number of extra properties. When +there is no wildcard property, the value can only have the properties defined in the +type statement. A type can have at most one wildcard property. + + Built-in base types are also similar to JavaScript types: String - Character strings diff --git a/samples/issue-212.bolt b/samples/issue-212.bolt new file mode 100644 index 0000000..1fe1fc4 --- /dev/null +++ b/samples/issue-212.bolt @@ -0,0 +1,40 @@ +type MyType { + num: Number; + str: String; + any: Any; + $extras: String; +} + +type AnyMap { + $extras: Any; +} + +type OtherType extends AnyMap { + name: String; +} + +type WithExtras extends T { + $extras: Any; +} + +type AnotherType { + name: String; +} + + +path /path is MyType { + read() { true } + write() { true } +} + +path /other is OtherType { + read() { true } +} + +path /another is AnotherType { + read() { true } +} + +path /anotherWithExtras is WithExtras { + read() { true } +} diff --git a/samples/issue-212.json b/samples/issue-212.json new file mode 100644 index 0000000..95bed69 --- /dev/null +++ b/samples/issue-212.json @@ -0,0 +1,45 @@ +{ + "rules": { + "path": { + ".validate": "newData.hasChildren(['num', 'str', 'any'])", + "num": { + ".validate": "newData.isNumber()" + }, + "str": { + ".validate": "newData.isString()" + }, + "any": { + ".validate": "true" + }, + "$extras": { + ".validate": "newData.isString()" + }, + ".read": "true", + ".write": "true" + }, + "other": { + ".validate": "newData.hasChildren(['name'])", + "name": { + ".validate": "newData.isString()" + }, + ".read": "true" + }, + "another": { + ".validate": "newData.hasChildren(['name'])", + "name": { + ".validate": "newData.isString()" + }, + "$other": { + ".validate": "false" + }, + ".read": "true" + }, + "anotherWithExtras": { + ".validate": "newData.hasChildren(['name'])", + "name": { + ".validate": "newData.isString()" + }, + ".read": "true" + } + } +} diff --git a/src/rules-generator.ts b/src/rules-generator.ts index 25de82d..9b5f947 100644 --- a/src/rules-generator.ts +++ b/src/rules-generator.ts @@ -448,6 +448,7 @@ export class Generator { let wildProperties = 0; Object.keys(schema.properties).forEach((propName) => { if (propName[0] === '$') { + delete validator.$other; wildProperties += 1; if (INVALID_KEY_REGEX.test(propName.slice(1))) { this.fatal(errors.invalidPropertyName + propName); @@ -467,7 +468,7 @@ export class Generator { extendValidator( validator[propName], this.ensureValidator(propType)); }); - if (wildProperties > 1 || wildProperties === 1 && requiredProperties.length > 0) { + if (wildProperties > 1) { this.fatal(errors.invalidWildChildren); } @@ -478,7 +479,11 @@ export class Generator { } // Disallow $other properties by default - if (hasProps) { + let hasWildProps = Object.keys(validator).some((v) => { + return v[0] === '$'; + }); + + if (hasProps && !hasWildProps) { validator['$other'] = {}; extendValidator( validator['$other'], {'.validate': ast.boolean(false)}); diff --git a/src/test/sample-files.ts b/src/test/sample-files.ts index 8dab860..ef9aeca 100644 --- a/src/test/sample-files.ts +++ b/src/test/sample-files.ts @@ -13,6 +13,7 @@ export let samples = [ "issue-169", "issue-232", "issue-97", + "issue-212", "mail", "map-scalar", "multi-update",