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 }