Skip to content

stamat/jules

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

41 Commits
 
 
 
 
 
 
 
 

Repository files navigation

JULES - JavaScript JSON Schema Validator

JULES - JavaScript JSON Schema Validator (yes... another one...)

  • Easy to use
  • Easy to extend
  • Supports both JSON Schema draft-04 and draft-03 (except extends)

About

An easily extensible, simplest possible JavaScript JSON validator written in declarative style which enabled easy extending and pay as you go execution, minimizing the number of functions executed and conditions passed while traversing through schema.
This way it is an open platform for experimenting with validation rules and enabling easier creation of a future schema for all of us to benefit from.
It supports both draft-04 and draft-03 keywords. (except extends, whitch is soon to come) One Bad Motherfucker...

Note that this is a client side schema validator, and not a Node.js module. Well not yet... Made exclusively for validating form fields and JSON requests. (That explains the purely done object comparison and error reports for complex schemas, and missing extends...)

Usage example

Usage is quite simple. You have some value that is any type (null, boolean, integer, number(float or integer), string, array, object) and you have a [JSON Schema][http://json-schema.org]. All you have to do is:

var result = jules.validate(value, schema);

Errors
It returns true or false depending on success of the validation. By default, validation will stop by the first error and return false. If you want to get an error message you can use:

var error = jules.errors.getLast();

You can change this option, so the validator will always pass through whole schema and it will generate all errors. The error reports do not look nice if you dont use id in every schema, but this issue will be fixed.

jules.aggregate_errors = true; //Collect all the errors. False by default;
	
var result = jules.validate(value, schema);
var errors = jules.errors; //array of error messages

Error messages are generated by jules.generateErrorMessage(value, i, schema), where i is a current keyword of the schema. You will find that almost all of the functions recieve these arguments.You can easily override this funcion and suite it to your liking. Default error message for each schema keyword is: [schema.id]: Invalid schema_keyword. If you want to have custom error messages, you can either use a callback or you can supply list of templates for each keyword error messages.

jules.error_messages = {
	"type": "{{schema_id}} - Invalid {{keyword}}. {{value}} should be {{key_val}} type.",
	"minimum": "{{schema_id}} - Invalid {{keyword}}. Should be lager than {{key_val}}"
	//...
}

Callbacks
Well, cause this validator comes only in a form of an object instance and it can't be used in threds/workers callbacks dont have much sense, and they are presented in a form of extensions. Something like build and teardown functions. If you want to capture the result only, or result and errors at the end of validation, the easiest way next to putting the validation function in an "if" clause or returning a value to a variable is:

jules.validator(value, schema, function(value, schema, result){/*...*/});

This "callback" is as same as onFinish, described below, but for that validation only.
There are six callbacks, for begining and ending of each step. onStart execute at the sole beginning of the validation, and onFinish, at the finish of the validaion. onEachSchema on the beginning of each schema that validator comes across, and onEachSchemaResult at the end of schema validation, withthe result and errors returned. Next are callbacks for beginning and finishing the validation of each schemma key-value field, onEachField and onEachFieldResult, having the value, keyword, schema and result as parameters. You can use the callbacks like this:

jules.onStart = function(value, schema){/*...*/};
jules.onFinish = function(value, schema, result){/*...*/};
jules.onEachSchema = function(value, schema){/*...*/};
jules.onEachSchemaResult = function(value, schema, result, errors){/*...*/};
jules.onEachField = function(value, keyword, schema){/*...*/};
jules.onEachFieldResult = function(value, keyword, schema, result){/*...*/};

Functions
There are only two functions that you should consider using. Well only one really, the one described above, for validating value against the schema.

var result = jules.validate(value, schema);

But if you want to validate a schema against a metaschema, you can pass schema as a value and metaschema as schema, using the same function. But there is a function that is a shortcut for doing that.

var valid = jules.validateSchema(schema (,metaschema));

You only need to pass schema, if that schema has $schema property of course, if not you can pass a string uri or localy stored metaschema as a metaschema argument.

Options
Two of three options are already described in Errors section of this chapter. The third one you can use if you intend to use this validator to validate simple schemas without detailed error reports. if you dont use id-s to uniquely identify your scopes, this validator uses CRC32 to uniquely identify them for you. This is a slow and almost unecessary solution used only for testing this validator and it is disabled by default. Three options and their default values are as follows:

	jules.dont_label = true;
	jules.error_messages = {};
	jules.aggregate_errors = false;

An example using JSON Schema draft 04:

//Here we have some values
var a = 4, 
	b = 'you@example.com', 
	c = [1,2,3,4], 
	d = [1,2,'f', 4],
	e = {foo:1, bar: 'baz'},
	f = {foo:1, bar: 'baz', qux: true};

//And here we have some schemas to validate the values above
var schema1 = {
	"type": "integer",
	"minimum": 1,
	"maximum": 5,
	"exclusiveMaximum": true,
	"enum": [1,2,3,4]
},

schema2 = {
	"type": "string",
	"format": "email",
	"minLength" : 8,
	"maxLength" : 64
},

schema3 = {
	"type": ["integer","string"],
	"format": "email",
	"minLength" : 8,
	"maxLength" : 64,
	"minimum": 1,
	"maximum": 5,
	"multipleOf": 2,
	"exclusiveMaximum": true,
	"oneOf":[{"type":"integer", "enum": [1,2,3,4]},
		{"type":"string", "pattern": "\.com"}]
},

schema4 = {
	"definitions": {
		"someIntegers": {
			"type": "integer",
			"minimum": 1,
			"maximum": 5,
			"exclusiveMaximum": true,
			"enum": [1,2,3,4]
		}
	},
	"type": ["array"],
	"maxItems" : 16,
	"uniqueItems": true,
	"items":{
		"$ref":"#definitions/someIntegers"
	}
},

schema5 = {
	"type": ["array"],
	"maxItems" : 16,
	"uniqueItems": true,
	"items":[schema1, schema1, {
		"type": "string",
		"maxLength": 1,
		"pattern": "f"
	}, schema1],
	
	"additionalItems": false
},

schema6 = {
	"definitions": {
		"someIntegers": schema1
	},
	"type": "object",
	"maxProperties" : 2,
	"properties": {
		"foo": {
			"$ref":"#definitions/someIntegers"
		}
	},
	"patternProperties": {
		"^ba[a-z]+": {
			"type": "string",
			"maxLength": 3
		}
	},
	"dependencies": {
		"foo": ["bar"]
	},
	"additionalProperties": false
},

schema7 = JSON.parse(JSON.stringify(schema6)); //clone
schema7.maxProperties = 3;
schema7.additionalProperties = {"type": "boolean"};

//Here we validate values against schemas using jules
console.log('Validate `a` against `schema 1`');
console.log(jules.validate(a, schema1)); //true

console.log('Validate `b` against `schema 2`');
console.log(jules.validate(b, schema2)); //true

console.log('Validate `a` against `schema 3`');
console.log(jules.validate(a, schema3)); //true

console.log('Validate `b` against `schema 3`');
console.log(jules.validate(b, schema3)); //true

console.log('Validate `c` against `schema 4`');
console.log(jules.validate(c, schema4)); //true

console.log('Validate `d` against `schema 4`');
console.log(jules.validate(d, schema4)); //false

console.log('Validate `d` against `schema 5`');
console.log(jules.validate(d, schema5)); //true

console.log('Validate `e` against `schema 6`');
console.log(jules.validate(e, schema6)); //true

console.log('Validate `f` against `schema 6`');
console.log(jules.validate(f, schema6)); //false

console.log('Validate `f` against `schema 7`');
console.log(jules.validate(f, schema7)); //true

An example using extensions:

var schema8 = {
	"type": "integer",
	"required": true,
	"min": 4,
	"max": 64,
	"if": {
		"condition": {
			"numberPattern": "/^4.?/"
		},
		"then": {
			"multipleOf": 2
		},
		"else": {
			"min": 9,
			"multipleOf": 3
		}
	}
},

schema9 = {
	"type": "any",
	"disallow": ["tesest", "boolean"],
	"min": [
		{
			"type": "integer",
			"value": 0,
			"exclusive": true
		},
		{
			"type": "string",
			"value": 8
		},
		{
			"value": 1
		}
	],
	
	"max": 64,
	"properties": {
		"/^ba/i" : {
			"type": "integer",
			"minimum": 1,
			"exclusiveMinimum": true
		},
		"foo": {
			"type": "string",
			"min": 2,
			"regex": "/[tes]{1,4}/i",
			"forbidden": ["tese", "tess"]
		}
	}
	
};

console.log('Validate `33` against `schema 8`');
console.log(jules.validate(33, schema8)); //true

console.log('Validate `44` against `schema 8`');
console.log(jules.validate(44, schema8)); //true

console.log('Validate `64` against `schema 8`');
console.log(jules.validate(64, schema8)); //false

console.log('Validate `{}` against `schema 9`');
console.log(jules.validate({}, schema9)); //false

console.log('Validate `{"foo":"test","bar": 4, "baz": 8}` against `schema 9`');
console.log(jules.validate({"foo":"test","bar": 4, "baz": 8}, schema9)); //true

console.log('Validate `{"foo":"tess","bar": 4, "baz": 8}` against `schema 9`');
console.log(jules.validate({"foo":"tess","bar": 4, "baz": 8}, schema9)); //false

Extending example

Every possible function of the validator can be overrided. Old validator funtions can be replaced or completely new validator functions can be added. There are two types of validator functions, data type independent and data type dependent. To each validator function, same arguments are always passed respectively: value, current schema keyword and current schema.

Type independent validators will execute no matter the type of the value if the function with the keyword name exist. Your function MUST return a boolean!

jules.validator.new_keyword = function(value, keyword, schema) {
	//define your validator here
	return true;
};

If you decide to store a schema under your new keyword, you can call schema validation inside your function in this way:

jules.validator.new_keyword = function(value, keyword, schema) {
	return jules._validate(value, schema[keyword](, aggregate_errors));
};

Aggregate errors argument is an optional boolean whitch function is described above.

Type dependent validators are defined in similar manner to type independent. As described above, there are several data types, a bit extended set then the regular javascript's: null, boolean, integer, number(foat or integer), string, array, object. If you want to make a new keyword only for, let's say array type of passed value, you can do it in this way:

jules.validator.array.new_keyword = function(value, keyword, schema) {
	//define your validator here
	return true;
};

Same goes for every other type.

You can also easiy extend the formats for strings and numbers supported by this schema. For now there are only time, uri, regex, email formats, but to add more is a piece of a cake. All format functions are called by format name, they receive only one argument "value" and return boolean if format validation passes or not.

jules.formats.new_format = function(value) {
	return /ab+c/i.test(value); //only an example
};

So this is all you need to know about extending. It is very simple. If a keyword doesnt appear validation function wont be called at all. If you dont like the speed of the current functions, or you would like to add new functionality to them, you can simply override them in the way described above. Of course before the sole validation processes.

Warnings

  • Be careful while using $ref, you can fall into an infinite loop.
  • You should know that getting external $ref-s is done synchronously!

Compatibility

The validator can validate schema defined by draft 04 and/or 03. That means that you will be able to mix different schemas, and you can extend the validator to support earlier or drafts yet to come, or even make custom keywords and rules for them or just renames. In a form of an experiment some custom keywords were added witch you might find unethical. But don't forget that you can easily change the code to fit your liking.
Extensions: forbidden, min, max, if, numberPattern or numberRegex, numberFormat, requiredProperies alias for d04 required, and properties are changed a bit. Look for explanations in chapter below. Schema for supported extension is yet to be written but you can start using the additional keywords described in the next chapter.

Supported schema keywords

NOTE: - non of the keywords are mandatory

LEGEND:
[d04] - only in official draft 04
[d03] - only in official draft 03, depreciated
[e] - ivartech custom extension

[1] Meta keywords

Keywords used to describe the schema and not really used in validation, with some obvious exceptions.

  • id {string:uri} - Unique identificator of the schema, in URI format. Listen up maggots, this one is important for $ref resolving. It's usage is RECOMMENDED but if not provided the schema wil be identified by a CRC32 with sorted parameters or $ref URI.

  • $schema {string:uri} - If you want to check if your JSON schema is valid supply the meta-schema URI in this property and call jules.validateSchema(your_json_schema). Guys who write JSON schema say it is RECOMMENDED to supply this property.

  • title {string} - Give your schema a fancy title, this property is far from mandatory. Make sure your titles are short and meaningful.

  • description {string} - Describe your schema here, of course this property is far from mandatory too. But if you are going to publish your schema, better put a description so others will have a notion of what is it for.

  • default {any} - Defines a default value used if instance is undefined or validation

[2] Schema related keywords - type independent

  • definitions {object[object:schema]} - This is an object that holds local labeled schemas which you will use a lot through your root schema. So to disable redundancy you can reference them from this object using "$ref": #/definitions/some_schema. You can see an example of using definitions in Usage chapter.

  • $ref {string:uri} - Instead of writting a schema again and again you can just reference already defined one. This keyword and it's value are used to reference self, local or external schema for validation of provided instance. If a schema doesnt have an id it will be assigned one via the refeference uri. The following example will give you a brief insight of how you can reference schemas:

    • Referencing self '$ref': '#' - The scope becomes schema itself without a fragment so the instance is validated against the schema again. Beware: of the infinite loop.
    • Referencing local schema '$ref': '#/definitions/positiveInteger' or for instance '$ref': '#anyOf/0' - The scope stays the same and the instance is validated against the schema which path is defined by a fragment. Note: fragments can be URL encoded.
    • Referencing external schema '$ref': 'http://example.com/schema' or '$ref': 'http://example.com/schema#definitions/positiveInteger' - the reference can have a scope and/or a fragment. Rootschema is loaded via the scope and instance is validated depending on the fragment. If there is no fragment the instance is validated against the external schema.
  • extends [n/a][d03] {string:uri} - Not available in this version. Similar as $ref but instead of validating instance againsts the schema, it extends the linked schema with properties of the container schema replacing the values of keywords if they match and then validates the instance against schema combined in such manner.

[3] All types - keywords that apply to all data types

  • type or allow[e] {string:type-enum | array[string:type-enum]} - Describes which data types are allowed. It can be a STRING or an ARRAY of strings that represent names of data types that can be passed. JSON data types are:

    • null
    • boolean
    • integer - integer only
    • number - integer and float
    • string
    • array
    • object
    • any | * | empty string

    If this property is not provided, it will be assumed that any/all data types are allowed.

  • disallow [d03] {string:type-enum | array[string:type-enum]} - Describes which data types are NOT allowed. Can be a string or an array of strings, see type.

  • enum or only [e] {array[any]} - Lists all allowed values in an array. Your instance MUST be equal to one of listed values. Objects and array values are submited to CRC32 so the search could be performed faster (via hash tables). All of enum object's properties are ordered and then submited to CRC32 (this is done because JavaScript object properties are unordered), then the value object is processed the same way, and then the comparison between objects can be done very fast in order to see if the object is listed in enum field or not.

  • forbidden [e] {array[any]} - Lists all forbidden values of an instance. Your instance MUST NOT equal to any of forbidden values. See enum.

  • min [e] {number | object:range-object | array[object:range-object]} - Represents a minimum for all data types. It can be just a number and in that form it limits the size of an array or string, number of properties in an object, or minimal value for a number or an integer. If we want the minimum to be exclusive we can write the 'min' keyword value as a range-object: 'min':{ 'value': 3, 'exclusive': true}, so the instance value has to be more than 3, or to be longer than 3, or to have more than three properties. If we want to define minimum for each separate data type, that is if we validate more than one data type with one schema, we can write an array of range object adding a property 'type' to them. For example:

	'min':[{'type': 'object', 'value':5, 'exclusive': true},  /* requires the object 
	instance to have minimum of 6 properties */
		{'type':'string', 'value':3}, //only for string  
		{'value': 2} //for any other type  
	]

A special example would be if you define min like this: min:{type:string, value:4}, which would mean that this minimum will be applied only when a type is string, for other types this minimum will validate as true.
NOTE: Float minimum values are only allowed for number type. In other cases they dont make sense.

  • max [e] {number | object:range-object | array[object:range-object]} - Defines a maximum for all data types. Same as min, see it for usage details.

  • if [e] {object:condition-object | array[object:condition-object]} - Used to make conditions inside a schema. That means if a schema that is presented as a condition passes then one schema must pass else if it fails some other schema must pass. The value of this keyword must be an object structured as a condition object, or an array of contiditon objects. Structure of a condition object looks like this:

	'if': {
		'not': false,
		'condition': {condition_schema},
		'then': {then_schema},
		'else': {else_schema}
	}
Properties of a condition object are as follows:
+ not {boolean} - not mandatory, defines the negation of condition
+ condition {object:schema} - mandatory, MUST be a valid schema
+ then {object:schema} - mandatory, MUST be a valid schema
+ else {object:schema} - not mandatory, MUST be a valid schema  
  
You can nest conditions of course.  
Even though the same result can be achieved with oneOf, anyOf, allOf, not, one must argue that this approach to logic is more elegant.
  • required {boolean[d03] | array[string][d04]} - Required keyword can be used in two ways. If it is a boolean then it can be used as defined in draft 03, which says that the instance validated must not be undefined. This can be used in more 'meta' manner, for example labeling that the instance is mandatory. If the keyword value is array of strings it behaves as described in draft 04, as a list of required/mandatory pattern names. If you desire, you can use the keyword as boolean and provide the required/mandatory properties under requiredProperties.

  • anyOf [d04] {array[object:schema]} - One or more provided schemas in an array must validate the instance. Value of this keyword MUST be an array and it MUST have one or more valid schemas.

  • allOf [d04] {array[object:schema]} - All of provided schemas in an array must validate the instance. Value of this keyword MUST be an array and it MUST have one or more valid schemas.

  • oneOf [d04] {array[object:schema]} - One and only one of provided schemas in an array must validate the instance. Value of this keyword MUST be an array and it MUST have one or more valid schemas.

  • not [d04] {array[object:schema]} - Any of provided schemas in an array MUST NOT validate the instance. Value of this keyword MUST be an array and it MUST have one or more valid schemas.

[4] Number

  • minimum {number} - Defines instance's minimum value if the instance is number. Non exclusive. It MUST be a number (integer or float).

  • exclusiveMinimum {boolean} - If the minimum is exclusive or not. Default value is false. If this keyword is provided minimum value MUST also be provided, that is, it depends on minimum keyword.

  • maximum {number} - Defines instance's maximum value if the instance is number. Non exclusive. It MUST be a number (integer or float).

  • exclusiveMaximum {boolean} - If the maximum is exclusive or not. Default value is false. If this keyword is provided maximum value MUST also be provided, that is, it depends on maximum keyword.

  • numberPattern [e] or numberRegex [e] {string:regex} - Validates a number against a Regular Expression built from a provided string writen in compilance to JavaScript's ECMA-262 standard. You can form the string in two ways "ab+c" or "/ab+c/gi". It turns the number to string and tests the RegExp.

  • numberFormat [e] {string:available-format-names} - Values of this keyword are predifined names of common regular expressions used for checking the format of numbers, like the spaces below the floating point. Currently there are no formats for numbers. More expressions are soon to come, nevertheless you can extend the set yourself. For ways how to do it, see Extending example chapter.

  • multipleOf [d04] or dividableBy [d03] {number != 0} - If the number instance is dividable/multiple of provided number. Provided dividableBy number MUST NOT be 0.

[5] String

  • pattern or regex [e] {string:regex} - Validates a string instance against a Regular Expression built from a provided string writen in compilance to JavaScript's ECMA-262 standard. You can form the string in two ways "ab+c" or "/ab+c/gi". Regex is just a self explanatory rename.

  • format [d04] {string:available-format-names} - Values of this keyword are predifined names of common regular expressions. Currently you can use one of the available at the time: uri, regex, time, email, json. More expressions are soon to come, nevertheless you can extend the set yourself. For ways how to do it, see Extending example chapter.

  • minLength {+integer=0} - Minimum number of characters. Must be a positive integer. Default value is 0.

  • maxLength {+integer} - Maximum number of characters. Must be a positive integer.

[6] Array

  • uniqueItems or unique [e] {boolean=false} - Check if the contents of the array are unique. If the value repeats itself validation fails. Default value is false. Remember object comparison is performed by CRC32!

  • items {object:schema | array[object:schema]} - Can be an object which is a valid schema or an array of valid schema objects. If it is a schema, each item in the array is validated against this schema. If it is an array of schemas, each array item is validated by a schema of the same index position.

  • additionalItems {boolean=true} - If this motherfucker is false, and if an array instance has more items than specified in schema keyword items, this motherfucker fails the validation. We happy?

  • minItems {+integer=0} - Defines minimum allowed items in an array instance. MUST be a positive integer. Default value is zero.

  • maxItems {+integer} - Defines maximum allowed items in an array instance. MUST be a positive integer. Default value is zero.

[7] Object

  • required [d04] or requiredProperies [e] {array[string:property-names]} - List of required property names in an array. This value MUST be an array. If you want to use required as noted in draft 03, to label the instance as mandatory, use requredProperties to note required properties of course.

  • properties {object[object:schema]} - Object that contains schemas under properties named as expected properties of the instance object. Each property of the instance objet is validated against the correct schema. NOTE: You can use regular expressions as property names in this section of a schema, labeling them between '/' and adding the regex options at the end like "/ab+c/gi". This might be removed, cause it might piss you off, if you use paths as a property name. But when i come to think of it, there is no reason it shouldn't work, except when you same your paths /something/gi, something/ig... :) Be warned!

  • patternProperties {object[object:schema]} - Use regular expression strings to select properties of an object instance and test them against correct schemas. All properties of an instance that match one schema formed property under this keyword will be tested against the supplied schema. Regular expression properties can be written in two ways "ab+c" or with properties "/ab+c/gi".

  • additionalProperties {boolean=true | object:schema} - If this schema property is false then all of instance object's properties must be covered with schemas defined in properties and patternProperties segment of the schema. If there are any that are not covered the validation fails. In other words only properties defined in properties and patternProperties are allowed. The keyword can also be one schema for all other additional properies. Default value is true, which means that additional properties are allowed.

  • dependencies {object[array[string:property-names]]} - It is an object that hold arrays in properties labeled as expected properties of an instance object. Arrays contain strings of property names the labeled property name depends on. If there are no such propertis the validation fails.

  • minProperties {+integer=0} - Minimal number of properties. MUST be a positive integer. Default value is zero.

  • maxProperties {+integer} - Maximum number of properties. MUST be a positive integer.

More details

JSON Schema draft 04: http://json-schema.org/latest/json-schema-validation.html
JSON Schema draft 03: http://tools.ietf.org/html/draft-zyp-json-schema-03

To Do

  • Schema extends
  • Refactor the code (this will enable all other tasks to be completed with ease)
  • Better errors. Point out whitch part of the schema failed the validation.
  • Better $ref resolving, forbid the infinite loop
  • Comment the code! The thing you hate the most... but dont come back whining who's code is this after a year...
  • Hyper Schema
  • Node.js package

Authors

ivartech.com

Artwork: Silhouette taken from http://la-feuille-verte.tumblr.com/post/34228956696/pulp-fiction-silhouette and font from http://www.dafont.com/pulp-fiction-m54.font. Thanks!

Licence

Motherfuckin' MIT protects our ass...

` Copyright (C) 2013. ivartech.com - Nikola Stamatovic Stamat < stamat@ivartech.com >

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. `

Curiosities

Since all of the simple and selfexplanatory names were taken, like JSV or schema.js, or whatever... I wanted to name it JVlS, and then said to myself: "Chill that fuckin' bitch out!" So in that manner:

Name comes from a Quentin Tarantino's movie "Pulp Finction" character named Jules Winnfield, portrayed by Samuel L. Jackson. The reason is the famous quote: 'There's a passage I got memorized. Ezekiel 25:17. "The path of the righteous man is beset on all sides by the inequities of the selfish and the tyranny of evil men. Blessed is he who, in the name of charity and good will, shepherds the weak through the valley of the darkness, for he is truly his brother's keeper and the finder of lost children. And I will strike down upon thee with great vengeance and furious anger those who attempt to poison and destroy My brothers. And you will know I am the Lord when I lay My vengeance upon you."'

Vincent: And you know what they call a... a... a Quarter Pounder with Cheese in Paris?
Jules: They don't call it a Quarter Pounder with cheese?
Vincent: No man, they got the metric system. They wouldn't know what the fuck a Quarter Pounder is.
Jules: Then what do they call it?
Vincent: They call it a Royale with cheese.

About

JULES - (another) JavaScript Schema Validator

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published