1 /** 2 Copyright: © 2013-2016 rejectedsoftware e.K. 3 License: Subject to the terms of the GNU GPLv3 license, as written in the included LICENSE.txt file. 4 Authors: Colden Cullen 5 */ 6 module dubregistry.api; 7 8 import dubregistry.dbcontroller; 9 import dubregistry.registry; 10 11 import std.algorithm.iteration : map; 12 import std.array : array; 13 import std.exception : enforce; 14 import vibe.data.json : Json; 15 import vibe.http.router; 16 import vibe.inet.url; 17 import vibe.textfilter.urlencode; 18 import vibe.web.rest; 19 20 21 /** Registers the DUB registry REST API endpoints in the given router. 22 */ 23 void registerDubRegistryAPI(URLRouter router, DubRegistry registry) 24 { 25 auto pkgs = new LocalDubRegistryAPI(registry); 26 router.registerRestInterface(pkgs, "/api"); 27 router.get("/api/packages/dump", (req, res) @trusted => dumpPackages(req, res, registry)); 28 } 29 30 /// Compatibility alias. 31 deprecated("Use registerDubRegistryAPI instead.") 32 alias registerDubRegistryWebApi = registerDubRegistryAPI; 33 34 35 private void dumpPackages(HTTPServerRequest req, HTTPServerResponse res, DubRegistry registry) 36 { 37 import vibe.data.json : serializeToPrettyJson; 38 import vibe.stream.wrapper : StreamOutputRange; 39 40 res.contentType = "application/json; charset=UTF-8"; 41 res.headers["Content-Encoding"] = "gzip"; // force GZIP compressed response 42 auto dst = StreamOutputRange(res.bodyWriter); 43 dst.put('['); 44 bool first = true; 45 foreach (p; registry.getPackageDump()) { 46 if (!first) dst.put(','); 47 else first = false; 48 serializeToPrettyJson(&dst, p); 49 } 50 dst.put(']'); 51 } 52 53 /** Returns a REST client instance for communicating with a DUB registry's API. 54 55 Params: 56 url = URL of the DUB registry (e.g. "https://code.dlang.org/") 57 */ 58 DubRegistryAPI connectDubRegistryAPI(URL url) 59 { 60 return new RestInterfaceClient!DubRegistryAPI(url); 61 } 62 /// ditto 63 DubRegistryAPI connectDubRegistry(string url) 64 { 65 return connectDubRegistryAPI(URL(url)); 66 } 67 68 interface DubRegistryAPI { 69 @property IPackages packages(); 70 } 71 72 struct SearchResult { string name, description, version_; } 73 74 interface IPackages { 75 @method(HTTPMethod.GET) 76 SearchResult[] search(string q = ""); 77 78 @path(":name/latest") 79 string getLatestVersion(string _name); 80 81 @path(":name/stats") 82 Json getStats(string _name); 83 84 @path(":name/:version/stats") 85 Json getStats(string _name, string _version); 86 87 @path(":name/info") 88 Json getInfo(string _name); 89 90 @path(":name/:version/info") 91 Json getInfo(string _name, string _version); 92 } 93 94 class LocalDubRegistryAPI : DubRegistryAPI { 95 private { 96 Packages m_packages; 97 } 98 99 this(DubRegistry registry) 100 { 101 m_packages = new Packages(registry); 102 } 103 104 @property Packages packages() { return m_packages; } 105 } 106 107 class Packages : IPackages { 108 private { 109 DubRegistry m_registry; 110 } 111 112 this(DubRegistry registry) 113 { 114 m_registry = registry; 115 } 116 117 override { 118 @method(HTTPMethod.GET) 119 SearchResult[] search(string q) { 120 return m_registry.searchPackages(q) 121 .map!(p => SearchResult(p.name, p.info["description"].opt!string, p.version_)) 122 .array; 123 } 124 125 string getLatestVersion(string name) { 126 return m_registry.getLatestVersion(rootOf(name)) 127 .check!(r => r.length)(HTTPStatus.notFound, "Package not found"); 128 } 129 130 Json getStats(string name) { 131 return m_registry.getPackageStats(rootOf(name)) 132 .check!(r => r.type != Json.Type.null_)(HTTPStatus.notFound, "Package not found"); 133 } 134 135 Json getStats(string name, string ver) { 136 return m_registry.getPackageStats(rootOf(name), ver) 137 .check!(r => r.type != Json.Type.null_)(HTTPStatus.notFound, "Package/Version not found"); 138 } 139 140 Json getInfo(string name) { 141 return m_registry.getPackageInfo(rootOf(name)) 142 .check!(r => r.info.type != Json.Type.null_)(HTTPStatus.notFound, "Package/Version not found") 143 .info; 144 } 145 146 Json getInfo(string name, string ver) { 147 return m_registry.getPackageVersionInfo(rootOf(name), ver) 148 .check!(r => r.type != Json.Type.null_)(HTTPStatus.notFound, "Package/Version not found"); 149 } 150 } 151 152 private: 153 string rootOf(string pkg) { 154 import std.algorithm: findSplitBefore; 155 return pkg.urlDecode().findSplitBefore(":")[0]; 156 } 157 } 158 159 160 private auto ref T check(alias cond, T)(auto ref T t, HTTPStatus status, string msg) 161 { 162 enforce(cond(t), new HTTPStatusException(status, msg)); 163 return t; 164 }