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