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

knowledgepixels / nanopub-registry / 24127979685

08 Apr 2026 09:21AM UTC coverage: 32.613%. Remained the same
24127979685

push

github

tkuhn
fix: use counter field for jelly endpoint compatibility with old nanopubs

Nanopubs loaded by older code only have the "counter" field, not
"seqNum". The jelly endpoint filtered on "seqNum" which excluded all
old nanopubs, causing query services to sync only a handful.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

264 of 898 branches covered (29.4%)

Branch coverage included in aggregate %.

793 of 2343 relevant lines covered (33.85%)

5.67 hits per line

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

0.0
src/main/java/com/knowledgepixels/registry/ListPage.java
1
package com.knowledgepixels.registry;
2

3
import com.google.gson.Gson;
4
import com.mongodb.client.ClientSession;
5
import com.mongodb.client.MongoCursor;
6
import io.vertx.ext.web.RoutingContext;
7
import org.bson.Document;
8
import org.bson.conversions.Bson;
9
import org.nanopub.jelly.NanopubStream;
10

11
import java.io.IOException;
12
import java.net.URLEncoder;
13
import java.util.ArrayList;
14
import java.util.List;
15

16
import static com.knowledgepixels.registry.RegistryDB.collection;
17
import static com.knowledgepixels.registry.RegistryDB.unhash;
18
import static com.knowledgepixels.registry.Utils.*;
19
import static com.mongodb.client.model.Aggregates.*;
20
import static com.mongodb.client.model.Filters.gt;
21
import static com.mongodb.client.model.Indexes.ascending;
22
import static com.mongodb.client.model.Indexes.descending;
23
import static com.mongodb.client.model.Projections.exclude;
24
import static com.mongodb.client.model.Projections.include;
25

