• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

SingingBush / dubclient / 381

26 Sep 2025 12:09PM UTC coverage: 37.154% (+0.08%) from 37.073%
381

push

github

SingingBush
Add stopbugs plugin and run during verify phase

39 of 242 branches covered (16.12%)

Branch coverage included in aggregate %.

2 of 4 new or added lines in 3 files covered. (50.0%)

34 existing lines in 2 files now uncovered.

243 of 517 relevant lines covered (47.0%)

1.05 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

67.5
/src/main/java/com/singingbush/dubclient/DubClientImpl.java
1
package com.singingbush.dubclient;
2

3
import com.google.gson.Gson;
4
import com.google.gson.GsonBuilder;
5
import com.google.gson.JsonSyntaxException;
6
import com.singingbush.dubclient.data.*;
7

8
import org.jetbrains.annotations.NotNull;
9
import org.jetbrains.annotations.Nullable;
10
import org.slf4j.Logger;
11
import org.slf4j.LoggerFactory;
12

13
import javax.net.ssl.SSLContext;
14
import java.io.*;
15
import java.net.*;
16
import java.net.http.HttpClient;
17
import java.net.http.HttpRequest;
18
import java.net.http.HttpResponse;
19
import java.time.Duration;
20
import java.util.stream.Stream;
21

22
import static java.time.temporal.ChronoUnit.MILLIS;
23

24
/**
25
 * @author Samael Bate (singingbush)
26
 * created on 16/06/18
27
 */
28
class DubClientImpl implements DubClient {
29

30
    private static final Logger log = LoggerFactory.getLogger(DubClientImpl.class);
3✔
31

32
    private static final String APPLICATION_JSON_VALUE = "application/json";
33

34
    private final String scheme;
35
    private final String hostName;
36
    private final int port;
37
    private final Gson gson;
38

39
    private final HttpClient httpClient;
40
    final HttpRequest.Builder requestBuilder;
41

42
    DubClientImpl(
43
        @NotNull final String scheme,
44
        @NotNull String hostName,
45
        int port,
46
        long timeout,
47
        @NotNull String userAgent,
48
        @Nullable final SSLContext sslContext
49
    ) {
3✔
50
        this.scheme = !scheme.isBlank() ? scheme : "https";
3!
51
        this.hostName = !hostName.isBlank() ? hostName : "code.dlang.org";
3!
52
        this.port = port;
3✔
53

54
        final HttpClient.Builder httpClientBuilder = HttpClient.newBuilder()
3✔
55
            .version(HttpClient.Version.HTTP_1_1)
3✔
56
            .followRedirects(HttpClient.Redirect.ALWAYS);
3✔
57

58
        if(sslContext != null) {
3!
UNCOV
59
            httpClientBuilder.sslContext(sslContext);
×
60
        }
61

62
        this.httpClient = httpClientBuilder
3✔
63
            .build();
3✔
64

65
        this.requestBuilder = HttpRequest.newBuilder()
3✔
66
            .version(HttpClient.Version.HTTP_1_1)
3✔
67
            .timeout(Duration.of(timeout, MILLIS))
3✔
68
            .header("Content-Type", APPLICATION_JSON_VALUE)
3✔
69
            .header("Accept", APPLICATION_JSON_VALUE)
3✔
70
            .header("User-Agent", userAgent)
3✔
71
        //.header("Accept-Language", "en-GB")
72
        ;
73
        gson = new GsonBuilder().create();
3✔
74
    }
3✔
75

76
    @Override
77
    public DubProject parseProjectFile(@NotNull File dubFile) {
78
        if (!dubFile.exists()) throw new IllegalArgumentException("dub file does not exist");
2!
79
        if (!dubFile.isFile()) throw new IllegalArgumentException("dub file does not a valid file");
2!
80
        if (!dubFile.canRead()) throw new IllegalArgumentException("dub file cannot be read");
2!
81

82
        final DubFileParser parser = dubFile.getName().endsWith(".json") ? new JsonDubFileParser() : new SdlDubFileParser();
2✔
83

84
        try {
85
            return parser.parse(dubFile);
2✔
NEW
86
        } catch (final IOException e) {
×
NEW
87
            log.error("IO exception parsing '{}'", dubFile.getName(), e);
×
88
        }
UNCOV
89
        return null; // todo: don't return null, throw a custom exception
×
90
    }
91

92
    @Override
93
    public DubProject parseDubJsonFile(@NotNull Reader reader) {
94
        final DubFileParser parser = new JsonDubFileParser();
2✔
95
        return parser.parse(reader);
2✔
96
    }
97

98
    @Override
99
    public DubProject parseDubSdlFile(@NotNull Reader reader) {
100
        final DubFileParser parser = new SdlDubFileParser();
2✔
101
        return parser.parse(reader);
2✔
102
    }
103

104
    @Override
105
    public Stream<SearchResult> search(@NotNull final String text) throws DubRepositoryException {
106
        if (text.isEmpty()) throw new IllegalArgumentException("Search text cannot be blank");
3!
107

108
        try {
109
            final HttpRequest request = this.makeGetRequest("/api/packages/search", String.format("q=%s", text));
3✔
110

111
            return Stream.of(gson.fromJson(this.sendRequest(request, HttpResponse.BodyHandlers.ofString()), SearchResult[].class));
3✔
112
        } catch (URISyntaxException | JsonSyntaxException e) {
2✔
113
            log.error("There was a problem building the URI", e);
2✔
114
            throw new DubRepositoryException("There was a problem building the URI", e);
2✔
115
        }
116
    }
117

118
    @Override
119
    public PackageInfo packageInfo(@NotNull final String packageName) throws DubRepositoryException {
120
        if (packageName.isEmpty()) throw new IllegalArgumentException("Package Name cannot be blank");
2!
121

122
        try {
123
            final HttpRequest request = this.makeGetRequest(String.format("/api/packages/%s/info", packageName), null);
2✔
124

125
            return gson.fromJson(sendRequest(request, HttpResponse.BodyHandlers.ofString()), PackageInfo.class);
2✔
UNCOV
126
        } catch (URISyntaxException | JsonSyntaxException e) {
×
UNCOV
127
            log.error("There was a problem building the URI", e);
×
UNCOV
128
            throw new DubRepositoryException("There was a problem building the URI", e);
×
129
        }
130
    }
131

132
    @Override
133
    public VersionInfo packageInfo(@NotNull final String packageName, @NotNull final String version) throws DubRepositoryException {
134
        if (packageName.isEmpty() || version.isEmpty()) throw new IllegalArgumentException("args cannot be blank");
2!
135

136
        try {
137
            final HttpRequest request = this.makeGetRequest(String.format("/api/packages/%s/%s/info", packageName, version), null);
2✔
138

139
            return gson.fromJson(sendRequest(request, HttpResponse.BodyHandlers.ofString()), VersionInfo.class);
2✔
UNCOV
140
        } catch (URISyntaxException | JsonSyntaxException e) {
×
UNCOV
141
            log.error("There was a problem building the URI", e);
×
UNCOV
142
            throw new DubRepositoryException("There was a problem building the URI", e);
×
143
        }
144
    }
145

146
    @Override
147
    public PackageStats packageStats(@NotNull final String packageName) throws DubRepositoryException {
148
        if (packageName.isEmpty()) throw new IllegalArgumentException("Package Name cannot be blank");
2!
149

150
        try {
151
            final HttpRequest request = this.makeGetRequest(String.format("/api/packages/%s/stats", packageName), null);
2✔
152

153
            return gson.fromJson(sendRequest(request, HttpResponse.BodyHandlers.ofString()), PackageStats.class);
2✔
154
        } catch (URISyntaxException | JsonSyntaxException e) {
×
UNCOV
155
            log.error("There was a problem building the URI", e);
×
UNCOV
156
            throw new DubRepositoryException("There was a problem building the URI", e);
×
157
        }
158
    }
159

160
    @Override
161
    public DownloadStats packageStats(@NotNull final String packageName, @NotNull final String version) throws DubRepositoryException {
162
        if (packageName.isEmpty() || version.isEmpty()) throw new IllegalArgumentException("args cannot be blank");
2!
163

164
        try {
165
            final HttpRequest request = this.makeGetRequest(String.format("/api/packages/%s/%s/stats", packageName, version), null);
2✔
166

167
            return gson.fromJson(sendRequest(request, HttpResponse.BodyHandlers.ofString()), PackageStats.class).getDownloads();
2✔
UNCOV
168
        } catch (URISyntaxException | JsonSyntaxException e) {
×
UNCOV
169
            log.error("There was a problem building the URI", e);
×
UNCOV
170
            throw new DubRepositoryException("There was a problem building the URI", e);
×
171
        }
172
    }
173

174
    @Override
175
    public String latestVersion(@NotNull final String packageName) throws DubRepositoryException {
176
        if (packageName.isEmpty()) throw new IllegalArgumentException("Package Name cannot be blank");
2!
177

178
        try {
179
            final HttpRequest request = this.makeGetRequest(String.format("/api/packages/%s/latest", packageName), null);
2✔
180

181
            return sendRequest(request, HttpResponse.BodyHandlers.ofString()).replaceAll("\"", "");
2✔
182
        } catch (URISyntaxException | JsonSyntaxException e) {
×
183
            log.error("There was a problem building the URI", e);
×
UNCOV
184
            throw new DubRepositoryException("There was a problem building the URI", e);
×
185
        }
186
    }
187

188
    private HttpRequest makeGetRequest(@NotNull final String path, @Nullable final String query) throws URISyntaxException {
189
        return this.requestBuilder
3✔
190
            .uri(
3✔
191
                new URI(this.scheme, null, this.hostName, this.port, path, query, null)
192
            )
193
            .GET()
3✔
194
            .build();
3✔
195
    }
196

197
    private <T> T sendRequest(final HttpRequest request, final HttpResponse.BodyHandler<T> handler) throws DubRepositoryException {
198
        try {
199
            final HttpResponse<T> response = this.httpClient.send(request, handler);
3✔
200
            if(response.statusCode() == 200) {
3✔
201
                final T body = response.body();
3✔
202
                log.debug("Dub server responded with {}, body : {}", response.statusCode(), body);
3✔
203
                return body;
3✔
204
            } else {
205
                final String msg = String.format("Dub server responded with %s, body : %s", response.statusCode(), response.body());
3✔
206
                log.error(msg);
3✔
207

208
                // todo: handle error response payloads
209
//                if (response.body() != null) {
210
//                    gson.fromJson(response.body(), ErrorMessage.class).getStatusMessage();
211
//                }
212

213
                throw new DubRepositoryException(msg);
3✔
214
            }
215
        } catch (IOException | InterruptedException e) {
2✔
216
            throw new DubRepositoryException("Error calling "+this.hostName, e);
2✔
217
        }
218
    }
219

220
    @Override
221
    public String toString() {
UNCOV
222
        final StringBuilder sb = new StringBuilder("DubClientImpl{");
×
UNCOV
223
        sb.append("hostName='").append(hostName).append('\'');
×
UNCOV
224
        sb.append(", httpClient=").append(httpClient);
×
UNCOV
225
        sb.append(", gson=").append(gson);
×
UNCOV
226
        sb.append('}');
×
UNCOV
227
        return sb.toString();
×
228
    }
229
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc