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

knowledgepixels / nanopub-registry / 28094324302

24 Jun 2026 11:10AM UTC coverage: 32.271% (+0.2%) from 32.089%
28094324302

Pull #116

github

web-flow
Merge 8d4b30b75 into b71f518fc
Pull Request #116: Enhance and standardize logging across multiple components

312 of 1094 branches covered (28.52%)

Branch coverage included in aggregate %.

1024 of 3046 relevant lines covered (33.62%)

5.62 hits per line

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

29.05
src/main/java/com/knowledgepixels/registry/TrustStatePage.java
1
package com.knowledgepixels.registry;
2

3
import com.mongodb.client.ClientSession;
4
import com.mongodb.client.MongoCursor;
5
import io.vertx.ext.web.RoutingContext;
6
import org.bson.Document;
7
import org.slf4j.Logger;
8
import org.slf4j.LoggerFactory;
9

10
import java.io.IOException;
11
import java.util.List;
12

13
import static com.knowledgepixels.registry.RegistryDB.collection;
14
import static com.knowledgepixels.registry.Utils.TYPE_HTML;
15
import static com.knowledgepixels.registry.Utils.TYPE_JSON;
16
import static com.mongodb.client.model.Projections.exclude;
17
import static com.mongodb.client.model.Sorts.descending;
18

19
/**
20
 * Serves hash-keyed trust state snapshots.
21
 *
22
 * <ul>
23
 *   <li><code>/trust-state</code> — list of retained snapshots (HTML or JSON)</li>
24
 *   <li><code>/trust-state/&lt;hash&gt;</code> — single snapshot (HTML or JSON)</li>
25
 * </ul>
26
 *
27
 * <p>Snapshot content is immutable once a hash is known. Consumers use
28
 * <code>/trust-state/&lt;hash&gt;.json</code> after detecting a hash change (via the
29
 * {@code Nanopub-Registry-Trust-State-Hash} response header) to load the corresponding
30
 * snapshot side-by-side with the previous one.
31
 */
