OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2017, 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 /** |
| 6 * Data structures representing an API definition, and visitor base classes |
| 7 * for visiting those data structures. |
| 8 */ |
| 9 import 'dart:collection'; |
| 10 |
| 11 import 'package:html/dom.dart' as dom; |
| 12 |
| 13 /** |
| 14 * Toplevel container for the API. |
| 15 */ |
| 16 class Api extends ApiNode { |
| 17 final String version; |
| 18 final List<Domain> domains; |
| 19 final Types types; |
| 20 final Refactorings refactorings; |
| 21 |
| 22 Api(this.version, this.domains, this.types, this.refactorings, |
| 23 dom.Element html, |
| 24 {bool experimental}) |
| 25 : super(html, experimental); |
| 26 } |
| 27 |
| 28 /** |
| 29 * Base class for objects in the API model. |
| 30 */ |
| 31 class ApiNode { |
| 32 /** |
| 33 * A flag to indicate if this API is experimental. |
| 34 */ |
| 35 final bool experimental; |
| 36 |
| 37 /** |
| 38 * Html element representing this part of the API. |
| 39 */ |
| 40 final dom.Element html; |
| 41 |
| 42 ApiNode(this.html, bool experimental) |
| 43 : this.experimental = experimental ?? false; |
| 44 } |
| 45 |
| 46 /** |
| 47 * Base class for visiting the API definition. |
| 48 */ |
| 49 abstract class ApiVisitor<T> { |
| 50 /** |
| 51 * Dispatch the given [type] to the visitor. |
| 52 */ |
| 53 T visitTypeDecl(TypeDecl type) => type.accept(this) as T; |
| 54 T visitTypeEnum(TypeEnum typeEnum); |
| 55 T visitTypeList(TypeList typeList); |
| 56 T visitTypeMap(TypeMap typeMap); |
| 57 T visitTypeObject(TypeObject typeObject); |
| 58 T visitTypeReference(TypeReference typeReference); |
| 59 |
| 60 T visitTypeUnion(TypeUnion typeUnion); |
| 61 } |
| 62 |
| 63 /** |
| 64 * Definition of a single domain. |
| 65 */ |
| 66 class Domain extends ApiNode { |
| 67 final String name; |
| 68 final List<Request> requests; |
| 69 final List<Notification> notifications; |
| 70 |
| 71 Domain(this.name, this.requests, this.notifications, dom.Element html, |
| 72 {bool experimental}) |
| 73 : super(html, experimental); |
| 74 } |
| 75 |
| 76 /** |
| 77 * API visitor that visits the entire API hierarchically by default. |
| 78 */ |
| 79 class HierarchicalApiVisitor extends ApiVisitor { |
| 80 /** |
| 81 * The API to visit. |
| 82 */ |
| 83 final Api api; |
| 84 |
| 85 HierarchicalApiVisitor(this.api); |
| 86 |
| 87 /** |
| 88 * If [type] is a [TypeReference] that is defined in the API, follow the |
| 89 * chain until a non-[TypeReference] is found, if possible. |
| 90 * |
| 91 * If it is not possible (because the chain ends with a [TypeReference] that |
| 92 * is not defined in the API), then that final [TypeReference] is returned. |
| 93 */ |
| 94 TypeDecl resolveTypeReferenceChain(TypeDecl type) { |
| 95 while (type is TypeReference && api.types.containsKey(type.typeName)) { |
| 96 type = api.types[(type as TypeReference).typeName].type; |
| 97 } |
| 98 return type; |
| 99 } |
| 100 |
| 101 void visitApi() { |
| 102 api.domains.forEach(visitDomain); |
| 103 visitTypes(api.types); |
| 104 visitRefactorings(api.refactorings); |
| 105 } |
| 106 |
| 107 void visitDomain(Domain domain) { |
| 108 domain.requests.forEach(visitRequest); |
| 109 domain.notifications.forEach(visitNotification); |
| 110 } |
| 111 |
| 112 void visitNotification(Notification notification) { |
| 113 if (notification.params != null) { |
| 114 visitTypeDecl(notification.params); |
| 115 } |
| 116 } |
| 117 |
| 118 void visitRefactoring(Refactoring refactoring) { |
| 119 if (refactoring.feedback != null) { |
| 120 visitTypeDecl(refactoring.feedback); |
| 121 } |
| 122 if (refactoring.options != null) { |
| 123 visitTypeDecl(refactoring.options); |
| 124 } |
| 125 } |
| 126 |
| 127 void visitRefactorings(Refactorings refactorings) { |
| 128 refactorings?.forEach(visitRefactoring); |
| 129 } |
| 130 |
| 131 void visitRequest(Request request) { |
| 132 if (request.params != null) { |
| 133 visitTypeDecl(request.params); |
| 134 } |
| 135 if (request.result != null) { |
| 136 visitTypeDecl(request.result); |
| 137 } |
| 138 } |
| 139 |
| 140 void visitTypeDefinition(TypeDefinition typeDefinition) { |
| 141 visitTypeDecl(typeDefinition.type); |
| 142 } |
| 143 |
| 144 @override |
| 145 void visitTypeEnum(TypeEnum typeEnum) { |
| 146 typeEnum.values.forEach(visitTypeEnumValue); |
| 147 } |
| 148 |
| 149 void visitTypeEnumValue(TypeEnumValue typeEnumValue) {} |
| 150 |
| 151 @override |
| 152 void visitTypeList(TypeList typeList) { |
| 153 visitTypeDecl(typeList.itemType); |
| 154 } |
| 155 |
| 156 @override |
| 157 void visitTypeMap(TypeMap typeMap) { |
| 158 visitTypeDecl(typeMap.keyType); |
| 159 visitTypeDecl(typeMap.valueType); |
| 160 } |
| 161 |
| 162 @override |
| 163 void visitTypeObject(TypeObject typeObject) { |
| 164 typeObject.fields.forEach(visitTypeObjectField); |
| 165 } |
| 166 |
| 167 void visitTypeObjectField(TypeObjectField typeObjectField) { |
| 168 visitTypeDecl(typeObjectField.type); |
| 169 } |
| 170 |
| 171 @override |
| 172 void visitTypeReference(TypeReference typeReference) {} |
| 173 |
| 174 void visitTypes(Types types) { |
| 175 types.forEach(visitTypeDefinition); |
| 176 } |
| 177 |
| 178 @override |
| 179 void visitTypeUnion(TypeUnion typeUnion) { |
| 180 typeUnion.choices.forEach(visitTypeDecl); |
| 181 } |
| 182 } |
| 183 |
| 184 /** |
| 185 * Description of a notification method. |
| 186 */ |
| 187 class Notification extends ApiNode { |
| 188 /** |
| 189 * Name of the domain enclosing this request. |
| 190 */ |
| 191 final String domainName; |
| 192 |
| 193 /** |
| 194 * Name of the notification, without the domain prefix. |
| 195 */ |
| 196 final String event; |
| 197 |
| 198 /** |
| 199 * Type of the object associated with the "params" key in the notification |
| 200 * object, or null if the notification has no parameters. |
| 201 */ |
| 202 final TypeObject params; |
| 203 |
| 204 Notification(this.domainName, this.event, this.params, dom.Element html, |
| 205 {bool experimental}) |
| 206 : super(html, experimental); |
| 207 |
| 208 /** |
| 209 * Get the name of the notification, including the domain prefix. |
| 210 */ |
| 211 String get longEvent => '$domainName.$event'; |
| 212 |
| 213 /** |
| 214 * Get the full type of the notification object, including the common "id" |
| 215 * and "error" fields. |
| 216 */ |
| 217 TypeDecl get notificationType { |
| 218 List<TypeObjectField> fields = [ |
| 219 new TypeObjectField('event', new TypeReference('String', null), null, |
| 220 value: '$domainName.$event') |
| 221 ]; |
| 222 if (params != null) { |
| 223 fields.add(new TypeObjectField('params', params, null)); |
| 224 } |
| 225 return new TypeObject(fields, null); |
| 226 } |
| 227 } |
| 228 |
| 229 /** |
| 230 * Description of a single refactoring. |
| 231 */ |
| 232 class Refactoring extends ApiNode { |
| 233 /** |
| 234 * Name of the refactoring. This should match one of the values allowed for |
| 235 * RefactoringKind. |
| 236 */ |
| 237 final String kind; |
| 238 |
| 239 /** |
| 240 * Type of the refactoring feedback, or null if the refactoring has no |
| 241 * feedback. |
| 242 */ |
| 243 final TypeObject feedback; |
| 244 |
| 245 /** |
| 246 * Type of the refactoring options, or null if the refactoring has no options. |
| 247 */ |
| 248 final TypeObject options; |
| 249 |
| 250 Refactoring(this.kind, this.feedback, this.options, dom.Element html, |
| 251 {bool experimental}) |
| 252 : super(html, experimental); |
| 253 } |
| 254 |
| 255 /** |
| 256 * A collection of refactoring definitions. |
| 257 */ |
| 258 class Refactorings extends ApiNode with IterableMixin<Refactoring> { |
| 259 final List<Refactoring> refactorings; |
| 260 |
| 261 Refactorings(this.refactorings, dom.Element html, {bool experimental}) |
| 262 : super(html, experimental); |
| 263 |
| 264 @override |
| 265 Iterator<Refactoring> get iterator => refactorings.iterator; |
| 266 } |
| 267 |
| 268 /** |
| 269 * Description of a request method. |
| 270 */ |
| 271 class Request extends ApiNode { |
| 272 /** |
| 273 * Name of the domain enclosing this request. |
| 274 */ |
| 275 final String domainName; |
| 276 |
| 277 /** |
| 278 * Name of the request, without the domain prefix. |
| 279 */ |
| 280 final String method; |
| 281 |
| 282 /** |
| 283 * Type of the object associated with the "params" key in the request object, |
| 284 * or null if the request has no parameters. |
| 285 */ |
| 286 final TypeObject params; |
| 287 |
| 288 /** |
| 289 * Type of the object associated with the "result" key in the response object, |
| 290 * or null if the response has no results. |
| 291 */ |
| 292 final TypeObject result; |
| 293 |
| 294 Request( |
| 295 this.domainName, this.method, this.params, this.result, dom.Element html, |
| 296 {bool experimental}) |
| 297 : super(html, experimental); |
| 298 |
| 299 /** |
| 300 * Get the name of the request, including the domain prefix. |
| 301 */ |
| 302 String get longMethod => '$domainName.$method'; |
| 303 |
| 304 /** |
| 305 * Get the full type of the request object, including the common "id" and |
| 306 * "method" fields. |
| 307 */ |
| 308 TypeDecl get requestType { |
| 309 List<TypeObjectField> fields = [ |
| 310 new TypeObjectField('id', new TypeReference('String', null), null), |
| 311 new TypeObjectField('method', new TypeReference('String', null), null, |
| 312 value: '$domainName.$method') |
| 313 ]; |
| 314 if (params != null) { |
| 315 fields.add(new TypeObjectField('params', params, null)); |
| 316 } |
| 317 return new TypeObject(fields, null); |
| 318 } |
| 319 |
| 320 /** |
| 321 * Get the full type of the response object, including the common "id" and |
| 322 * "error" fields. |
| 323 */ |
| 324 TypeDecl get responseType { |
| 325 List<TypeObjectField> fields = [ |
| 326 new TypeObjectField('id', new TypeReference('String', null), null), |
| 327 new TypeObjectField( |
| 328 'error', new TypeReference('RequestError', null), null, |
| 329 optional: true) |
| 330 ]; |
| 331 if (result != null) { |
| 332 fields.add(new TypeObjectField('result', result, null)); |
| 333 } |
| 334 return new TypeObject(fields, null); |
| 335 } |
| 336 } |
| 337 |
| 338 /** |
| 339 * Base class for all possible types. |
| 340 */ |
| 341 abstract class TypeDecl extends ApiNode { |
| 342 TypeDecl(dom.Element html, bool experimental) : super(html, experimental); |
| 343 |
| 344 accept(ApiVisitor visitor); |
| 345 } |
| 346 |
| 347 /** |
| 348 * Description of a named type definition. |
| 349 */ |
| 350 class TypeDefinition extends ApiNode { |
| 351 final String name; |
| 352 final TypeDecl type; |
| 353 |
| 354 TypeDefinition(this.name, this.type, dom.Element html, {bool experimental}) |
| 355 : super(html, experimental); |
| 356 } |
| 357 |
| 358 /** |
| 359 * Type of an enum. We represent enums in JSON as strings, so this type |
| 360 * declaration simply lists the allowed values. |
| 361 */ |
| 362 class TypeEnum extends TypeDecl { |
| 363 final List<TypeEnumValue> values; |
| 364 |
| 365 TypeEnum(this.values, dom.Element html, {bool experimental}) |
| 366 : super(html, experimental); |
| 367 |
| 368 @override |
| 369 accept(ApiVisitor visitor) => visitor.visitTypeEnum(this); |
| 370 } |
| 371 |
| 372 /** |
| 373 * Description of a single allowed value for an enum. |
| 374 */ |
| 375 class TypeEnumValue extends ApiNode { |
| 376 final String value; |
| 377 |
| 378 TypeEnumValue(this.value, dom.Element html, {bool experimental}) |
| 379 : super(html, experimental); |
| 380 } |
| 381 |
| 382 /** |
| 383 * Type of a JSON list. |
| 384 */ |
| 385 class TypeList extends TypeDecl { |
| 386 final TypeDecl itemType; |
| 387 |
| 388 TypeList(this.itemType, dom.Element html, {bool experimental}) |
| 389 : super(html, experimental); |
| 390 |
| 391 @override |
| 392 accept(ApiVisitor visitor) => visitor.visitTypeList(this); |
| 393 } |
| 394 |
| 395 /** |
| 396 * Type of a JSON map. |
| 397 */ |
| 398 class TypeMap extends TypeDecl { |
| 399 /** |
| 400 * Type of map keys. Note that since JSON map keys must always be strings, |
| 401 * this must either be a [TypeReference] for [String], or a [TypeReference] |
| 402 * to a type which is defined in the API as an enum or a synonym for [String]. |
| 403 */ |
| 404 final TypeReference keyType; |
| 405 |
| 406 /** |
| 407 * Type of map values. |
| 408 */ |
| 409 final TypeDecl valueType; |
| 410 |
| 411 TypeMap(this.keyType, this.valueType, dom.Element html, {bool experimental}) |
| 412 : super(html, experimental); |
| 413 |
| 414 @override |
| 415 accept(ApiVisitor visitor) => visitor.visitTypeMap(this); |
| 416 } |
| 417 |
| 418 /** |
| 419 * Type of a JSON object with specified fields, some of which may be optional. |
| 420 */ |
| 421 class TypeObject extends TypeDecl { |
| 422 final List<TypeObjectField> fields; |
| 423 |
| 424 TypeObject(this.fields, dom.Element html, {bool experimental}) |
| 425 : super(html, experimental); |
| 426 |
| 427 @override |
| 428 accept(ApiVisitor visitor) => visitor.visitTypeObject(this); |
| 429 |
| 430 /** |
| 431 * Return the field with the given [name], or null if there is no such field. |
| 432 */ |
| 433 TypeObjectField getField(String name) { |
| 434 for (TypeObjectField field in fields) { |
| 435 if (field.name == name) { |
| 436 return field; |
| 437 } |
| 438 } |
| 439 return null; |
| 440 } |
| 441 } |
| 442 |
| 443 /** |
| 444 * Description of a single field in a [TypeObject]. |
| 445 */ |
| 446 class TypeObjectField extends ApiNode { |
| 447 final String name; |
| 448 final TypeDecl type; |
| 449 final bool optional; |
| 450 |
| 451 /** |
| 452 * Value that the field is required to contain, or null if it may vary. |
| 453 */ |
| 454 final Object value; |
| 455 |
| 456 TypeObjectField(this.name, this.type, dom.Element html, |
| 457 {this.optional: false, this.value, bool experimental}) |
| 458 : super(html, experimental); |
| 459 } |
| 460 |
| 461 /** |
| 462 * A reference to a type which is either defined elsewhere in the API or which |
| 463 * is built-in ([String], [bool], or [int]). |
| 464 */ |
| 465 class TypeReference extends TypeDecl { |
| 466 final String typeName; |
| 467 |
| 468 TypeReference(this.typeName, dom.Element html, {bool experimental}) |
| 469 : super(html, experimental) { |
| 470 if (typeName.isEmpty) { |
| 471 throw new Exception('Empty type name'); |
| 472 } |
| 473 } |
| 474 |
| 475 @override |
| 476 accept(ApiVisitor visitor) => visitor.visitTypeReference(this); |
| 477 } |
| 478 |
| 479 /** |
| 480 * A collection of type definitions. |
| 481 */ |
| 482 class Types extends ApiNode with IterableMixin<TypeDefinition> { |
| 483 final Map<String, TypeDefinition> types; |
| 484 |
| 485 Types(this.types, dom.Element html, {bool experimental}) |
| 486 : super(html, experimental); |
| 487 |
| 488 @override |
| 489 Iterator<TypeDefinition> get iterator => types.values.iterator; |
| 490 |
| 491 Iterable<String> get keys => types.keys; |
| 492 |
| 493 TypeDefinition operator [](String typeName) => types[typeName]; |
| 494 |
| 495 bool containsKey(String typeName) => types.containsKey(typeName); |
| 496 } |
| 497 |
| 498 /** |
| 499 * Type which represents a union among multiple choices. |
| 500 */ |
| 501 class TypeUnion extends TypeDecl { |
| 502 final List<TypeDecl> choices; |
| 503 |
| 504 /** |
| 505 * The field that is used to disambiguate this union |
| 506 */ |
| 507 final String field; |
| 508 |
| 509 TypeUnion(this.choices, this.field, dom.Element html, {bool experimental}) |
| 510 : super(html, experimental); |
| 511 |
| 512 @override |
| 513 accept(ApiVisitor visitor) => visitor.visitTypeUnion(this); |
| 514 } |
OLD | NEW |