26
public class ListPage extends Page {
27

28
    private static final Gson gson = new Gson();
×
29

30
    public static void show(RoutingContext context) {
31
        ListPage page;
32
        try (ClientSession s = RegistryDB.getClient().startSession()) {
×
33
            // No transaction here: the nanopubs.jelly endpoint streams large result sets
34
            // that would exceed MongoDB's transaction timeout.
35
            page = new ListPage(s, context);
×
36
            page.show();
×
37
        } catch (IOException ex) {
×
38
            ex.printStackTrace();
×
39
        } finally {
40
            context.response().end();
×
41
        }
42
    }
×
43

44
    private ListPage(ClientSession mongoSession, RoutingContext context) {
45
        super(mongoSession, context);
×
46
    }
×
47

48
    protected void show() throws IOException {
49
        RoutingContext context = getContext();
×
50
        String format;
51
        String ext = getExtension();
×
52
        final String req = getRequestString();
×
53
        if ("json".equals(ext)) {
×
54
            format = TYPE_JSON;
×
55
        } else if ("jelly".equals(ext)) {
×
56
            format = TYPE_JELLY;
×
57
        } else if (ext == null || "html".equals(ext)) {
×
58
            format = Utils.getMimeType(context, SUPPORTED_TYPES_LIST);
×
59
        } else {
60
            context.response().setStatusCode(400).setStatusMessage("Invalid request: " + getFullRequest());
×
61
            return;
×
62
        }
63

64
        if (getPresentationFormat() != null) {
×
65
            setRespContentType(getPresentationFormat());
×
66
        } else {
67
            setRespContentType(format);
×
68
        }
69

70
        if (req.matches("/list/[0-9a-f]{64}/([0-9a-f]{64}|\\$)")) {
×
71
            String pubkey = req.replaceFirst("/list/([0-9a-f]{64})/([0-9a-f]{64}|\\$)", "$1");
×
72
            String type = req.replaceFirst("/list/([0-9a-f]{64})/([0-9a-f]{64}|\\$)", "$2");
×
73

74
            if (TYPE_JELLY.equals(format)) {
×
75
                // Determine start position from afterChecksums parameter (comma-separated, geometric fallback)
76
                long afterPosition = -1;
×
77
                String afterChecksums = getParam("afterChecksums", null);
×
78
                if (afterChecksums != null) {
×
79
                    for (String checksum : afterChecksums.split(",")) {
×
80
                        checksum = checksum.trim();
×
81
                        if (checksum.isEmpty()) continue;
×
82
                        Document match = collection("listEntries").find(mongoSession,
×
83
                                new Document("pubkey", pubkey).append("type", type).append("checksum", checksum)).first();
×
84
                        if (match != null) {
×
85
                            long matchPos = match.getLong("position");
×
86
                            if (matchPos > afterPosition) {
×
87
                                afterPosition = matchPos;
×
88
                            }
89
                        }
90
                    }
91
                }
92

93
                // Build pipeline with optional position filter
94
                Document matchFilter = new Document("pubkey", pubkey).append("type", type);
×
95
                if (afterPosition >= 0) {
×
96
                    matchFilter.append("position", new Document("$gt", afterPosition));
×
97
                }
98
                List<Bson> pipeline = List.of(match(matchFilter), sort(ascending("position")),
×
99
                        lookup("nanopubs", "np", "_id", "nanopub"), project(new Document("jelly", "$nanopub.jelly")), unwind("$jelly"));
×
100
                try (var result = collection("listEntries").aggregate(mongoSession, pipeline).cursor()) {
×
101
                    NanopubStream npStream = NanopubStream.fromMongoCursor(result);
×
102
                    BufferOutputStream outputStream = new BufferOutputStream();
×
103
                    npStream.writeToByteStream(outputStream);
×
104
                    context.response().write(outputStream.getBuffer());
×
105
                }
106
            } else {
×
107
                MongoCursor<Document> c = collection("listEntries").find(mongoSession, new Document("pubkey", pubkey).append("type", type)).projection(exclude("_id")).sort(ascending("position")).cursor();
×
108

109
                if (TYPE_JSON.equals(format)) {
×
110
                    println("[");
×
111
                    while (c.hasNext()) {
×
112
                        Document d = c.next();
×
113
                        // Transforming long to int, so the JSON output looks nice:
114
                        // TODO Make this scale beyond the int range
115
                        d.replace("position", d.getLong("position").intValue());
×
116
                        print(d.toJson());
×
117
                        println(c.hasNext() ? "," : "");
×
118
                    }
×
119
                    println("]");
×
120
                } else {
121
                    printHtmlHeader("List for pubkey " + getLabel(pubkey) + " / type " + getLabel(type) + " - Nanopub Registry");
×
122
                    println("<h1>List</h1>");
×
123
                    println("<p><a href=\"/list/" + pubkey + "\">&lt; Pubkey</a></p>");
×
124
                    println("<h3>Formats</h3>");
×
125
                    println("<p>");
×
126
                    println("<a href=\"/list/" + pubkey + "/" + type + ".json\">.json</a> |");
×
127
                    println("<a href=\"/list/" + pubkey + "/" + type + ".json.txt\">.json.txt</a>");
×
128
                    println("</p>");
×
129
                    println("<h3>Pubkey Hash</h3>");
×
130
                    println("<p><code>" + pubkey + "</code></p>");
×
131
                    println("<h3>Type Hash</h3>");
×
132
                    println("<p><code>" + type + "</code></p>");
×
133
                    println("<h3>Entries</h3>");
×
134
                    println("<ol>");
×
135
                    while (c.hasNext()) {
×
136
                        Document d = c.next();
×
137
                        println("<li><a href=\"/np/" + d.getString("np") + "\"><code>" + getLabel(d.getString("np")) + "</code></a></li>");
×
138
                    }
×
139
                    println("</ol>");
×
140
                    printHtmlFooter();
×
141
                }
142
            }
143
        } else if (req.matches("/list/[0-9a-f]{64}")) {
×
144
            String pubkey = req.replaceFirst("/list/([0-9a-f]{64})", "$1");
×
145
            MongoCursor<Document> c = collection("lists").find(mongoSession, new Document("pubkey", pubkey)).projection(exclude("_id")).cursor();
×
146
            if (TYPE_JSON.equals(format)) {
×
147
                println("[");
×
148
                while (c.hasNext()) {
×
149
                    print(c.next().toJson());
×
150
                    println(c.hasNext() ? "," : "");
×
151
                }
152
                println("]");
×
153
            } else {
154
                printHtmlHeader("Accounts for Pubkey " + getLabel(pubkey) + " - Nanopub Registry");
×
155
                println("<h1>Accounts for Pubkey " + getLabel(pubkey) + "</h1>");
×
156
                println("<p><a href=\"/list\">&lt; Account List</a></p>");
×
157
                println("<h3>Formats</h3>");
×
158
                println("<p>");
×
159
                println("<a href=\"/list/" + pubkey + ".json\">.json</a> |");
×
160
                println("<a href=\"/list/" + pubkey + ".json.txt\">.json.txt</a>");
×
161
                println("</p>");
×
162
                println("<h3>Pubkey Hash</h3>");
×
163
                println("<p><code>" + pubkey + "</code></p>");
×
164
                println("<h3>Entry Lists</h3>");
×
165
                println("<ol>");
×
166
                while (c.hasNext()) {
×
167
                    Document d = c.next();
×
168
                    String type = d.getString("type");
×
169
                    println("<li>");
×
170
                    println("<a href=\"/list/" + pubkey + "/" + type + "\"><code>" + getLabel(type) + "</code></a> ");
×
171
                    if (type.equals("$")) {
×
172
                        println("(all types)");
×
173
                    } else {
174
                        String typeUri = unhash(type);
×
175
                        println("(type " + (typeUri != null ? typeUri : type) + ")");
×
176
                    }
177
                    println("</li>");
×
178
                }
×
179
                println("</ol>");
×
180
                printHtmlFooter();
×
181
            }
182
        } else if (req.equals("/list")) {
×
183
            try (var c = collection(Collection.ACCOUNTS.toString()).find(mongoSession).sort(ascending("pubkey")).projection(exclude("_id")).cursor()) {
×
184
                if (TYPE_JSON.equals(format)) {
×
185
                    println("[");
×
186
                    while (c.hasNext()) {
×
187
                        print(c.next().toJson());
×
188
                        println(c.hasNext() ? "," : "");
×
189
                    }
190
                    println("]");
×
191
                } else {
192
                    printHtmlHeader("Account List - Nanopub Registry");
×
193
                    println("<h1>Account List</h1>");
×
194
                    println("<p><a href=\"/\">&lt; Home</a></p>");
×
195
                    println("<h3>Formats</h3>");
×
196
                    println("<p>");
×
197
                    println("<a href=\"list.json\">.json</a> |");
×
198
                    println("<a href=\"list.json.txt\">.json.txt</a>");
×
199
                    println("</p>");
×
200
                    println("<h3>Accounts</h3>");
×
201
                    println("<ol>");
×
202
                    while (c.hasNext()) {
×
203
                        Document d = c.next();
×
204
                        String pubkey = d.getString("pubkey");
×
205
                        if (!pubkey.equals("$")) {
×
206
                            println("<li>");
×
207
                            println("<a href=\"/list/" + pubkey + "\"><code>" + getLabel(pubkey) + "</code></a>");
×
208
                            String a = d.getString("agent");
×
209
                            if (a != null && !a.isBlank()) {
×
210
                                print(" by <a href=\"/agent?id=" + URLEncoder.encode(a, "UTF-8") + "\">" + Utils.getAgentLabel(a) + "</a>");
×
211
                            }
212
                            print(", status: " + d.get("status"));
×
213
                            print(", depth: " + d.get("depth"));
×
214
                            if (d.get("pathCount") != null) {
×
215
                                print(", pathCount: " + d.get("pathCount"));
×
216
                            }
217
                            if (d.get("ratio") != null) {
×
218
                                print(", ratio: " + df8.format(d.get("ratio")));
×
219
                            }
220
                            Document dollarList = RegistryDB.getOne(mongoSession, "lists",
×
221
                                    new Document("pubkey", pubkey).append("type", "$"));
×
222
                            if (dollarList != null && dollarList.get("maxPosition") != null) {
×
223
                                print(", count: " + (dollarList.getLong("maxPosition") + 1));
×
224
                            }
225
                            if (d.get("quota") != null) {
×
226
                                print(", quota: " + d.get("quota"));
×
227
                            }
228
                            println("");
×
229
                            println("</li>");
×
230
                        }
231
                    }
×
232
                    println("</ol>");
×
233
                    printHtmlFooter();
×
234
                }
235
            }
236
        } else if (req.equals("/agent") && context.request().getParam("id") != null) {
×
237
            String agentId = context.request().getParam("id");
×
238
            if (TYPE_JSON.equals(format)) {
×
239
                print(AgentInfo.get(mongoSession, agentId).asJson());
×
240
            } else {
241
                Document agentDoc = RegistryDB.getOne(mongoSession, Collection.AGENTS.toString(), new Document("agent", agentId));
×
242
                printHtmlHeader("Agent " + Utils.getAgentLabel(agentId) + " - Nanopub Registry");
×
243
                println("<h1>Agent " + Utils.getAgentLabel(agentId) + "</h1>");
×
244
                println("<p><a href=\"/agents\">&lt; Agent List</a></p>");
×
245
                println("<h3>Formats</h3>");
×
246
                println("<p>");
×
247
                println("<a href=\"agent.json?id=" + URLEncoder.encode(agentId, "UTF-8") + "\">.json</a> |");
×
248
                println("<a href=\"agent.json.txt?id=" + URLEncoder.encode(agentId, "UTF-8") + "\">.json.txt</a>");
×
249
                println("</p>");
×
250
                println("<h3>ID</h3>");
×
251
                println("<p><a href=\"" + agentId + "\"><code>" + agentId + "</code></a></p>");
×
252
                println("<h3>Properties</h3>");
×
253
                println("<ul>");
×
254
                println("<li>Average path count: " + agentDoc.get("avgPathCount") + "</li>");
×
255
                println("<li>Total ratio: " + agentDoc.get("totalRatio") + "</li>");
×
256
                println("</ul>");
×
257
                println("<h3>Accounts</h3>");
×
258
                println("<p>Count: " + agentDoc.get("accountCount") + "</p>");
×
259
                println("<p><a href=\"agentAccounts?id=" + URLEncoder.encode(agentId, "UTF-8") + "\">&gt; agentAccounts</a></p>");
×
260
                printHtmlFooter();
×
261
            }
262
        } else if (req.equals("/agentAccounts") && context.request().getParam("id") != null) {
×
263
            String agentId = context.request().getParam("id");
×
264
            MongoCursor<Document> c = collection(Collection.ACCOUNTS.toString()).find(mongoSession, new Document("agent", agentId)).projection(exclude("_id")).cursor();
×
265
            if (TYPE_JSON.equals(format)) {
×
266
                println("[");
×
267
                while (c.hasNext()) {
×
268
                    print(c.next().toJson());
×
269
                    println(c.hasNext() ? "," : "");
×
270
                }
271
                println("]");
×
272
            } else {
273
                printHtmlHeader("Accounts of Agent " + Utils.getAgentLabel(agentId) + " - Nanopub Registry");
×
274
                println("<h1>Accounts of Agent " + Utils.getAgentLabel(agentId) + "</h1>");
×
275
                println("<p><a href=\"/agent?id=" + URLEncoder.encode(agentId, "UTF-8") + "\">&lt; Agent</a></p>");
×
276
                println("<h3>Formats</h3>");
×
277
                println("<p>");
×
278
                println("<a href=\"agentAccounts.json?id=" + URLEncoder.encode(agentId, "UTF-8") + "\">.json</a> |");
×
279
                println("<a href=\"agentAccounts.json.txt?id=" + URLEncoder.encode(agentId, "UTF-8") + "\">.json.txt</a>");
×
280
                println("</p>");
×
281
                println("<h3>Account List</h3>");
×
282
                println("<ul>");
×
283
                while (c.hasNext()) {
×
284
                    Document d = c.next();
×
285
                    String pubkey = d.getString("pubkey");
×
286
                    //                                Object iCount = getMaxValue("listEntries", new Document("pubkey", pubkey).append("type", INTRO_TYPE_HASH), "position");
287
                    //                                Object eCount = getMaxValue("listEntries", new Document("pubkey", pubkey).append("type", ENDORSE_TYPE), "position");
288
                    //                                Object fCount = getMaxValue("listEntries", new Document("pubkey", pubkey).append("type", "$"), "position");
289
                    Document dollarList = RegistryDB.getOne(mongoSession, "lists",
×
290
                            new Document("pubkey", pubkey).append("type", "$"));
×
291
                    long npCount = (dollarList != null && dollarList.get("maxPosition") != null)
×
292
                            ? dollarList.getLong("maxPosition") + 1 : 0;
×
293
                    println("<li><a href=\"/list/" + pubkey + "\"><code>" + getLabel(pubkey) + "</code></a> (" + d.get("status") + "), " + "count " + npCount + ", " + "quota " + d.get("quota") + ", " + "ratio " + df8.format(d.get("ratio")) + ", " + "path count " + d.get("pathCount") + "</li>");
×
294
                }
×
295
                println("</ul>");
×
296
                printHtmlFooter();
×
297
            }
298
        } else if (req.equals("/agents")) {
×
299
            MongoCursor<Document> c = collection(Collection.AGENTS.toString()).find(mongoSession).sort(descending("totalRatio")).projection(exclude("_id")).cursor();
×
300
            if (TYPE_JSON.equals(format)) {
×
301
                println("[");
×
302
                while (c.hasNext()) {
×
303
                    print(c.next().toJson());
×
304
                    println(c.hasNext() ? "," : "");
×
305
                }
306
                println("]");
×
307
            } else {
308
                printHtmlHeader("Agent List - Nanopub Registry");
×
309
                println("<h1>Agent List</h1>");
×
310
                println("<p><a href=\"/\">&lt; Home</a></p>");
×
311
                println("<h3>Formats</h3>");
×
312
                println("<p>");
×
313
                println("<a href=\"agents.json\">.json</a> |");
×
314
                println("<a href=\"agents.json.txt\">.json.txt</a>");
×
315
                println("</p>");
×
316
                println("<h3>Agents</h3>");
×
317
                println("<ol>");
×
318
                while (c.hasNext()) {
×
319
                    Document d = c.next();
×
320
                    if (d.get("agent").equals("$")) continue;
×
321
                    String a = d.getString("agent");
×
322
                    int accountCount = d.getInteger("accountCount");
×
323
                    println("<li><a href=\"/agent?id=" + URLEncoder.encode(a, "UTF-8") + "\">" + Utils.getAgentLabel(a) + "</a>, " + accountCount + " account" + (accountCount == 1 ? "" : "s") + ", " + "ratio " + df8.format(d.get("totalRatio")) + ", " + "avg. path count " + df1.format(d.get("avgPathCount")) + "</li>");
×
324
                }
×
325
                println("</ol>");
×
326
                printHtmlFooter();
×
327
            }
328
        } else if (req.equals("/nanopubs")) {
×
329
            if (TYPE_JELLY.equals(format)) {
×
330
                // Return all nanopubs after seqNum X (-1 by default)
331
                // TODO(transition): Remove afterCounter fallback after all peers upgraded
332
                long afterSeqNum;
333
                try {
334
                    afterSeqNum = Long.parseLong(getParam("afterSeqNum",
×
335
                            getParam("afterCounter", "-1")));
×
336
                } catch (NumberFormatException ex) {
×
337
                    context.response().setStatusCode(400).setStatusMessage("Invalid afterSeqNum parameter.");
×
338
                    return;
×
339
                }
×
340
                // TODO(transition): Use "seqNum" once all nanopubs have it and nanopub-java library is updated.
341
                // Old nanopubs may only have "counter", so we filter/sort by "counter" for compatibility.
342
                var pipeline = collection(Collection.NANOPUBS.toString()).find(mongoSession).filter(gt("counter", afterSeqNum)).sort(ascending("counter"))
×
343
                        // NanopubStream.fromMongoCursorWithCounter reads the hardcoded "counter" field
344
                        .projection(include("jelly", "counter"));
×
345

346
                try (var result = pipeline.cursor()) {
×
347
                    NanopubStream npStream = NanopubStream.fromMongoCursorWithCounter(result);
×
348
                    BufferOutputStream outputStream = new BufferOutputStream();
×
349
                    npStream.writeToByteStream(outputStream);
×
350
                    context.response().write(outputStream.getBuffer());
×
351
                }
352
            } else {
×
353
                // Return nanopubs as streamed JSON or HTML
354
                String sortParam = getParam("sort", "date");
×
355

356
                if (TYPE_JSON.equals(format)) {
×
357
                    Bson filter;
358
                    Bson sort;
359
                    if ("id".equals(sortParam)) {
×
360
                        String afterId = getParam("after", "");
×
361
                        filter = afterId.isEmpty() ? new Document() : gt("_id", afterId);
×
362
                        sort = ascending("_id");
×
363
                    } else {
×
364
                        // sort=date (default): latest first, using indexed seqNum field
365
                        filter = new Document();
×
366
                        sort = descending("seqNum");
×
367
                    }
368
                    try (MongoCursor<Document> c = collection(Collection.NANOPUBS.toString()).find(mongoSession)
×
369
                            .filter(filter).sort(sort)
×
370
                            .projection(include("_id")).cursor()) {
×
371
                        println("[");
×
372
                        boolean first = true;
×
373
                        while (c.hasNext()) {
×
374
                            if (!first) println(",");
×
375
                            first = false;
×
376
                            print(gson.toJson(c.next().getString("_id")));
×
377
                        }
378
                        println("\n]");
×
379
                    }
380
                } else {
×
381
                    printHtmlHeader("Nanopubs - Nanopub Registry");
×
382
                    println("<h1>Nanopubs</h1>");
×
383
                    println("<p><a href=\"/\">&lt; Home</a></p>");
×
384
                    println("<h3>All Nanopub IDs (JSON, latest first)</h3>");
×
385
                    println("<p>");
×
386
                    println("<a href=\"nanopubs.json\">.json</a> |");
×
387
                    println("<a href=\"nanopubs.json.txt\">.json.txt</a>");
×
388
                    println("</p>");
×
389
                    println("<h3>All Nanopub IDs (JSON, sorted by artifact code)</h3>");
×
390
                    println("<p>");
×
391
                    println("<a href=\"nanopubs.json?sort=id\">.json</a> |");
×
392
                    println("<a href=\"nanopubs.json.txt?sort=id\">.json.txt</a>");
×
393
                    println("</p>");
×
394
                    println("<h3>All Nanopubs (Jelly)</h3>");
×
395
                    println("<p><a href=\"nanopubs.jelly\">.jelly</a></p>");
×
396
                    println("<h3>Latest Nanopubs (max. 1000)</h3>");
×
397
                    println("<ol>");
×
398
                    try (MongoCursor<Document> c = collection(Collection.NANOPUBS.toString()).find(mongoSession)
×
399
                            .sort(descending("seqNum")).limit(1000).cursor()) {
×
400
                        while (c.hasNext()) {
×
401
                            String npId = c.next().getString("_id");
×
402
                            println("<li><a href=\"/np/" + npId + "\"><code>" + getLabel(npId) + "</code></a></li>");
×
403
                        }
×
404
                    }
405
                    println("</ol>");
×
406
                    printHtmlFooter();
×
407
                }
408
            }
×
409
        } else if (req.equals("/pubkeys")) {
×
410
            try (var c = collection("lists").distinct(mongoSession, "pubkey", String.class).cursor()) {
×
411
                if (TYPE_JSON.equals(format)) {
×
412
                    println("[");
×
413
                    while (c.hasNext()) {
×
414
                        print(gson.toJson(c.next()));
×
415
                        println(c.hasNext() ? "," : "");
×
416
                    }
417
                    println("]");
×
418
                } else {
419
                    printHtmlHeader("Pubkey List - Nanopub Registry");
×
420
                    println("<h1>Pubkey List</h1>");
×
421
                    println("<p><a href=\"/\">&lt; Home</a></p>");
×
422
                    println("<h3>Formats</h3>");
×
423
                    println("<p>");
×
424
                    println("<a href=\"pubkeys.json\">.json</a> |");
×
425
                    println("<a href=\"pubkeys.json.txt\">.json.txt</a>");
×
426
                    println("</p>");
×
427
                    println("<h3>Pubkeys</h3>");
×
428
                    println("<ol>");
×
429
                    while (c.hasNext()) {
×
430
                        String pubkey = c.next();
×
431
                        if (!pubkey.equals("$")) {
×
432
                            println("<li>");
×
433
                            println("<a href=\"/list/" + pubkey + "\"><code>" + getLabel(pubkey) + "</code></a>");
×
434
                            println("</li>");
×
435
                        }
436
                    }
×
437
                    println("</ol>");
×
438
                    printHtmlFooter();
×
439
                }
440
            }
441
        } else {
442
            context.response().setStatusCode(400).setStatusMessage("Invalid request: " + getFullRequest());
×
443
        }
444
    }
×
445

446
    private static String getLabel(Object obj) {
447
        if (obj == null) return null;
×
448
        if (obj.toString().length() < 10) return obj.toString();
×
449
        return obj.toString().substring(0, 10);
×
450
    }
451

452
}
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

© 2026 Coveralls, Inc