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

nats-io / nats.java / #2101

12 Aug 2025 11:26AM UTC coverage: 95.457% (+0.02%) from 95.433%
#2101

push

github

web-flow
Merge pull request #1387 from nats-io/info-nullability

Ensuring nullability contracts

92 of 92 new or added lines in 10 files covered. (100.0%)

108 existing lines in 12 files now uncovered.

11913 of 12480 relevant lines covered (95.46%)

0.95 hits per line

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

96.7
/src/main/java/io/nats/client/support/NatsUri.java
1
// Copyright 2023 The NATS Authors
2
// Licensed under the Apache License, Version 2.0 (the "License");
3
// you may not use this file except in compliance with the License.
4
// You may obtain a copy of the License at:
5
//
6
// http://www.apache.org/licenses/LICENSE-2.0
7
//
8
// Unless required by applicable law or agreed to in writing, software
9
// distributed under the License is distributed on an "AS IS" BASIS,
10
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
// See the License for the specific language governing permissions and
12
// limitations under the License.
13

14
package io.nats.client.support;
15

16
import io.nats.client.Options;
17
import org.jspecify.annotations.NonNull;
18
import org.jspecify.annotations.Nullable;
19

20
import java.net.URI;
21
import java.net.URISyntaxException;
22
import java.util.List;
23
import java.util.regex.Pattern;
24

25
import static io.nats.client.support.NatsConstants.*;
26

27
public class NatsUri {
28
    private static final int NO_PORT = -1;
29
    private static final String UNABLE_TO_PARSE = "Unable to parse URI string.";
30
    private static final String UNSUPPORTED_SCHEME = "Unsupported NATS URI scheme.";
31
    private static final String URI_E_ALLOW_TRY_PREFIXED = "Illegal character in scheme name at index";
32
    private static final Pattern IPV4_RE = Pattern.compile("(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])");
1✔
33
    private static final String COLON_SLASH_SLASH = "://";
34

35
    public static NatsUri DEFAULT_NATS_URI = new NatsUri();
1✔
36

37
    private final URI uri;
38
    private boolean isSecure;
39
    private boolean isWebsocket;
40
    private boolean hostIsIpAddress;
41

42
    @NonNull
43
    public URI getUri() {
44
        return uri;
1✔
45
    }
46

47
    @NonNull
48
    public String getScheme() {
49
        return uri.getScheme();
1✔
50
    }
51

52
    @NonNull
53
    public String getHost() {
54
        return uri.getHost();
1✔
55
    }
56

57
    public int getPort() {
58
        return uri.getPort();
1✔
59
    }
60

61
    @Nullable
62
    public String getUserInfo() {
63
        return uri.getUserInfo();
1✔
64
    }
65

66
    public boolean isSecure() {
67
        return isSecure;
1✔
68
    }
69

70
    public boolean isWebsocket() {
71
        return isWebsocket;
1✔
72
    }
73

74
    public boolean hostIsIpAddress() {
75
        return hostIsIpAddress;
1✔
76
    }
77

78
    @NonNull
79
    public NatsUri reHost(String newHost) throws URISyntaxException {
80
        String newUrl = (uri.getRawUserInfo() == null)
1✔
81
            ? uri.getScheme() + "://" + newHost + ":" + uri.getPort()
1✔
82
            : uri.getScheme() + "://" + uri.getRawUserInfo() + "@" + newHost + ":" + uri.getPort();
1✔
83
        return new NatsUri(newUrl, uri.getScheme());
1✔
84
    }
85

86
    @Override
87
    public String toString() {
88
        return uri.toString();
1✔
89
    }
90

91
    @Override
92
    public boolean equals(Object o) {
93
        if (o == null) return false;
1✔
94
        if (this == o) return true;
1✔
95
        if (o instanceof NatsUri) {
1✔
96
            o = ((NatsUri)o).uri;
1✔
97
        }
98
        return uri.equals(o);
1✔
99
    }
100

101
    @Override
102
    public int hashCode() {
103
        return uri.hashCode();
1✔
104
    }
105

106
    public NatsUri() {
1✔
107
        try {
108
            uri = new URI(Options.DEFAULT_URL);
1✔
UNCOV
109
        } catch (URISyntaxException e) {
×
110
            // seriously, this better not happen!
UNCOV
111
            throw new RuntimeException(e);
×
112
        }
1✔
113
        postConstruct();
1✔
114
    }
1✔
115

116
    public NatsUri(@NonNull URI uri) throws URISyntaxException {
117
        this(uri.toString(), null);
1✔
118
    }
1✔
119

120
    public NatsUri(@NonNull String url) throws URISyntaxException {
121
        this(url, null);
1✔
122
    }
1✔
123

124
    public NatsUri(@NonNull String url, @Nullable String defaultScheme) throws URISyntaxException {
1✔
125
    /*
126
        test string --> result of new URI(String)
127

128
        [1] provide protocol and try again
129
        1.2.3.4:4222 --> Illegal character in scheme name at index 0: 1.2.3.4:4222
130

131
        [2] throw exception
132
        proto:// --> Expected authority at index
133

134
        [3] null scheme, non-empty path? provide protocol and try again
135
        host    --> scheme:'null', host:'null', up:'null', port:-1, path:'host'
136
        1.2.3.4 --> scheme:'null', host:'null', up:'null', port:-1, path:'1.2.3.4'
137

138
        [4] has scheme but null host/path, provide protocol and try again
139
        x:p@host         --> scheme:'x', host:'null', up:'null', port:-1, path:'null'
140
        x:p@1.2.3.4      --> scheme:'x', host:'null', up:'null', port:-1, path:'null'
141
        x:4222           --> scheme:'x', host:'null', up:'null', port:-1, path:'null'
142
        x:p@host:4222    --> scheme:'x', host:'null', up:'null', port:-1, path:'null'
143
        x:p@1.2.3.4:4222 --> scheme:'x', host:'null', up:'null', port:-1, path:'null'
144

145
        [5] has scheme, null host, non-null path, make/throw
146
        proto://u:p@      --> scheme:'proto', host:'null', up:'null', port:-1, path:''
147
        proto://:4222     --> scheme:'proto', host:'null', up:'null', port:-1, path:''
148
        proto://u:p@:4222 --> scheme:'proto', host:'null', up:'null', port:-1, path:''
149

150
        [6] has scheme and host just needs port
151
        proto://host        --> scheme:'proto', host:'host', up:'null', port:-1, path:''
152
        proto://u:p@host    --> scheme:'proto', host:'host', up:'u:p', port:-1, path:''
153
        proto://1.2.3.4     --> scheme:'proto', host:'1.2.3.4', up:'null', port:-1, path:''
154
        proto://u:p@1.2.3.4 --> scheme:'proto', host:'1.2.3.4', up:'u:p', port:-1, path:''
155

156
        [7] has scheme, host and port
157
        proto://host:4222        --> scheme:'proto', host:'host', up:'null', port:4222, path:''
158
        proto://u:p@host:4222    --> scheme:'proto', host:'host', up:'u:p', port:4222, path:''
159
        proto://1.2.3.4:4222     --> scheme:'proto', host:'1.2.3.4', up:'null', port:4222, path:''
160
        proto://u:p@1.2.3.4:4222 --> scheme:'proto', host:'1.2.3.4', up:'u:p', port:4222, path:''
161
     */
162

163
        String prefix;
164
        if (defaultScheme == null) {
1✔
165
            prefix = NATS_PROTOCOL_SLASH_SLASH;
1✔
166
        }
167
        else {
168
            prefix = defaultScheme.toLowerCase();
1✔
169
            if (!prefix.endsWith(COLON_SLASH_SLASH)) {
1✔
170
                prefix += COLON_SLASH_SLASH;
1✔
171
            }
172
        }
173

174
        url = url.trim();
1✔
175
        Helper helper = parse(url, true, prefix);
1✔
176
        String scheme = helper.uri.getScheme();
1✔
177
        String path = helper.uri.getPath();
1✔
178
        if (scheme == null) {
1✔
179
            if (path != null) {
1✔
180
                // [3]
181
                helper = tryPrefixed(helper.url, prefix);
1✔
182
                scheme = helper.uri.getScheme();
1✔
183
                path = helper.uri.getPath();
1✔
184
            }
185
            else {
186
                // [X] not in the examples so don't know what to do, we are done
UNCOV
187
                throw new URISyntaxException(url, UNABLE_TO_PARSE);
×
188
            }
189
        }
190

191
        String host = helper.uri.getHost();
1✔
192
        if (host == null) {
1✔
193
            if (path == null) {
1✔
194
                // [4]
195
                helper = tryPrefixed(helper.url, prefix);
1✔
196
                scheme = helper.uri.getScheme();
1✔
197
                host = helper.uri.getHost();
1✔
198
            }
199
            else {
200
                // [5]
201
                throw new URISyntaxException(url, UNABLE_TO_PARSE);
1✔
202
            }
203
        }
204

205
        if (host == null) {
1✔
206
            // if these aren't here by now, nothing we can do
207
            throw new URISyntaxException(url, UNABLE_TO_PARSE);
1✔
208
        }
209

210
        String lower = scheme.toLowerCase();
1✔
211
        if (!KNOWN_PROTOCOLS.contains(lower)) {
1✔
212
            throw new URISyntaxException(url, UNSUPPORTED_SCHEME);
1✔
213
        }
214
        if (!lower.equals(scheme)) {
1✔
215
            helper.url = helper.url.replace(scheme, lower);
1✔
216
        }
217

218
        if (helper.uri.getPort() == NO_PORT) {
1✔
219
            // [6]
220
            uri = new URI(helper.url + ":" + DEFAULT_PORT);
1✔
221
        }
222
        else {
223
            uri = new URI(helper.url);
1✔
224
        }
225

226
        postConstruct();
1✔
227
    }
1✔
228

229
    private void postConstruct() {
230
        String s = uri.getScheme().toLowerCase();
1✔
231
        isSecure = SECURE_PROTOCOLS.contains(s);
1✔
232
        isWebsocket = WEBSOCKET_PROTOCOLS.contains(s);
1✔
233
        s = uri.getHost();
1✔
234
        hostIsIpAddress = IPV4_RE.matcher(s).matches() || s.startsWith("[") && s.endsWith("]");
1✔
235
    }
1✔
236

237
    static class Helper {
238
        String url;
239
        URI uri;
240

241
        public Helper(String url) throws URISyntaxException {
1✔
242
            this.url = url;
1✔
243
            this.uri = new URI(url);
1✔
244
        }
1✔
245
    }
246

247
    private Helper tryPrefixed(String url, String prefix) throws URISyntaxException {
248
        return parse(prefix + url, false, prefix);
1✔
249
    }
250

251
    private Helper parse(String url, boolean allowTryPrefixed, String prefix) throws URISyntaxException {
252
        try {
253
            return new Helper(url);
1✔
254
        }
255
        catch (URISyntaxException e) {
1✔
256
            if (allowTryPrefixed && e.getMessage().contains(URI_E_ALLOW_TRY_PREFIXED)) {
1✔
257
                // [4]
258
                return tryPrefixed(url, prefix);
1✔
259
            }
260
            else {
261
                // [5]
262
                throw e;
1✔
263
            }
264
        }
265
    }
266

267
    @NonNull
268
    public static String join(@NonNull String delimiter, @NonNull List<NatsUri> uris) {
269
        StringBuilder sb = new StringBuilder();
1✔
270
        for (int i = 0; i < uris.size(); i++) {
1✔
271
            if (i > 0) {
1✔
272
                sb.append(delimiter);
1✔
273
            }
274
            sb.append(uris.get(i));
1✔
275
        }
276
        return sb.toString();
1✔
277
    }
278

279
    private String equivalentComparable() {
280
        return uri.getHost().toLowerCase() + uri.getPort();
1✔
281
    }
282

283
    public boolean equivalent(@NonNull NatsUri other) {
284
        return equivalentComparable().compareTo(other.equivalentComparable()) == 0;
1✔
285
    }
286
}
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