From a92df478e9d89dffe6d16dcf0cdd77f583506e1e Mon Sep 17 00:00:00 2001 From: Joe Conway Date: Tue, 5 Jan 2016 09:45:31 -0500 Subject: [PATCH 1/7] Adding Documentation comments to description for API documenting via analyzer --- lib/base/documentable.dart | 11 +++--- lib/base/http_controller.dart | 25 +++++++++++++ lib/monadart.dart | 1 + pubspec.yaml | 1 + test/base/document_test.dart | 67 +++++++++++++++++++++++++++++++++++ 5 files changed, 100 insertions(+), 5 deletions(-) create mode 100644 test/base/document_test.dart diff --git a/lib/base/documentable.dart b/lib/base/documentable.dart index 0a623a341..ca8a3233f 100644 --- a/lib/base/documentable.dart +++ b/lib/base/documentable.dart @@ -89,10 +89,9 @@ class APISecurityItem { m["description"] = description; switch(type) { - case APISecurityType.basic: - { + case APISecurityType.basic: { m["type"] = "basic"; - } break; + } break; case APISecurityType.oauth2: { m["type"] = "oauth2"; if (flow != null) { @@ -113,6 +112,7 @@ class APIDocumentItem { String path; String method; String securityItemName; + String description; List acceptedContentTypes; List pathParameters; List queryParameters; @@ -120,7 +120,7 @@ class APIDocumentItem { Map asMap() { Map i = {}; - i["description"] = "Description"; + i["description"] = description ?? ""; i["produces"] = responseFormats; var combined = []; @@ -177,8 +177,9 @@ class APIParameter { case "String" : m["type"] = "string"; break; case "bool" : m["type"] = "bool"; break; case "double" : m["type"] = "number"; break; + default: m["type"] = "string"; } return m; } -} \ No newline at end of file +} diff --git a/lib/base/http_controller.dart b/lib/base/http_controller.dart index 7d62b9b6d..7243bbcc2 100644 --- a/lib/base/http_controller.dart +++ b/lib/base/http_controller.dart @@ -269,8 +269,32 @@ abstract class HttpController extends RequestHandler { return mm.metadata.firstWhere((im) => im.reflectee is HttpMethod, orElse: () => null) != null; }); + var reflectedType = reflect(this).type; + var uri = reflectedType.location.sourceUri; + var fileUnit = parseDartFile(uri.path); + + var classUnit = fileUnit.declarations + .where((u) => u is ClassDeclaration) + .firstWhere((ClassDeclaration u) => u.name.token.lexeme == MirrorSystem.getName(reflectedType.simpleName)); + + Map methodMap = {}; + classUnit.childEntities.forEach((child) { + if (child is MethodDeclaration) { + MethodDeclaration c = child; + methodMap[c.name.token.lexeme] = child; + } + }); + return handlerMethodMirrors.map((MethodMirror mm) { var i = new APIDocumentItem(); + + var matchingMethodDeclaration = methodMap[MirrorSystem.getName(mm.simpleName)]; + if (matchingMethodDeclaration != null) { + var comment = matchingMethodDeclaration.documentationComment; + var tokens = comment?.tokens ?? []; + i.description = tokens.map((t) => t.lexeme.trimLeft().substring(3).trim()).join(" "); + } + var httpMethod = mm.metadata.firstWhere((im) => im.reflectee is HttpMethod).reflectee; i.method = httpMethod.method; @@ -308,6 +332,7 @@ abstract class HttpController extends RequestHandler { i.acceptedContentTypes = acceptedContentTypes.map((ct) => "${ct.primaryType}/${ct.subType}").toList(); i.responseFormats = ["${responseContentType.primaryType}/${responseContentType.subType}"]; + return i; }).toList(); } diff --git a/lib/monadart.dart b/lib/monadart.dart index f624cd2e3..9ee7c8267 100644 --- a/lib/monadart.dart +++ b/lib/monadart.dart @@ -13,6 +13,7 @@ import 'package:http/http.dart' as http; import 'package:logging/logging.dart'; import 'package:postgresql/postgresql.dart'; import 'package:matcher/matcher.dart'; +import 'package:analyzer/analyzer.dart'; export 'package:http_server/http_server.dart'; export 'package:logging/logging.dart'; diff --git a/pubspec.yaml b/pubspec.yaml index 383bd0d11..fd4e7230c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -8,6 +8,7 @@ dependencies: crypto: any http: any logging: any + analyzer: any postgresql: '0.3.2' dev_dependencies: diff --git a/test/base/document_test.dart b/test/base/document_test.dart new file mode 100644 index 000000000..790f9c0ed --- /dev/null +++ b/test/base/document_test.dart @@ -0,0 +1,67 @@ +import 'package:test/test.dart'; +import 'package:monadart/monadart.dart'; +import 'dart:mirrors'; +import 'dart:async'; +import 'package:analyzer/analyzer.dart'; +import 'dart:convert'; +main() { + + test("Documentation test", () { + var t = new TController(); + print("${t.document().asMap()}"); + + + }); + + test("Tests", () { +// ApplicationPipeline pipeline = new TPipeline({}); +// pipeline.addRoutes(); +// +// var docs = pipeline.document(); +// var document = new APIDocument() +// ..items = docs; + + }); + +} + +class TPipeline extends ApplicationPipeline { + TPipeline(Map opts) : super(opts); + + void addRoutes() { + router.route("/t").then(new RequestHandlerGenerator()); + } +} + +/// +/// Documentation +/// +class TController extends HttpController { + /// ABCD + /// EFGH + /// IJKL + @httpGet getAll() async { + return new Response.ok(""); + } + /// ABCD + @httpPut putOne(int id) async { + return new Response.ok(""); + } + @httpGet getOne(int id) async { + return new Response.ok(""); + } + + /// MNOP + /// QRST + + @httpGet getTwo(int id, int notID) async { + return new Response.ok(""); + } + /// EFGH + /// IJKL + @httpPost + + Future createOne() async { + return new Response.ok(""); + } +} From b657e7e9fdd3e51572e0bb6af82930bef1bbb257 Mon Sep 17 00:00:00 2001 From: Joe Conway Date: Tue, 5 Jan 2016 11:33:24 -0500 Subject: [PATCH 2/7] Added package reoslution to documenter --- lib/auth/authenticator.dart | 4 ++-- lib/base/documentable.dart | 34 +++++++++++++++++++++++++++++++++- lib/base/http_controller.dart | 4 ++-- lib/base/pipeline.dart | 4 ++-- lib/base/request_handler.dart | 8 ++++---- lib/base/router.dart | 4 ++-- test/base/document_test.dart | 12 +++++++++++- 7 files changed, 56 insertions(+), 14 deletions(-) diff --git a/lib/auth/authenticator.dart b/lib/auth/authenticator.dart index d3b3a378f..43e6644ec 100644 --- a/lib/auth/authenticator.dart +++ b/lib/auth/authenticator.dart @@ -62,8 +62,8 @@ class Authenticator extends RequestHandler { } - List document() { - List items = nextHandler.document(); + List document(PackagePathResolver resolver) { + List items = nextHandler.document(resolver); items.forEach((i) { if (strategy == AuthenticationStrategy.Client) { diff --git a/lib/base/documentable.dart b/lib/base/documentable.dart index ca8a3233f..791aa8dcd 100644 --- a/lib/base/documentable.dart +++ b/lib/base/documentable.dart @@ -68,7 +68,7 @@ class APIDocument { } abstract class APIDocumentable { - List document(); + List document(PackagePathResolver resolver); } enum APISecurityType { @@ -183,3 +183,35 @@ class APIParameter { return m; } } + +class PackagePathResolver { + PackagePathResolver(String packageMapPath) { + var contents = new File(packageMapPath).readAsStringSync(); + var lines = contents + .split("\n") + .where((l) => !l.startsWith("#") && l.indexOf(":") != -1) + .map((l) { + var firstColonIdx = l.indexOf(":"); + var packageName = l.substring(0, firstColonIdx); + var packagePath = l.substring(firstColonIdx + 1, l.length).replaceFirst(r"file://", ""); + return [packageName, packagePath]; + }); + _map = new Map.fromIterable(lines, key: (k) => k.first, value: (v) => v.last); + } + + Map _map; + + String resolve(Uri uri) { + if (uri.scheme == "package") { + var firstElement = uri.pathSegments.first; + var packagePath = _map[firstElement]; + if (packagePath == null) { + throw new Exception("Package $firstElement could not be resolved."); + } + print("$uri"); + var remainingPath = uri.pathSegments.sublist(1).join("/"); + return "$packagePath$remainingPath"; + } + return uri.path; + } +} \ No newline at end of file diff --git a/lib/base/http_controller.dart b/lib/base/http_controller.dart index 7243bbcc2..158d15834 100644 --- a/lib/base/http_controller.dart +++ b/lib/base/http_controller.dart @@ -262,7 +262,7 @@ abstract class HttpController extends RequestHandler { } @override - List document() { + List document(PackagePathResolver resolver) { var handlerMethodMirrors = reflect(this).type.declarations.values .where((dm) => dm is MethodMirror) .where((mm) { @@ -271,7 +271,7 @@ abstract class HttpController extends RequestHandler { var reflectedType = reflect(this).type; var uri = reflectedType.location.sourceUri; - var fileUnit = parseDartFile(uri.path); + var fileUnit = parseDartFile(resolver.resolve(uri)); var classUnit = fileUnit.declarations .where((u) => u is ClassDeclaration) diff --git a/lib/base/pipeline.dart b/lib/base/pipeline.dart index 76388492d..b824bdb20 100644 --- a/lib/base/pipeline.dart +++ b/lib/base/pipeline.dart @@ -69,7 +69,7 @@ abstract class ApplicationPipeline extends RequestHandler { } @override - List document() { - return initialHandler().document(); + List document(PackagePathResolver resolver) { + return initialHandler().document(resolver); } } \ No newline at end of file diff --git a/lib/base/request_handler.dart b/lib/base/request_handler.dart index 668287143..4ca4ddb17 100644 --- a/lib/base/request_handler.dart +++ b/lib/base/request_handler.dart @@ -93,9 +93,9 @@ class RequestHandler implements APIDocumentable { return req; } - List document() { + List document(PackagePathResolver resolver) { if (nextHandler != null) { - return nextHandler.document(); + return nextHandler.document(resolver); } return []; @@ -122,8 +122,8 @@ class RequestHandlerGenerator extends RequestHandler { } @override - List document() { - return instantiate().document(); + List document(PackagePathResolver resolver) { + return instantiate().document(resolver); } } diff --git a/lib/base/router.dart b/lib/base/router.dart index 3725a0337..3580ac03f 100644 --- a/lib/base/router.dart +++ b/lib/base/router.dart @@ -101,11 +101,11 @@ class Router extends RequestHandler { } @override - List document() { + List document(PackagePathResolver resolver) { List items = []; for (var route in _routes) { - var routeItems = route.handler.document(); + var routeItems = route.handler.document(resolver); items.addAll(routeItems.map((i) { i.path = (basePath ?? "") + route.pattern.documentedPathWithVariables(i.pathParameters); diff --git a/test/base/document_test.dart b/test/base/document_test.dart index 790f9c0ed..5e83d117d 100644 --- a/test/base/document_test.dart +++ b/test/base/document_test.dart @@ -4,11 +4,21 @@ import 'dart:mirrors'; import 'dart:async'; import 'package:analyzer/analyzer.dart'; import 'dart:convert'; +import 'dart:io'; + main() { + test("Package resolver", () { + String homeDir = Platform.environment['HOME'] ?? Platform.environment['USERPROFILE']; + var p = new PackagePathResolver(new File(".packages").path); + var resolvedPath = p.resolve(new Uri(scheme: "package", path: "analyzer/file_system/file_system.dart")); + + expect(resolvedPath.endsWith("file_system/file_system.dart"), true); + expect(resolvedPath.startsWith("$homeDir/.pub-cache/hosted/pub.dartlang.org"), true); + }); test("Documentation test", () { var t = new TController(); - print("${t.document().asMap()}"); +// print("${t.document().asMap()}"); }); From e5cd0de34ed678e09b2abe68ed57aed7c162f142 Mon Sep 17 00:00:00 2001 From: Joe Conway Date: Tue, 5 Jan 2016 11:37:43 -0500 Subject: [PATCH 3/7] Removed debug print --- lib/base/documentable.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/base/documentable.dart b/lib/base/documentable.dart index 791aa8dcd..945ead6e8 100644 --- a/lib/base/documentable.dart +++ b/lib/base/documentable.dart @@ -208,7 +208,7 @@ class PackagePathResolver { if (packagePath == null) { throw new Exception("Package $firstElement could not be resolved."); } - print("$uri"); + var remainingPath = uri.pathSegments.sublist(1).join("/"); return "$packagePath$remainingPath"; } From b8eb5adb7386be5e2705e3813b01d1f53acc7d8f Mon Sep 17 00:00:00 2001 From: Joe Conway Date: Tue, 5 Jan 2016 11:41:40 -0500 Subject: [PATCH 4/7] Splits comment lines with newlines instead of spaces to preserve format of comment --- lib/base/http_controller.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/base/http_controller.dart b/lib/base/http_controller.dart index 158d15834..15f67c650 100644 --- a/lib/base/http_controller.dart +++ b/lib/base/http_controller.dart @@ -292,7 +292,7 @@ abstract class HttpController extends RequestHandler { if (matchingMethodDeclaration != null) { var comment = matchingMethodDeclaration.documentationComment; var tokens = comment?.tokens ?? []; - i.description = tokens.map((t) => t.lexeme.trimLeft().substring(3).trim()).join(" "); + i.description = tokens.map((t) => t.lexeme.trimLeft().substring(3).trim()).join("\n"); } var httpMethod = mm.metadata.firstWhere((im) => im.reflectee is HttpMethod).reflectee; From cbcb13d7116dfeb81d46c997fc3369c201efc932 Mon Sep 17 00:00:00 2001 From: Joe Conway Date: Tue, 5 Jan 2016 11:55:18 -0500 Subject: [PATCH 5/7] Added documentation to auth controller --- lib/auth/auth_controller.dart | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/auth/auth_controller.dart b/lib/auth/auth_controller.dart index 6df0f4494..f8f026e10 100644 --- a/lib/auth/auth_controller.dart +++ b/lib/auth/auth_controller.dart @@ -10,6 +10,12 @@ class AuthController create({String grant_type, String username, String password, String refresh_token}) async { var authorizationHeader = request.innerRequest.headers[HttpHeaders.AUTHORIZATION]?.first; From 560fdb898eea77a692d1195b5edbcf13e92d5285 Mon Sep 17 00:00:00 2001 From: Joe Conway Date: Tue, 5 Jan 2016 12:08:42 -0500 Subject: [PATCH 6/7] Added authorization info to authcontroller docs --- lib/auth/auth_controller.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/auth/auth_controller.dart b/lib/auth/auth_controller.dart index f8f026e10..49068c434 100644 --- a/lib/auth/auth_controller.dart +++ b/lib/auth/auth_controller.dart @@ -12,6 +12,8 @@ class AuthController Date: Tue, 5 Jan 2016 16:30:12 -0500 Subject: [PATCH 7/7] Allow updates to nullify relationships --- lib/db/postgresql/postgresl_query.dart | 24 ++++++----- test/db/postgresql/update_test.dart | 56 ++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 11 deletions(-) diff --git a/lib/db/postgresql/postgresl_query.dart b/lib/db/postgresql/postgresl_query.dart index 2ab4c10c7..bb781db5d 100644 --- a/lib/db/postgresql/postgresl_query.dart +++ b/lib/db/postgresql/postgresl_query.dart @@ -102,21 +102,23 @@ class _PostgresqlQuery { return; } - var relatedValue = (value as Model).dynamicBacking[column.relationship.destinationModelKey]; + if (value != null) { + var relatedValue = (value as Model).dynamicBacking[column.relationship.destinationModelKey]; - if (relatedValue == null) { - var thisType = MirrorSystem.getName(reflect(valueObject).type.simpleName); + if (relatedValue == null) { + var thisType = MirrorSystem.getName(reflect(valueObject).type.simpleName); - var relatedType = MirrorSystem.getName(reflectType(column.relationship.destinationType).simpleName); + var relatedType = MirrorSystem.getName(reflectType(column.relationship.destinationType).simpleName); - throw new QueryException( - 500, - "Query object of type ${thisType} contains embedded object of type ${relatedType}," - "but embedded object does not contain foreign model key ${column.relationship.destinationModelKey}", - -1); - } + throw new QueryException(500, "Query object of type ${thisType} contains embedded object of type ${relatedType}," + "but embedded object does not contain foreign model key ${column.relationship.destinationModelKey}", + -1); + } - m[column.name] = relatedValue; + m[column.name] = relatedValue; + } else { + m[column.name] = null; + } } else { m[column.name] = value; } diff --git a/test/db/postgresql/update_test.dart b/test/db/postgresql/update_test.dart index fec475f48..e6e259719 100644 --- a/test/db/postgresql/update_test.dart +++ b/test/db/postgresql/update_test.dart @@ -42,6 +42,30 @@ void main() { expect(result.emailAddress, "2@a.com"); }); + test("Setting relationship to null succeeds", () async { + await generateTemporarySchemaFromModels(adapter, [Child, Parent]); + + var parent = new Parent() + ..name = "Bob"; + var q = new Query() + ..valueObject = parent; + parent = await q.insert(adapter); + + var child = new Child() + ..name = "Fred" + ..parent = parent; + q = new Query() + ..valueObject = child; + child = await q.insert(adapter); + expect(child.parent.id, parent.id); + + q = new Query() + ..predicateObject = (new Child()..id = child.id) + ..valueObject = (new Child()..parent = null); + child = (await q.update(adapter)).first; + expect(child.parent, isNull); + }); + test("Updating non-existant object fails", () async {}); } @@ -60,3 +84,35 @@ class TestModelBacking extends Model { @Attributes(nullable: true, unique: true) String emailAddress; } + +@ModelBacking(ChildBacking) @proxy +class Child extends Object with Model implements ChildBacking { + noSuchMethod(i) => super.noSuchMethod(i); +} + +class ChildBacking { + @Attributes(primaryKey: true, databaseType: "bigserial") + int id; + + String name; + + @Attributes(nullable: true) + @RelationshipAttribute(RelationshipType.belongsTo, "child") + Parent parent; +} + +@ModelBacking(ParentBacking) @proxy +class Parent extends Object with Model implements ChildBacking { + noSuchMethod(i) => super.noSuchMethod(i); +} + +class ParentBacking { + @Attributes(primaryKey: true, databaseType: "bigserial") + int id; + + String name; + + @RelationshipAttribute(RelationshipType.hasOne, "parent") + Child child; +} +