| OLD | NEW |
| 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | 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 | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 library protocol; | 5 library protocol; |
| 6 | 6 |
| 7 import 'dart:collection'; | 7 import 'dart:collection'; |
| 8 import 'dart:convert' show JsonDecoder; | 8 import 'dart:convert' show JsonDecoder; |
| 9 | 9 |
| 10 import 'package:analysis_server/src/services/json.dart'; | 10 import 'package:analysis_server/src/services/json.dart'; |
| (...skipping 77 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 88 return new Request(id, method, params); | 88 return new Request(id, method, params); |
| 89 } else { | 89 } else { |
| 90 return null; | 90 return null; |
| 91 } | 91 } |
| 92 } catch (exception) { | 92 } catch (exception) { |
| 93 return null; | 93 return null; |
| 94 } | 94 } |
| 95 } | 95 } |
| 96 | 96 |
| 97 /** | 97 /** |
| 98 * Return the value of the parameter with the given [name], or throw a | |
| 99 * [RequestFailure] exception with an appropriate error message if there is no | |
| 100 * such parameter associated with this request. | |
| 101 */ | |
| 102 RequestDatum getRequiredParameter(String name) { | |
| 103 if (!params.containsKey(name)) { | |
| 104 throw new RequestFailure(new Response.missingRequiredParameter(this, name)
); | |
| 105 } | |
| 106 return new RequestDatum(this, name, params[name]); | |
| 107 } | |
| 108 | |
| 109 /** | |
| 110 * Return a table representing the structure of the Json object that will be | 98 * Return a table representing the structure of the Json object that will be |
| 111 * sent to the client to represent this response. | 99 * sent to the client to represent this response. |
| 112 */ | 100 */ |
| 113 Map<String, Object> toJson() { | 101 Map<String, Object> toJson() { |
| 114 Map<String, Object> jsonObject = new HashMap<String, Object>(); | 102 Map<String, Object> jsonObject = new HashMap<String, Object>(); |
| 115 jsonObject[ID] = id; | 103 jsonObject[ID] = id; |
| 116 jsonObject[METHOD] = method; | 104 jsonObject[METHOD] = method; |
| 117 if (params.isNotEmpty) { | 105 if (params.isNotEmpty) { |
| 118 jsonObject[PARAMS] = params; | 106 jsonObject[PARAMS] = params; |
| 119 } | 107 } |
| 120 return jsonObject; | 108 return jsonObject; |
| 121 } | 109 } |
| 122 } | 110 } |
| 123 | 111 |
| 124 /** | 112 /** |
| 125 * Instances of the class [RequestDatum] wrap a piece of data from a | |
| 126 * request parameter, and contain accessor methods which automatically validate | |
| 127 * and convert the data into the appropriate form. | |
| 128 */ | |
| 129 class RequestDatum { | |
| 130 /** | |
| 131 * Request object that should be referred to in any errors that are | |
| 132 * generated. | |
| 133 */ | |
| 134 final Request request; | |
| 135 | |
| 136 /** | |
| 137 * String description of how [datum] was obtained from the request. | |
| 138 */ | |
| 139 final String path; | |
| 140 | |
| 141 /** | |
| 142 * Value to be decoded and validated. | |
| 143 */ | |
| 144 final dynamic datum; | |
| 145 | |
| 146 /** | |
| 147 * Create a RequestDatum for decoding and validating [datum], which refers to | |
| 148 * [request] in any errors it reports. | |
| 149 */ | |
| 150 RequestDatum(this.request, this.path, this.datum); | |
| 151 | |
| 152 /** | |
| 153 * Validate that the datum is a Map containing the given [key], and return | |
| 154 * a [RequestDatum] containing the corresponding value. | |
| 155 */ | |
| 156 RequestDatum operator [](String key) { | |
| 157 Map<String, Object> map = _asMap(); | |
| 158 if (!map.containsKey(key)) { | |
| 159 throw new RequestFailure(new Response.invalidParameter(request, path, | |
| 160 "contain key '$key'")); | |
| 161 } | |
| 162 return new RequestDatum(request, "$path.$key", map[key]); | |
| 163 } | |
| 164 | |
| 165 /** | |
| 166 * Return `true` if the datum is a Map containing the given [key]. | |
| 167 */ | |
| 168 bool hasKey(String key) { | |
| 169 return _asMap().containsKey(key); | |
| 170 } | |
| 171 | |
| 172 /** | |
| 173 * Validate that the datum is a Map, and return an iterable for its keys. | |
| 174 */ | |
| 175 Iterable<String> get keys => _asMap().keys; | |
| 176 | |
| 177 /** | |
| 178 * Validate that the datum is a Map whose keys are strings, and call [f] on | |
| 179 * each key/value pair in the map. | |
| 180 */ | |
| 181 void forEachMap(void f(String key, RequestDatum value)) { | |
| 182 _asMap().forEach((String key, value) { | |
| 183 f(key, new RequestDatum(request, "$path.$key", value)); | |
| 184 }); | |
| 185 } | |
| 186 | |
| 187 /** | |
| 188 * Validate that the datum is an integer (or a string that can be parsed | |
| 189 * as an integer), and return the int. | |
| 190 */ | |
| 191 int asInt() { | |
| 192 if (datum is int) { | |
| 193 return datum; | |
| 194 } else if (datum is String) { | |
| 195 return int.parse(datum, onError: (String value) { | |
| 196 throw new RequestFailure(new Response.invalidParameter(request, path, | |
| 197 "be an integer")); | |
| 198 }); | |
| 199 } | |
| 200 throw new RequestFailure(new Response.invalidParameter(request, path, | |
| 201 "be an integer")); | |
| 202 } | |
| 203 | |
| 204 /** | |
| 205 * Validate that the datum is a boolean (or a string that can be parsed | |
| 206 * as a boolean), and return the bool. | |
| 207 * | |
| 208 * The value is typically the result of invoking either [getParameter] or | |
| 209 * [getRequiredParameter]. | |
| 210 */ | |
| 211 bool asBool() { | |
| 212 if (datum is bool) { | |
| 213 return datum; | |
| 214 } else if (datum == 'true') { | |
| 215 return true; | |
| 216 } else if (datum == 'false') { | |
| 217 return false; | |
| 218 } | |
| 219 throw new RequestFailure(new Response.invalidParameter(request, datum, | |
| 220 "be a boolean")); | |
| 221 } | |
| 222 | |
| 223 /** | |
| 224 * Determine if the datum is a list. Note: null is considered a synonym for | |
| 225 * the empty list. | |
| 226 */ | |
| 227 bool get isList { | |
| 228 return datum == null || datum is List; | |
| 229 } | |
| 230 | |
| 231 /** | |
| 232 * Validate that the datum is a list, and return it in raw form. | |
| 233 */ | |
| 234 List _asList() { | |
| 235 if (!isList) { | |
| 236 throw new RequestFailure(new Response.invalidParameter(request, path, | |
| 237 "be a list")); | |
| 238 } | |
| 239 if (datum == null) { | |
| 240 return []; | |
| 241 } else { | |
| 242 return datum; | |
| 243 } | |
| 244 } | |
| 245 | |
| 246 /** | |
| 247 * Validate that the datum is a list, and return a list where each element in | |
| 248 * the datum has been converted using the provided function. | |
| 249 */ | |
| 250 List asList(elementConverter(RequestDatum datum)) { | |
| 251 List result = []; | |
| 252 List list = _asList(); | |
| 253 for (int i = 0; i < list.length; i++) { | |
| 254 result.add(elementConverter(new RequestDatum(request, "$path.$i", list[i])
)); | |
| 255 } | |
| 256 return result; | |
| 257 } | |
| 258 | |
| 259 /** | |
| 260 * Validate that the datum is a string, and return it. | |
| 261 */ | |
| 262 String asString() { | |
| 263 if (datum is! String) { | |
| 264 throw new RequestFailure(new Response.invalidParameter(request, path, | |
| 265 "be a string")); | |
| 266 } | |
| 267 return datum; | |
| 268 } | |
| 269 | |
| 270 /** | |
| 271 * Determine if the datum is a list of strings. Note: null is considered a | |
| 272 * synonym for the empty list. | |
| 273 */ | |
| 274 bool get isStringList { | |
| 275 if (!isList) { | |
| 276 return false; | |
| 277 } | |
| 278 for (var element in _asList()) { | |
| 279 if (element is! String) { | |
| 280 return false; | |
| 281 } | |
| 282 } | |
| 283 return true; | |
| 284 } | |
| 285 | |
| 286 /** | |
| 287 * Validate that the datum is a list of strings, and return it. Note: null | |
| 288 * is considered a synonym for the empty list. | |
| 289 */ | |
| 290 List<String> asStringList() { | |
| 291 if (!isStringList) { | |
| 292 throw new RequestFailure(new Response.invalidParameter(request, path, | |
| 293 "be a list of strings")); | |
| 294 } | |
| 295 return _asList(); | |
| 296 } | |
| 297 | |
| 298 /** | |
| 299 * Determine if the datum is a map. Note: null is considered a synonym for | |
| 300 * the empty map. | |
| 301 */ | |
| 302 bool get isMap { | |
| 303 return datum == null || datum is Map; | |
| 304 } | |
| 305 | |
| 306 /** | |
| 307 * Validate that the datum is a map, and return it in raw form. | |
| 308 */ | |
| 309 Map<String, Object> _asMap() { | |
| 310 if (!isMap) { | |
| 311 throw new RequestFailure(new Response.invalidParameter(request, path, | |
| 312 "be a map")); | |
| 313 } | |
| 314 if (datum == null) { | |
| 315 return {}; | |
| 316 } else { | |
| 317 return datum; | |
| 318 } | |
| 319 } | |
| 320 | |
| 321 /** | |
| 322 * Determine if the datum is a map whose values are all strings. Note: null | |
| 323 * is considered a synonym for the empty map. | |
| 324 * | |
| 325 * Note: we can safely assume that the keys are all strings, since JSON maps | |
| 326 * cannot have any other key type. | |
| 327 */ | |
| 328 bool get isStringMap { | |
| 329 if (!isMap) { | |
| 330 return false; | |
| 331 } | |
| 332 for (var value in _asMap().values) { | |
| 333 if (value is! String) { | |
| 334 return false; | |
| 335 } | |
| 336 } | |
| 337 return true; | |
| 338 } | |
| 339 | |
| 340 /** | |
| 341 * Validate that the datum is a map from strings to strings, and return it. | |
| 342 */ | |
| 343 Map<String, String> asStringMap() { | |
| 344 if (!isStringMap) { | |
| 345 throw new RequestFailure(new Response.invalidParameter(request, path, | |
| 346 "be a string map")); | |
| 347 } | |
| 348 return _asMap(); | |
| 349 } | |
| 350 | |
| 351 /** | |
| 352 * Determine if the datum is a map whose values are all string lists. Note: | |
| 353 * null is considered a synonym for the empty map. | |
| 354 * | |
| 355 * Note: we can safely assume that the keys are all strings, since JSON maps | |
| 356 * cannot have any other key type. | |
| 357 */ | |
| 358 bool get isStringListMap { | |
| 359 if (!isMap) { | |
| 360 return false; | |
| 361 } | |
| 362 for (var value in _asMap().values) { | |
| 363 if (value is! List) { | |
| 364 return false; | |
| 365 } | |
| 366 for (var listItem in value) { | |
| 367 if (listItem is! String) { | |
| 368 return false; | |
| 369 } | |
| 370 } | |
| 371 } | |
| 372 return true; | |
| 373 } | |
| 374 | |
| 375 /** | |
| 376 * Validate that the datum is a map from strings to string lists, and return | |
| 377 * it. | |
| 378 */ | |
| 379 Map<String, List<String>> asStringListMap() { | |
| 380 if (!isStringListMap) { | |
| 381 throw new RequestFailure(new Response.invalidParameter(request, path, | |
| 382 "be a string list map")); | |
| 383 } | |
| 384 return _asMap(); | |
| 385 } | |
| 386 | |
| 387 bool get isNull => datum == null; | |
| 388 } | |
| 389 | |
| 390 /** | |
| 391 * Instances of the class [Response] represent a response to a request. | 113 * Instances of the class [Response] represent a response to a request. |
| 392 */ | 114 */ |
| 393 class Response { | 115 class Response { |
| 394 /** | 116 /** |
| 395 * The [Response] instance that is returned when a real [Response] cannot | 117 * The [Response] instance that is returned when a real [Response] cannot |
| 396 * be provided at the moment. | 118 * be provided at the moment. |
| 397 */ | 119 */ |
| 398 static final Response DELAYED_RESPONSE = new Response('DELAYED_RESPONSE'); | 120 static final Response DELAYED_RESPONSE = new Response('DELAYED_RESPONSE'); |
| 399 | 121 |
| 400 /** | 122 /** |
| (...skipping 145 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 546 response.setResult(key, value); | 268 response.setResult(key, value); |
| 547 }); | 269 }); |
| 548 } | 270 } |
| 549 return response; | 271 return response; |
| 550 } catch (exception) { | 272 } catch (exception) { |
| 551 return null; | 273 return null; |
| 552 } | 274 } |
| 553 } | 275 } |
| 554 | 276 |
| 555 /** | 277 /** |
| 556 * Return the value of the result field with the given [name]. | |
| 557 */ | |
| 558 Object getResult(String name) { | |
| 559 return result == null ? null : result[name]; | |
| 560 } | |
| 561 | |
| 562 /** | |
| 563 * Set the value of the result field with the given [name] to the given [value
]. | 278 * Set the value of the result field with the given [name] to the given [value
]. |
| 564 */ | 279 */ |
| 565 void setResult(String name, Object value) { | 280 void setResult(String name, Object value) { |
| 566 if (result == null) { | 281 if (result == null) { |
| 567 result = new HashMap<String, Object>(); | 282 result = new HashMap<String, Object>(); |
| 568 } | 283 } |
| 569 result[name] = _toJson(value); | 284 result[name] = _toJson(value); |
| 570 } | 285 } |
| 571 | 286 |
| 572 /** | 287 /** |
| (...skipping 307 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 880 */ | 595 */ |
| 881 _toJson(Object value) { | 596 _toJson(Object value) { |
| 882 if (value is HasToJson) { | 597 if (value is HasToJson) { |
| 883 return value.toJson(); | 598 return value.toJson(); |
| 884 } | 599 } |
| 885 if (value is Iterable) { | 600 if (value is Iterable) { |
| 886 return value.map((item) => _toJson(item)).toList(); | 601 return value.map((item) => _toJson(item)).toList(); |
| 887 } | 602 } |
| 888 return value; | 603 return value; |
| 889 } | 604 } |
| OLD | NEW |