Index: pkg/json_rpc_2/lib/src/parameters.dart |
diff --git a/pkg/json_rpc_2/lib/src/parameters.dart b/pkg/json_rpc_2/lib/src/parameters.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..afc4a402c66e783875ec4fa8d39e84014849913d |
--- /dev/null |
+++ b/pkg/json_rpc_2/lib/src/parameters.dart |
@@ -0,0 +1,283 @@ |
+// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file |
+// for details. All rights reserved. Use of this source code is governed by a |
+// BSD-style license that can be found in the LICENSE file. |
+ |
+library json_rpc_2.parameters; |
+ |
+import 'dart:convert'; |
+ |
+import 'exception.dart'; |
+ |
+/// A wrapper for the parameters to a server method. |
+/// |
+/// JSON-RPC 2.0 allows parameters that are either a list or a map. This class |
+/// provides functions that not only assert that the parameters object is the |
+/// correct type, but also that the expected arguments exist and are themselves |
+/// the correct type. |
+/// |
+/// Example usage: |
+/// |
+/// server.registerMethod("subtract", (params) { |
+/// return params["minuend"].asNum - params["subtrahend"].asNum; |
+/// }); |
+class Parameters { |
+ /// The name of the method that this request called. |
+ final String method; |
+ |
+ /// The underlying value of the parameters object. |
+ /// |
+ /// If this is accessed for a [Parameter] that was not passed, the request |
+ /// will be automatically rejected. To avoid this, use [Parameter.valueOr]. |
+ final value; |
+ |
+ Parameters(this.method, this.value); |
+ |
+ /// Returns a single parameter. |
+ /// |
+ /// If [key] is a [String], the request is expected to provide named |
+ /// parameters. If it's an [int], the request is expected to provide |
+ /// positional parameters. Requests that don't do so will be rejected |
+ /// automatically. |
+ /// |
+ /// Whether or not the given parameter exists, this returns a [Parameter] |
+ /// object. If a parameter's value is accessed through a getter like [value] |
+ /// or [Parameter.asNum], the request will be rejected if that parameter |
+ /// doesn't exist. On the other hand, if it's accessed through a method with a |
+ /// default value like [Parameter.valueOr] or [Parameter.asNumOr], the default |
+ /// value will be returned. |
+ Parameter operator [](key) { |
+ if (key is int) { |
+ _assertPositional(); |
+ if (key < value.length) { |
+ return new Parameter._(method, value[key], this, key); |
+ } else { |
+ return new _MissingParameter(method, this, key); |
+ } |
+ } else if (key is String) { |
+ _assertNamed(); |
+ if (value.containsKey(key)) { |
+ return new Parameter._(method, value[key], this, key); |
+ } else { |
+ return new _MissingParameter(method, this, key); |
+ } |
+ } else { |
+ throw new ArgumentError('Parameters[] only takes an int or a string, was ' |
+ '"$key".'); |
+ } |
+ } |
+ |
+ /// Asserts that [value] exists and is a [List] and returns it. |
+ List get asList { |
+ _assertPositional(); |
+ return value; |
+ } |
+ |
+ /// Asserts that [value] exists and is a [Map] and returns it. |
+ Map get asMap { |
+ _assertNamed(); |
+ return value; |
+ } |
+ |
+ /// Asserts that [value] is a positional argument list. |
+ void _assertPositional() { |
+ if (value is List) return; |
+ throw new RpcException.invalidParams('Parameters for method "$method" ' |
+ 'must be passed by position.'); |
+ } |
+ |
+ /// Asserts that [value] is a named argument map. |
+ void _assertNamed() { |
+ if (value is Map) return; |
+ throw new RpcException.invalidParams('Parameters for method "$method" ' |
+ 'must be passed by name.'); |
+ } |
+} |
+ |
+/// A wrapper for a single parameter to a server method. |
+/// |
+/// This provides numerous functions for asserting the type of the parameter in |
+/// question. These functions each have a version that asserts that the |
+/// parameter exists (for example, [asNum] and [asString]) and a version that |
+/// returns a default value if the parameter doesn't exist (for example, |
+/// [asNumOr] and [asStringOr]). If an assertion fails, the request is |
+/// automatically rejected. |
+/// |
+/// This extends [Parameters] to make it easy to access nested parameters. For |
+/// example: |
+/// |
+/// // "params.value" is "{'scores': {'home': [5, 10, 17]}}" |
+/// params['scores']['home'][2].asInt // => 17 |
+class Parameter extends Parameters { |
+ // The parent parameters, used to construct [_path]. |
+ final Parameters _parent; |
+ |
+ /// The key used to access [this], used to construct [_path]. |
+ final _key; |
+ |
+ /// A human-readable representation of the path of getters used to get this. |
+ /// |
+ /// Named parameters are represented as `.name`, whereas positional parameters |
+ /// are represented as `[index]`. For example: `"foo[0].bar.baz"`. Named |
+ /// parameters that contain characters that are neither alphanumeric, |
+ /// underscores, or hyphens will be JSON-encoded. For example: `"foo |
+ /// bar"."baz.bang"`. If quotes are used for an individual component, they |
+ /// won't be used for the entire string. |
+ /// |
+ /// An exception is made for single-level parameters. A single-level |
+ /// positional parameter is just represented by the index plus one, because |
+ /// "parameter 1" is clearer than "parameter [0]". A single-level named |
+ /// parameter is represented by that name in quotes. |
+ String get _path { |
+ if (_parent is! Parameter) { |
+ return _key is int ? (_key + 1).toString() : JSON.encode(_key); |
+ } |
+ |
+ quoteKey(key) { |
+ if (key.contains(new RegExp(r'[^a-zA-Z0-9_-]'))) return JSON.encode(key); |
+ return key; |
+ } |
+ |
+ computePath(params) { |
+ if (params._parent is! Parameter) { |
+ return params._key is int ? "[${params._key}]" : quoteKey(params._key); |
+ } |
+ |
+ var path = computePath(params._parent); |
+ return params._key is int ? |
+ "$path[${params._key}]" : "$path.${quoteKey(params._key)}"; |
+ } |
+ |
+ return computePath(this); |
+ } |
+ |
+ /// Whether this parameter exists. |
+ final exists = true; |
+ |
+ Parameter._(String method, value, this._parent, this._key) |
+ : super(method, value); |
+ |
+ /// Returns [value], or [defaultValue] if this parameter wasn't passed. |
+ valueOr(defaultValue) => value; |
+ |
+ /// Asserts that [value] exists and is a number and returns it. |
+ /// |
+ /// [asNumOr] may be used to provide a default value instead of rejecting the |
+ /// request if [value] doesn't exist. |
+ num get asNum => _getTyped('a number', (value) => value is num); |
+ |
+ /// Asserts that [value] is a number and returns it. |
+ /// |
+ /// If [value] doesn't exist, this returns [defaultValue]. |
+ num asNumOr(num defaultValue) => asNum; |
+ |
+ /// Asserts that [value] exists and is an integer and returns it. |
+ /// |
+ /// [asIntOr] may be used to provide a default value instead of rejecting the |
+ /// request if [value] doesn't exist. |
+ /// |
+ /// Note that which values count as integers varies between the Dart VM and |
+ /// dart2js. The value `1.0` will be considered an integer under dart2js but |
+ /// not under the VM. |
+ int get asInt => _getTyped('an integer', (value) => value is int); |
+ |
+ /// Asserts that [value] is an integer and returns it. |
+ /// |
+ /// If [value] doesn't exist, this returns [defaultValue]. |
+ /// |
+ /// Note that which values count as integers varies between the Dart VM and |
+ /// dart2js. The value `1.0` will be considered an integer under dart2js but |
+ /// not under the VM. |
+ int asIntOr(int defaultValue) => asInt; |
+ |
+ /// Asserts that [value] exists and is a boolean and returns it. |
+ /// |
+ /// [asBoolOr] may be used to provide a default value instead of rejecting the |
+ /// request if [value] doesn't exist. |
+ bool get asBool => _getTyped('a boolean', (value) => value is bool); |
+ |
+ /// Asserts that [value] is a boolean and returns it. |
+ /// |
+ /// If [value] doesn't exist, this returns [defaultValue]. |
+ bool asBoolOr(bool defaultValue) => asBool; |
+ |
+ /// Asserts that [value] exists and is a string and returns it. |
+ /// |
+ /// [asStringOr] may be used to provide a default value instead of rejecting |
+ /// the request if [value] doesn't exist. |
+ String get asString => _getTyped('a string', (value) => value is String); |
+ |
+ /// Asserts that [value] is a string and returns it. |
+ /// |
+ /// If [value] doesn't exist, this returns [defaultValue]. |
+ String asStringOr(String defaultValue) => asString; |
+ |
+ /// Asserts that [value] exists and is a [List] and returns it. |
+ /// |
+ /// [asListOr] may be used to provide a default value instead of rejecting the |
+ /// request if [value] doesn't exist. |
+ List get asList => _getTyped('an Array', (value) => value is List); |
+ |
+ /// Asserts that [value] is a [List] and returns it. |
+ /// |
+ /// If [value] doesn't exist, this returns [defaultValue]. |
+ List asListOr(List defaultValue) => asList; |
+ |
+ /// Asserts that [value] exists and is a [Map] and returns it. |
+ /// |
+ /// [asListOr] may be used to provide a default value instead of rejecting the |
+ /// request if [value] doesn't exist. |
+ Map get asMap => _getTyped('an Object', (value) => value is Map); |
+ |
+ /// Asserts that [value] is a [Map] and returns it. |
+ /// |
+ /// If [value] doesn't exist, this returns [defaultValue]. |
+ Map asMapOr(Map defaultValue) => asMap; |
+ |
+ /// Get a parameter named [named] that matches [test], or the value of calling |
+ /// [orElse]. |
+ /// |
+ /// [type] is used for the error message. It should begin with an indefinite |
+ /// article. |
+ _getTyped(String type, bool test(value)) { |
+ if (test(value)) return value; |
+ throw new RpcException.invalidParams('Parameter $_path for method ' |
+ '"$method" must be $type, but was ${JSON.encode(value)}.'); |
+ } |
+ |
+ void _assertPositional() { |
+ // Throw the standard exception for a mis-typed list. |
+ asList; |
+ } |
+ |
+ void _assertNamed() { |
+ // Throw the standard exception for a mis-typed map. |
+ asMap; |
+ } |
+} |
+ |
+/// A subclass of [Parameter] representing a missing parameter. |
+class _MissingParameter extends Parameter { |
+ get value { |
+ throw new RpcException.invalidParams('Request for method "$method" is ' |
+ 'missing required parameter $_path.'); |
+ } |
+ |
+ final exists = false; |
+ |
+ _MissingParameter(String method, Parameters parent, key) |
+ : super._(method, null, parent, key); |
+ |
+ valueOr(defaultValue) => defaultValue; |
+ |
+ num asNumOr(num defaultValue) => defaultValue; |
+ |
+ int asIntOr(int defaultValue) => defaultValue; |
+ |
+ bool asBoolOr(bool defaultValue) => defaultValue; |
+ |
+ String asStringOr(String defaultValue) => defaultValue; |
+ |
+ List asListOr(List defaultValue) => defaultValue; |
+ |
+ Map asMapOr(Map defaultValue) => defaultValue; |
+} |