1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #ifndef COMPONENTS_JSON_SCHEMA_JSON_SCHEMA_VALIDATOR_H_
6 #define COMPONENTS_JSON_SCHEMA_JSON_SCHEMA_VALIDATOR_H_
7 
8 #include <map>
9 #include <memory>
10 #include <string>
11 #include <vector>
12 
13 #include "base/macros.h"
14 
15 namespace base {
16 class DictionaryValue;
17 class ListValue;
18 class Value;
19 }
20 
21 //==============================================================================
22 // This class implements a subset of JSON Schema.
23 // See: http://www.json.com/json-schema-proposal/ for more details.
24 //
25 // There is also an older JavaScript implementation of the same functionality in
26 // chrome/renderer/resources/json_schema.js.
27 //
28 // The following features of JSON Schema are not implemented:
29 // - requires
30 // - unique
31 // - disallow
32 // - union types (but replaced with 'choices')
33 // - number.maxDecimal
34 //
35 // The following properties are not applicable to the interface exposed by
36 // this class:
37 // - options
38 // - readonly
39 // - title
40 // - description
41 // - format
42 // - default
43 // - transient
44 // - hidden
45 //
46 // There are also these departures from the JSON Schema proposal:
47 // - null counts as 'unspecified' for optional values
48 // - added the 'choices' property, to allow specifying a list of possible types
49 //   for a value
50 // - by default an "object" typed schema does not allow additional properties.
51 //   if present, "additionalProperties" is to be a schema against which all
52 //   additional properties will be validated.
53 // - regular expression supports all syntaxes that re2 accepts.
54 //   See https://github.com/google/re2/blob/master/doc/syntax.txt for details.
55 //==============================================================================
56 class JSONSchemaValidator {
57  public:
58   // Details about a validation error.
59   struct Error {
60     Error();
61 
62     explicit Error(const std::string& message);
63 
64     Error(const std::string& path, const std::string& message);
65 
66     // The path to the location of the error in the JSON structure.
67     std::string path;
68 
69     // An english message describing the error.
70     std::string message;
71   };
72 
73   enum Options {
74     // Ignore unknown attributes. If this option is not set then unknown
75     // attributes will make the schema validation fail.
76     OPTIONS_IGNORE_UNKNOWN_ATTRIBUTES = 1 << 0,
77   };
78 
79   // Error messages.
80   static const char kUnknownTypeReference[];
81   static const char kInvalidChoice[];
82   static const char kInvalidEnum[];
83   static const char kObjectPropertyIsRequired[];
84   static const char kUnexpectedProperty[];
85   static const char kArrayMinItems[];
86   static const char kArrayMaxItems[];
87   static const char kArrayItemRequired[];
88   static const char kStringMinLength[];
89   static const char kStringMaxLength[];
90   static const char kStringPattern[];
91   static const char kNumberMinimum[];
92   static const char kNumberMaximum[];
93   static const char kInvalidType[];
94   static const char kInvalidTypeIntegerNumber[];
95   static const char kInvalidRegex[];
96 
97   // Classifies a Value as one of the JSON schema primitive types.
98   static std::string GetJSONSchemaType(const base::Value* value);
99 
100   // Utility methods to format error messages. The first method can have one
101   // wildcard represented by '*', which is replaced with s1. The second method
102   // can have two, which are replaced by s1 and s2.
103   static std::string FormatErrorMessage(const std::string& format,
104                                         const std::string& s1);
105   static std::string FormatErrorMessage(const std::string& format,
106                                         const std::string& s1,
107                                         const std::string& s2);
108 
109   // Verifies if |schema| is a valid JSON v3 schema. When this validation passes
110   // then |schema| is valid JSON that can be parsed into a DictionaryValue,
111   // and that DictionaryValue can be used to build a JSONSchemaValidator.
112   // Returns the parsed DictionaryValue when |schema| validated, otherwise
113   // returns NULL. In that case, |error| contains an error description.
114   // For performance reasons, currently IsValidSchema() won't check the
115   // correctness of regular expressions used in "pattern" and
116   // "patternProperties" and in Validate() invalid regular expression don't
117   // accept any strings.
118   static std::unique_ptr<base::DictionaryValue> IsValidSchema(
119       const std::string& schema,
120       std::string* error);
121 
122   // Same as above but with |options|, which is a bitwise-OR combination of the
123   // Options above.
124   static std::unique_ptr<base::DictionaryValue>
125   IsValidSchema(const std::string& schema, int options, std::string* error);
126 
127   // Creates a validator for the specified schema.
128   //
129   // NOTE: This constructor assumes that |schema| is well formed and valid.
130   // Errors will result in CHECK at runtime; this constructor should not be used
131   // with untrusted schemas.
132   explicit JSONSchemaValidator(base::DictionaryValue* schema);
133 
134   // Creates a validator for the specified schema and user-defined types. Each
135   // type must be a valid JSONSchema type description with an additional "id"
136   // field. Schema objects in |schema| can refer to these types with the "$ref"
137   // property.
138   //
139   // NOTE: This constructor assumes that |schema| and |types| are well-formed
140   // and valid. Errors will result in CHECK at runtime; this constructor should
141   // not be used with untrusted schemas.
142   JSONSchemaValidator(base::DictionaryValue* schema, base::ListValue* types);
143 
144   ~JSONSchemaValidator();
145 
146   // Whether the validator allows additional items for objects and lists, beyond
147   // those defined by their schema, by default.
148   //
149   // This setting defaults to false: all items in an instance list or object
150   // must be defined by the corresponding schema.
151   //
152   // This setting can be overridden on individual object and list schemas by
153   // setting the "additionalProperties" field.
default_allow_additional_properties()154   bool default_allow_additional_properties() const {
155     return default_allow_additional_properties_;
156   }
157 
set_default_allow_additional_properties(bool val)158   void set_default_allow_additional_properties(bool val) {
159     default_allow_additional_properties_ = val;
160   }
161 
162   // Returns any errors from the last call to to Validate().
errors()163   const std::vector<Error>& errors() const {
164     return errors_;
165   }
166 
167   // Validates a JSON value. Returns true if the instance is valid, false
168   // otherwise. If false is returned any errors are available from the errors()
169   // getter.
170   bool Validate(const base::Value* instance);
171 
172  private:
173   typedef std::map<std::string, const base::DictionaryValue*> TypeMap;
174 
175   // Each of the below methods handle a subset of the validation process. The
176   // path paramater is the path to |instance| from the root of the instance tree
177   // and is used in error messages.
178 
179   // Validates any instance node against any schema node. This is called for
180   // every node in the instance tree, and it just decides which of the more
181   // detailed methods to call.
182   void Validate(const base::Value* instance,
183                 const base::DictionaryValue* schema,
184                 const std::string& path);
185 
186   // Validates a node against a list of possible schemas. If any one of the
187   // schemas match, the node is valid.
188   void ValidateChoices(const base::Value* instance,
189                        const base::ListValue* choices,
190                        const std::string& path);
191 
192   // Validates a node against a list of exact primitive values, eg 42, "foobar".
193   void ValidateEnum(const base::Value* instance,
194                     const base::ListValue* choices,
195                     const std::string& path);
196 
197   // Validates a JSON object against an object schema node.
198   void ValidateObject(const base::DictionaryValue* instance,
199                       const base::DictionaryValue* schema,
200                       const std::string& path);
201 
202   // Validates a JSON array against an array schema node.
203   void ValidateArray(const base::ListValue* instance,
204                      const base::DictionaryValue* schema,
205                      const std::string& path);
206 
207   // Validates a JSON array against an array schema node configured to be a
208   // tuple. In a tuple, there is one schema node for each item expected in the
209   // array.
210   void ValidateTuple(const base::ListValue* instance,
211                      const base::DictionaryValue* schema,
212                      const std::string& path);
213 
214   // Validate a JSON string against a string schema node.
215   void ValidateString(const base::Value* instance,
216                       const base::DictionaryValue* schema,
217                       const std::string& path);
218 
219   // Validate a JSON number against a number schema node.
220   void ValidateNumber(const base::Value* instance,
221                       const base::DictionaryValue* schema,
222                       const std::string& path);
223 
224   // Validates that the JSON node |instance| has |expected_type|.
225   bool ValidateType(const base::Value* instance,
226                     const std::string& expected_type,
227                     const std::string& path);
228 
229   // Returns true if |schema| will allow additional items of any type.
230   bool SchemaAllowsAnyAdditionalItems(
231       const base::DictionaryValue* schema,
232       const base::DictionaryValue** addition_items_schema);
233 
234   // The root schema node.
235   base::DictionaryValue* schema_root_;
236 
237   // Map of user-defined name to type.
238   TypeMap types_;
239 
240   // Whether we allow additional properties on objects by default. This can be
241   // overridden by the allow_additional_properties flag on an Object schema.
242   bool default_allow_additional_properties_;
243 
244   // Errors accumulated since the last call to Validate().
245   std::vector<Error> errors_;
246 
247 
248   DISALLOW_COPY_AND_ASSIGN(JSONSchemaValidator);
249 };
250 
251 #endif  // COMPONENTS_JSON_SCHEMA_JSON_SCHEMA_VALIDATOR_H_
252