32
public class TrustStatePage extends Page {
33

34
    private static final Logger logger = LoggerFactory.getLogger(TrustStatePage.class);
12✔
35

36
    private static final String SUPPORTED_TYPES = TYPE_JSON + "," + TYPE_HTML;
37

38
    public static void show(RoutingContext context) {
39
        TrustStatePage page;
40
        logger.info("Received trust-state request: {}", context.request().path());
18✔
41
        try (ClientSession s = RegistryDB.getClient().startSession()) {
9✔
42
            s.startTransaction();
6✔
43
            page = new TrustStatePage(s, context);
18✔
44
            page.show();
6✔
45
        } catch (IOException ex) {
×
46
            logger.warn("Failed to show trust-state for request {}: {} ({})", context.request().path(), ex.getMessage(), ex.getClass().getSimpleName(), ex);
×
47
        } finally {
48
            logger.debug("Ending response for trust-state request: {}", context.request().path());
18✔
49
            context.response().end();
12✔
50
        }
51
    }
3✔
52

53
    private TrustStatePage(ClientSession mongoSession, RoutingContext context) {
54
        super(mongoSession, context);
12✔
55
    }
3✔
56

57
    protected void show() throws IOException {
58
        RoutingContext c = getContext();
9✔
59
        String req = getRequestString();
9✔
60
        String ext = getExtension();
9✔
61

62
        logger.debug("Preparing trust-state response for request: {} (ext={})", getFullRequest(), ext);
18✔
63

64
        String format;
65
        if ("json".equals(ext)) {
12✔
66
            format = TYPE_JSON;
9✔
67
        } else if (ext == null || "html".equals(ext)) {
18!
68
            format = Utils.getMimeType(c, SUPPORTED_TYPES);
×
69
        } else {
70
            logger.warn("Invalid trust-state request (unsupported extension) for {}: {}", getFullRequest(), ext);
18✔
71
            c.response().setStatusCode(400).setStatusMessage("Invalid request: " + getFullRequest());
27✔
72
            return;
3✔
73
        }
74

75
        if (getPresentationFormat() != null) {
9!
76
            setRespContentType(getPresentationFormat());
×
77
            logger.debug("Overriding response content type with presentation format: {}", getPresentationFormat());
×
78
        } else {
79
            setRespContentType(format);
9✔
80
            logger.debug("Set response content type: {}", format);
12✔
81
        }
82

83
        if ("/trust-state".equals(req) || "/trust-state/".equals(req)) {
24!
84
            showList(format);
×
85
        } else if (req.matches("/trust-state/[A-Za-z0-9_\\-]+")) {
12✔
86
            String hash = req.substring("/trust-state/".length());
15✔
87
            showDetail(hash, format);
12✔
88
        } else {
3✔
89
            logger.warn("Invalid trust-state request path: {}", getFullRequest());
15✔
90
            c.response().setStatusCode(400).setStatusMessage("Invalid request: " + getFullRequest());
27✔
91
        }
92
    }
3✔
93

94
    private void showList(String format) {
95
        int listed = 0;
×
96
        // Metadata only — the accounts array is heavy and not needed in the index.
97
        try (MongoCursor<Document> it = collection(Collection.TRUST_STATE_SNAPSHOTS.toString())
×
98
                .find(mongoSession)
×
99
                .projection(exclude("accounts"))
×
100
                .sort(descending("trustStateCounter"))
×
101
                .cursor()) {
×
102
            if (TYPE_JSON.equals(format)) {
×
103
                println("[");
×
104
                while (it.hasNext()) {
×
105
                    Document d = it.next();
×
106
                    Document out = new Document()
×
107
                            .append("trustStateHash", d.getString("_id"))
×
108
                            .append("trustStateCounter", d.get("trustStateCounter"))
×
109
                            .append("createdAt", d.get("createdAt"));
×
110
                    print(out.toJson());
×
111
                    println(it.hasNext() ? "," : "");
×
112
                    listed++;
×
113
                }
×
114
                println("]");
×
115
                logger.info("Listed {} trust-state snapshots (format=json) for {}", listed, getFullRequest());
×
116
            } else {
117
                printHtmlHeader("Trust State History - Nanopub Registry");
×
118
                println("<h1>Trust State History</h1>");
×
119
                println("<p><a href=\"/\">&lt; Home</a></p>");
×
120
                println("<h3>Formats</h3>");
×
121
                println("<p>");
×
122
                println("<a href=\"trust-state.json\">.json</a> |");
×
123
                println("<a href=\"trust-state.json.txt\">.json.txt</a>");
×
124
                println("</p>");
×
125
                println("<h3>Past Trust States</h3>");
×
126
                println("<ol>");
×
127
                while (it.hasNext()) {
×
128
                    Document d = it.next();
×
129
                    String hash = d.getString("_id");
×
130
                    println("<li>");
×
131
                    println("<a href=\"/trust-state/" + hash + "\"><code>" + getLabel(hash) + "</code></a>");
×
132
                    print(", counter " + d.get("trustStateCounter"));
×
133
                    Object createdAt = d.get("createdAt");
×
134
                    if (createdAt != null) {
×
135
                        print(", " + createdAt.toString().replaceFirst("\\.[^.]*$", ""));
×
136
                    }
137
                    println("</li>");
×
138
                }
×
139
                println("</ol>");
×
140
                printHtmlFooter();
×
141
                logger.info("Listed {} trust-state snapshots (format=html) for {}", listed, getFullRequest());
×
142
            }
143
        }
144
    }
×
145

146
    private void showDetail(String hash, String format) {
147
        Document snapshot = collection(Collection.TRUST_STATE_SNAPSHOTS.toString())
30✔
148
                .find(mongoSession, new Document("_id", hash)).first();
12✔
149
        if (snapshot == null) {
6✔
150
            logger.warn("Trust state snapshot not found: {} (request={})", hash, getFullRequest());
18✔
151
            getContext().response().setStatusCode(404).setStatusMessage("Trust state snapshot not found");
24✔
152
            return;
3✔
153
        }
154
        logger.info("Serving trust state snapshot {} (format={}) for {}", hash, format, getFullRequest());
54✔
155

156
        if (TYPE_JSON.equals(format)) {
12!
157
            // Content is immutable by construction: the URL encodes a hash that never rewrites.
158
            getContext().response().putHeader("Cache-Control", "public, immutable, max-age=31536000");
21✔
159
            logger.debug("Set immutable cache-control for snapshot {}", hash);
12✔
160
            Document output = new Document()
18✔
161
                    .append("trustStateHash", snapshot.getString("_id"))
15✔
162
                    .append("trustStateCounter", snapshot.get("trustStateCounter"))
15✔
163
                    .append("createdAt", snapshot.get("createdAt"))
15✔
164
                    .append("accounts", snapshot.get("accounts"));
9✔
165
            print(output.toJson());
12✔
166
            return;
3✔
167
        }
168

169
        // HTML detail view
170
        printHtmlHeader("Trust State " + getLabel(hash) + " - Nanopub Registry");
×
171
        println("<h1>Trust State <code>" + getLabel(hash) + "</code></h1>");
×
172
        println("<p><a href=\"/trust-state\">&lt; Trust State History</a></p>");
×
173
        println("<h3>Formats</h3>");
×
174
        println("<p>");
×
175
        println("<a href=\"" + hash + ".json\">.json</a> |");
×
176
        println("<a href=\"" + hash + ".json.txt\">.json.txt</a>");
×
177
        println("</p>");
×
178
        println("<h3>Hash</h3>");
×
179
        println("<p><code>" + hash + "</code></p>");
×
180
        println("<h3>Metadata</h3>");
×
181
        println("<ul>");
×
182
        println("<li><em>trustStateCounter:</em> " + snapshot.get("trustStateCounter") + "</li>");
×
183
        Object createdAt = snapshot.get("createdAt");
×
184
        if (createdAt != null) {
×
185
            println("<li><em>createdAt:</em> " + createdAt.toString().replaceFirst("\\.[^.]*$", "") + "</li>");
×
186
        }
187
        Object accountsObj = snapshot.get("accounts");
×
188
        int accountCount = (accountsObj instanceof List) ? ((List<?>) accountsObj).size() : 0;
×
189
        println("<li><em>accountCount:</em> " + accountCount + "</li>");
×
190
        println("</ul>");
×
191
        println("<h3>Accounts</h3>");
×
192
        println("<ol>");
×
193
        if (accountsObj instanceof List) {
×
194
            for (Object entry : (List<?>) accountsObj) {
×
195
                if (!(entry instanceof Document)) {
×
196
                    continue;
×
197
                }
198
                Document a = (Document) entry;
×
199
                String pubkey = a.getString("pubkey");
×
200
                String agent = a.getString("agent");
×
201
                String name = a.getString("name");
×
202
                println("<li>");
×
203
                println("<a href=\"/list/" + pubkey + "\"><code>" + getLabel(pubkey) + "</code></a>");
×
204
                if (agent != null && !agent.isBlank()) {
×
205
                    print(" by <a href=\"/agent?id=" + Utils.urlEncode(agent) + "\">" + Utils.getAgentLabel(agent) + "</a>");
×
206
                    if (name != null && !name.isBlank()) {
×
207
                        print(" (" + name + ")");
×
208
                    }
209
                }
210
                print(", status: " + a.get("status"));
×
211
                print(", depth: " + a.get("depth"));
×
212
                if (a.get("pathCount") != null) {
×
213
                    print(", pathCount: " + a.get("pathCount"));
×
214
                }
215
                if (a.get("ratio") != null) {
×
216
                    print(", ratio: " + a.get("ratio"));
×
217
                }
218
                if (a.get("quota") != null) {
×
219
                    print(", quota: " + a.get("quota"));
×
220
                }
221
                println("</li>");
×
222
            }
×
223
        }
224
        println("</ol>");
×
225
        printHtmlFooter();
×
226
    }
×
227

228
    private static String getLabel(String s) {
229
        if (s == null) {
×
230
            return null;
×
231
        }
232
        if (s.length() < 10) {
×
233
            return s;
×
234
        }
235
        return s.substring(0, 10);
×
236
    }
237

238
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc