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

allow wildcard properties in type statement #235

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -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 [email protected] 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

7 changes: 7 additions & 0 deletions docs/language.md
Original file line number Diff line number Diff line change
@@ -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() { <validation expression> }
@@ -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
40 changes: 40 additions & 0 deletions samples/issue-212.bolt
Original file line number Diff line number Diff line change
@@ -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<T> 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<AnotherType> {
read() { true }
}
45 changes: 45 additions & 0 deletions samples/issue-212.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
}
9 changes: 7 additions & 2 deletions src/rules-generator.ts
Original file line number Diff line number Diff line change
@@ -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> 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> validator['$other'],
<Validator> {'.validate': ast.boolean(false)});
1 change: 1 addition & 0 deletions src/test/sample-files.ts
Original file line number Diff line number Diff line change
@@ -13,6 +13,7 @@ export let samples = [
"issue-169",
"issue-232",
"issue-97",
"issue-212",
"mail",
"map-scalar",
"multi-update",