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 struct DownloadStats { DbDownloadStats downloads; } 74 75 interface IPackages { 76 @safe: 77 78 @method(HTTPMethod.GET) 79 SearchResult[] search(string q = ""); 80 81 @path(":name/latest") 82 string getLatestVersion(string _name); 83 84 @path(":name/stats") 85 DbPackageStats getStats(string _name); 86 87 @path(":name/:version/stats") 88 DownloadStats getStats(string _name, string _version); 89 90 @path(":name/info") 91 Json getInfo(string _name); 92 93 @path(":name/:version/info") 94 Json getInfo(string _name, string _version); 95 } 96 97 class LocalDubRegistryAPI : DubRegistryAPI { 98 private { 99 Packages m_packages; 100 } 101 102 this(DubRegistry registry) 103 { 104 m_packages = new Packages(registry); 105 } 106 107 @property Packages packages() { return m_packages; } 108 } 109 110 class Packages : IPackages { 111 private { 112 DubRegistry m_registry; 113 } 114 115 this(DubRegistry registry) 116 { 117 m_registry = registry; 118 } 119 120 override { 121 @method(HTTPMethod.GET) 122 SearchResult[] search(string q) { 123 return m_registry.searchPackages(q) 124 .map!(p => SearchResult(p.name, p.info["description"].opt!string, p.version_)) 125 .array; 126 } 127 128 string getLatestVersion(string name) { 129 return m_registry.getLatestVersion(rootOf(name)) 130 .check!(r => r.length)(HTTPStatus.notFound, "Package not found"); 131 } 132 133 DbPackageStats getStats(string name) { 134 try { 135 auto stats = m_registry.getPackageStats(rootOf(name)); 136 return stats; 137 } catch (RecordNotFound e) { 138 throw new HTTPStatusException(HTTPStatus.notFound, "Package not found"); 139 } 140 } 141 142 DownloadStats getStats(string name, string ver) { 143 try { 144 return typeof(return)(m_registry.getDownloadStats(rootOf(name), ver)); 145 } catch (RecordNotFound e) { 146 throw new HTTPStatusException(HTTPStatus.notFound, "Package or Version not found"); 147 } 148 } 149 150 Json getInfo(string name) { 151 return m_registry.getPackageInfo(rootOf(name)) 152 .check!(r => r.info.type != Json.Type.null_)(HTTPStatus.notFound, "Package/Version not found") 153 .info; 154 } 155 156 Json getInfo(string name, string ver) { 157 return m_registry.getPackageVersionInfo(rootOf(name), ver) 158 .check!(r => r.type != Json.Type.null_)(HTTPStatus.notFound, "Package/Version not found"); 159 } 160 } 161 162 private: 163 string rootOf(string pkg) @safe { 164 import std.algorithm: findSplitBefore; 165 // FIXME: urlDecode should not be necessary, as the REST paramters are 166 // already decoded. 167 return pkg.urlDecode().findSplitBefore(":")[0]; 168 } 169 } 170 171 172 private auto ref T check(alias cond, T)(auto ref T t, HTTPStatus status, string msg) 173 { 174 enforce(cond(t), new HTTPStatusException(status, msg)); 175 return t; 176 }