| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | |
| 2 // for details. All rights reserved. Use of this source code is governed by a | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 library json_rpc_2.parameters; | |
| 6 | |
| 7 import 'dart:convert'; | |
| 8 | |
| 9 import 'exception.dart'; | |
| 10 | |
| 11 /// A wrapper for the parameters to a server method. | |
| 12 /// | |
| 13 /// JSON-RPC 2.0 allows parameters that are either a list or a map. This class | |
| 14 /// provides functions that not only assert that the parameters object is the | |
| 15 /// correct type, but also that the expected arguments exist and are themselves | |
| 16 /// the correct type. | |
| 17 /// | |
| 18 /// Example usage: | |
| 19 /// | |
| 20 /// server.registerMethod("subtract", (params) { | |
| 21 /// return params["minuend"].asNum - params["subtrahend"].asNum; | |
| 22 /// }); | |
| 23 class Parameters { | |
| 24 /// The name of the method that this request called. | |
| 25 final String method; | |
| 26 | |
| 27 /// The underlying value of the parameters object. | |
| 28 /// | |
| 29 /// If this is accessed for a [Parameter] that was not passed, the request | |
| 30 /// will be automatically rejected. To avoid this, use [Parameter.valueOr]. | |
| 31 final value; | |
| 32 | |
| 33 Parameters(this.method, this.value); | |
| 34 | |
| 35 /// Returns a single parameter. | |
| 36 /// | |
| 37 /// If [key] is a [String], the request is expected to provide named | |
| 38 /// parameters. If it's an [int], the request is expected to provide | |
| 39 /// positional parameters. Requests that don't do so will be rejected | |
| 40 /// automatically. | |
| 41 /// | |
| 42 /// Whether or not the given parameter exists, this returns a [Parameter] | |
| 43 /// object. If a parameter's value is accessed through a getter like [value] | |
| 44 /// or [Parameter.asNum], the request will be rejected if that parameter | |
| 45 /// doesn't exist. On the other hand, if it's accessed through a method with a | |
| 46 /// default value like [Parameter.valueOr] or [Parameter.asNumOr], the default | |
| 47 /// value will be returned. | |
| 48 Parameter operator [](key) { | |
| 49 if (key is int) { | |
| 50 _assertPositional(); | |
| 51 if (key < value.length) { | |
| 52 return new Parameter._(method, value[key], this, key); | |
| 53 } else { | |
| 54 return new _MissingParameter(method, this, key); | |
| 55 } | |
| 56 } else if (key is String) { | |
| 57 _assertNamed(); | |
| 58 if (value.containsKey(key)) { | |
| 59 return new Parameter._(method, value[key], this, key); | |
| 60 } else { | |
| 61 return new _MissingParameter(method, this, key); | |
| 62 } | |
| 63 } else { | |
| 64 throw new ArgumentError('Parameters[] only takes an int or a string, was ' | |
| 65 '"$key".'); | |
| 66 } | |
| 67 } | |
| 68 | |
| 69 /// Asserts that [value] exists and is a [List] and returns it. | |
| 70 List get asList { | |
| 71 _assertPositional(); | |
| 72 return value; | |
| 73 } | |
| 74 | |
| 75 /// Asserts that [value] exists and is a [Map] and returns it. | |
| 76 Map get asMap { | |
| 77 _assertNamed(); | |
| 78 return value; | |
| 79 } | |
| 80 | |
| 81 /// Asserts that [value] is a positional argument list. | |
| 82 void _assertPositional() { | |
| 83 if (value is List) return; | |
| 84 throw new RpcException.invalidParams('Parameters for method "$method" ' | |
| 85 'must be passed by position.'); | |
| 86 } | |
| 87 | |
| 88 /// Asserts that [value] is a named argument map. | |
| 89 void _assertNamed() { | |
| 90 if (value is Map) return; | |
| 91 throw new RpcException.invalidParams('Parameters for method "$method" ' | |
| 92 'must be passed by name.'); | |
| 93 } | |
| 94 } | |
| 95 | |
| 96 /// A wrapper for a single parameter to a server method. | |
| 97 /// | |
| 98 /// This provides numerous functions for asserting the type of the parameter in | |
| 99 /// question. These functions each have a version that asserts that the | |
| 100 /// parameter exists (for example, [asNum] and [asString]) and a version that | |
| 101 /// returns a default value if the parameter doesn't exist (for example, | |
| 102 /// [asNumOr] and [asStringOr]). If an assertion fails, the request is | |
| 103 /// automatically rejected. | |
| 104 /// | |
| 105 /// This extends [Parameters] to make it easy to access nested parameters. For | |
| 106 /// example: | |
| 107 /// | |
| 108 /// // "params.value" is "{'scores': {'home': [5, 10, 17]}}" | |
| 109 /// params['scores']['home'][2].asInt // => 17 | |
| 110 class Parameter extends Parameters { | |
| 111 // The parent parameters, used to construct [_path]. | |
| 112 final Parameters _parent; | |
| 113 | |
| 114 /// The key used to access [this], used to construct [_path]. | |
| 115 final _key; | |
| 116 | |
| 117 /// A human-readable representation of the path of getters used to get this. | |
| 118 /// | |
| 119 /// Named parameters are represented as `.name`, whereas positional parameters | |
| 120 /// are represented as `[index]`. For example: `"foo[0].bar.baz"`. Named | |
| 121 /// parameters that contain characters that are neither alphanumeric, | |
| 122 /// underscores, or hyphens will be JSON-encoded. For example: `"foo | |
| 123 /// bar"."baz.bang"`. If quotes are used for an individual component, they | |
| 124 /// won't be used for the entire string. | |
| 125 /// | |
| 126 /// An exception is made for single-level parameters. A single-level | |
| 127 /// positional parameter is just represented by the index plus one, because | |
| 128 /// "parameter 1" is clearer than "parameter [0]". A single-level named | |
| 129 /// parameter is represented by that name in quotes. | |
| 130 String get _path { | |
| 131 if (_parent is! Parameter) { | |
| 132 return _key is int ? (_key + 1).toString() : JSON.encode(_key); | |
| 133 } | |
| 134 | |
| 135 quoteKey(key) { | |
| 136 if (key.contains(new RegExp(r'[^a-zA-Z0-9_-]'))) return JSON.encode(key); | |
| 137 return key; | |
| 138 } | |
| 139 | |
| 140 computePath(params) { | |
| 141 if (params._parent is! Parameter) { | |
| 142 return params._key is int ? "[${params._key}]" : quoteKey(params._key); | |
| 143 } | |
| 144 | |
| 145 var path = computePath(params._parent); | |
| 146 return params._key is int ? | |
| 147 "$path[${params._key}]" : "$path.${quoteKey(params._key)}"; | |
| 148 } | |
| 149 | |
| 150 return computePath(this); | |
| 151 } | |
| 152 | |
| 153 /// Whether this parameter exists. | |
| 154 final exists = true; | |
| 155 | |
| 156 Parameter._(String method, value, this._parent, this._key) | |
| 157 : super(method, value); | |
| 158 | |
| 159 /// Returns [value], or [defaultValue] if this parameter wasn't passed. | |
| 160 valueOr(defaultValue) => value; | |
| 161 | |
| 162 /// Asserts that [value] exists and is a number and returns it. | |
| 163 /// | |
| 164 /// [asNumOr] may be used to provide a default value instead of rejecting the | |
| 165 /// request if [value] doesn't exist. | |
| 166 num get asNum => _getTyped('a number', (value) => value is num); | |
| 167 | |
| 168 /// Asserts that [value] is a number and returns it. | |
| 169 /// | |
| 170 /// If [value] doesn't exist, this returns [defaultValue]. | |
| 171 num asNumOr(num defaultValue) => asNum; | |
| 172 | |
| 173 /// Asserts that [value] exists and is an integer and returns it. | |
| 174 /// | |
| 175 /// [asIntOr] may be used to provide a default value instead of rejecting the | |
| 176 /// request if [value] doesn't exist. | |
| 177 /// | |
| 178 /// Note that which values count as integers varies between the Dart VM and | |
| 179 /// dart2js. The value `1.0` will be considered an integer under dart2js but | |
| 180 /// not under the VM. | |
| 181 int get asInt => _getTyped('an integer', (value) => value is int); | |
| 182 | |
| 183 /// Asserts that [value] is an integer and returns it. | |
| 184 /// | |
| 185 /// If [value] doesn't exist, this returns [defaultValue]. | |
| 186 /// | |
| 187 /// Note that which values count as integers varies between the Dart VM and | |
| 188 /// dart2js. The value `1.0` will be considered an integer under dart2js but | |
| 189 /// not under the VM. | |
| 190 int asIntOr(int defaultValue) => asInt; | |
| 191 | |
| 192 /// Asserts that [value] exists and is a boolean and returns it. | |
| 193 /// | |
| 194 /// [asBoolOr] may be used to provide a default value instead of rejecting the | |
| 195 /// request if [value] doesn't exist. | |
| 196 bool get asBool => _getTyped('a boolean', (value) => value is bool); | |
| 197 | |
| 198 /// Asserts that [value] is a boolean and returns it. | |
| 199 /// | |
| 200 /// If [value] doesn't exist, this returns [defaultValue]. | |
| 201 bool asBoolOr(bool defaultValue) => asBool; | |
| 202 | |
| 203 /// Asserts that [value] exists and is a string and returns it. | |
| 204 /// | |
| 205 /// [asStringOr] may be used to provide a default value instead of rejecting | |
| 206 /// the request if [value] doesn't exist. | |
| 207 String get asString => _getTyped('a string', (value) => value is String); | |
| 208 | |
| 209 /// Asserts that [value] is a string and returns it. | |
| 210 /// | |
| 211 /// If [value] doesn't exist, this returns [defaultValue]. | |
| 212 String asStringOr(String defaultValue) => asString; | |
| 213 | |
| 214 /// Asserts that [value] exists and is a [List] and returns it. | |
| 215 /// | |
| 216 /// [asListOr] may be used to provide a default value instead of rejecting the | |
| 217 /// request if [value] doesn't exist. | |
| 218 List get asList => _getTyped('an Array', (value) => value is List); | |
| 219 | |
| 220 /// Asserts that [value] is a [List] and returns it. | |
| 221 /// | |
| 222 /// If [value] doesn't exist, this returns [defaultValue]. | |
| 223 List asListOr(List defaultValue) => asList; | |
| 224 | |
| 225 /// Asserts that [value] exists and is a [Map] and returns it. | |
| 226 /// | |
| 227 /// [asMapOr] may be used to provide a default value instead of rejecting the | |
| 228 /// request if [value] doesn't exist. | |
| 229 Map get asMap => _getTyped('an Object', (value) => value is Map); | |
| 230 | |
| 231 /// Asserts that [value] is a [Map] and returns it. | |
| 232 /// | |
| 233 /// If [value] doesn't exist, this returns [defaultValue]. | |
| 234 Map asMapOr(Map defaultValue) => asMap; | |
| 235 | |
| 236 /// Asserts that [value] exists, is a string, and can be parsed as a | |
| 237 /// [DateTime] and returns it. | |
| 238 /// | |
| 239 /// [asDateTimeOr] may be used to provide a default value instead of rejecting | |
| 240 /// the request if [value] doesn't exist. | |
| 241 DateTime get asDateTime => _getParsed('date/time', DateTime.parse); | |
| 242 | |
| 243 /// Asserts that [value] exists, is a string, and can be parsed as a | |
| 244 /// [DateTime] and returns it. | |
| 245 /// | |
| 246 /// If [value] doesn't exist, this returns [defaultValue]. | |
| 247 DateTime asDateTimeOr(DateTime defaultValue) => asDateTime; | |
| 248 | |
| 249 /// Asserts that [value] exists, is a string, and can be parsed as a | |
| 250 /// [Uri] and returns it. | |
| 251 /// | |
| 252 /// [asUriOr] may be used to provide a default value instead of rejecting the | |
| 253 /// request if [value] doesn't exist. | |
| 254 Uri get asUri => _getParsed('URI', Uri.parse); | |
| 255 | |
| 256 /// Asserts that [value] exists, is a string, and can be parsed as a | |
| 257 /// [Uri] and returns it. | |
| 258 /// | |
| 259 /// If [value] doesn't exist, this returns [defaultValue]. | |
| 260 Uri asUriOr(Uri defaultValue) => asUri; | |
| 261 | |
| 262 /// Get a parameter named [named] that matches [test], or the value of calling | |
| 263 /// [orElse]. | |
| 264 /// | |
| 265 /// [type] is used for the error message. It should begin with an indefinite | |
| 266 /// article. | |
| 267 _getTyped(String type, bool test(value)) { | |
| 268 if (test(value)) return value; | |
| 269 throw new RpcException.invalidParams('Parameter $_path for method ' | |
| 270 '"$method" must be $type, but was ${JSON.encode(value)}.'); | |
| 271 } | |
| 272 | |
| 273 _getParsed(String description, parse(String value)) { | |
| 274 var string = asString; | |
| 275 try { | |
| 276 return parse(string); | |
| 277 } on FormatException catch (error) { | |
| 278 // DateTime.parse doesn't actually include any useful information in the | |
| 279 // FormatException, just the string that was being parsed. There's no use | |
| 280 // in including that in the RPC exception. See issue 17753. | |
| 281 var message = error.message; | |
| 282 if (message == string) { | |
| 283 message = ''; | |
| 284 } else { | |
| 285 message = '\n$message'; | |
| 286 } | |
| 287 | |
| 288 throw new RpcException.invalidParams('Parameter $_path for method ' | |
| 289 '"$method" must be a valid $description, but was ' | |
| 290 '${JSON.encode(string)}.$message'); | |
| 291 } | |
| 292 } | |
| 293 | |
| 294 void _assertPositional() { | |
| 295 // Throw the standard exception for a mis-typed list. | |
| 296 asList; | |
| 297 } | |
| 298 | |
| 299 void _assertNamed() { | |
| 300 // Throw the standard exception for a mis-typed map. | |
| 301 asMap; | |
| 302 } | |
| 303 } | |
| 304 | |
| 305 /// A subclass of [Parameter] representing a missing parameter. | |
| 306 class _MissingParameter extends Parameter { | |
| 307 get value { | |
| 308 throw new RpcException.invalidParams('Request for method "$method" is ' | |
| 309 'missing required parameter $_path.'); | |
| 310 } | |
| 311 | |
| 312 final exists = false; | |
| 313 | |
| 314 _MissingParameter(String method, Parameters parent, key) | |
| 315 : super._(method, null, parent, key); | |
| 316 | |
| 317 valueOr(defaultValue) => defaultValue; | |
| 318 | |
| 319 num asNumOr(num defaultValue) => defaultValue; | |
| 320 | |
| 321 int asIntOr(int defaultValue) => defaultValue; | |
| 322 | |
| 323 bool asBoolOr(bool defaultValue) => defaultValue; | |
| 324 | |
| 325 String asStringOr(String defaultValue) => defaultValue; | |
| 326 | |
| 327 List asListOr(List defaultValue) => defaultValue; | |
| 328 | |
| 329 Map asMapOr(Map defaultValue) => defaultValue; | |
| 330 | |
| 331 DateTime asDateTimeOr(DateTime defaultValue) => defaultValue; | |
| 332 | |
| 333 Uri asUriOr(Uri defaultValue) => defaultValue; | |
| 334 } | |
| OLD | NEW |