Skip to content

Commit 0efbaa1

Browse files
committed
Improve error messages
1 parent 0461cf7 commit 0efbaa1

4 files changed

Lines changed: 90 additions & 19 deletions

File tree

src/main/java/com/apicatalog/multicodec/Multicodec.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ public byte[] encode(final byte[] value) {
113113
Objects.requireNonNull(value);
114114

115115
if (value.length == 0) {
116-
throw new IllegalArgumentException("The value to encode must be non empty byte array.");
116+
throw new IllegalArgumentException("The value to encode must be a non-empty byte array.");
117117
}
118118

119119
final byte[] encoded = new byte[codeVarint.length + value.length];
@@ -166,11 +166,14 @@ public byte[] decode(final byte[] encoded, final int index) {
166166
Objects.requireNonNull(encoded);
167167

168168
if ((encoded.length - index) < (codeVarint.length + 1)) {
169-
throw new IllegalArgumentException("The value to decode must be non empty byte array, min length = " + (codeVarint.length + 1) + ", actual = " + encoded.length + ".");
169+
throw new IllegalArgumentException(
170+
"The value to decode must be a non-empty byte array with a minimum length of "
171+
+ (codeVarint.length + 1) + " bytes, but the actual length is " + encoded.length + " bytes.");
170172
}
171173

172174
if (!IntStream.range(0, codeVarint.length).allMatch(i -> codeVarint[i] == encoded[i + index])) {
173-
throw new IllegalArgumentException("The value to decode is not encoded with " + toString() + ".");
175+
throw new IllegalArgumentException(
176+
"The provided value is not encoded with this codec: " + toString() + ".");
174177
}
175178

176179
return Arrays.copyOfRange(encoded, index + codeVarint.length, encoded.length - index);

src/main/java/com/apicatalog/multicodec/MulticodecDecoder.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ public Optional<Multicodec> getCodec(final byte[] encoded) {
8383
Objects.requireNonNull(encoded);
8484

8585
if (encoded.length == 0) {
86-
throw new IllegalArgumentException("The encoded value be non empty byte array.");
86+
throw new IllegalArgumentException("The encoded value must be a non-empty byte array.");
8787
}
8888

8989
final long code = UVarInt.decode(encoded);
@@ -103,7 +103,8 @@ public Optional<Multicodec> getCodec(final byte[] encoded) {
103103
public byte[] decode(final byte[] encoded) throws IllegalArgumentException {
104104
return getCodec(encoded)
105105
.map(codec -> codec.decode(encoded))
106-
.orElseThrow(() -> new IllegalArgumentException("Unsupported multicode encoding [" + String.format("0x%hh, 0x%hh, ...", encoded[0], encoded[1]) + "]."));
106+
.orElseThrow(() -> new IllegalArgumentException(
107+
"Unsupported multicodec encoding [" + String.format("0x%hh, 0x%hh, ...", encoded[0], encoded[1]) + "]."));
107108
}
108109

109110
/**

src/main/java/com/apicatalog/multicodec/codec/MulticodecRegistry.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,9 @@ public static final MulticodecRegistry getInstance(final Multicodec... codecs) {
121121
* @return map of numeric code to codec definition for matching tags
122122
*/
123123
public static final Map<Long, ? extends Multicodec> provided(final Tag... tags) {
124+
if (tags == null || tags.length == 0) {
125+
throw new IllegalArgumentException("At least one tag must be provided.");
126+
}
124127
if (tags.length == 1) {
125128
return TAGS.get(tags[0]);
126129
}

src/main/java/com/apicatalog/multihash/Multihash.java

Lines changed: 78 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,38 +8,80 @@
88
import com.apicatalog.uvarint.UVarInt;
99

1010
/**
11-
* Multihash API. Overrides {@link Multicodec#decode(byte[])} and
12-
* {@link Multicodec#encode(byte[])} methods.
11+
* Represents a <a href="https://multiformats.io/multihash/">multihash</a>
12+
* codec, which encodes values by prefixing them with a multicodec code,
13+
* followed by the length of the hash digest, and finally the digest bytes.
14+
*
15+
* <p>
16+
* This class extends {@link Multicodec} and overrides the
17+
* {@link #encode(byte[])} and {@link #decode(byte[], int)} methods to handle
18+
* the additional digest length prefix required by the multihash format.
19+
* </p>
20+
*
21+
* <p>
22+
* Instances of this class are immutable and thread-safe.
23+
* </p>
1324
*/
1425
public class Multihash extends Multicodec {
1526

27+
/**
28+
* Creates a new {@code Multihash} instance.
29+
*
30+
* @param name the multihash name (e.g., "sha2-256")
31+
* @param code the multicodec code
32+
* @param uvarint the varint-encoded code
33+
* @param status the registration status
34+
*/
1635
protected Multihash(String name, long code, byte[] uvarint, Status status) {
1736
super(name, Tag.Multihash, code, uvarint, status);
1837
}
1938

39+
/**
40+
* Creates a new {@code Multihash} with no explicit status.
41+
*
42+
* @param name the multihash name
43+
* @param code the multicodec code
44+
* @return a new {@code Multihash} instance
45+
*/
2046
public static Multihash of(String name, long code) {
2147
return of(name, code, null);
2248
}
2349

50+
/**
51+
* Creates a new {@code Multihash}.
52+
*
53+
* @param name the multihash name
54+
* @param code the multicodec code
55+
* @param status the registration status
56+
* @return a new {@code Multihash} instance
57+
*/
2458
public static Multihash of(String name, long code, Status status) {
2559
return new Multihash(name, code, UVarInt.encode(code), status);
2660
}
2761

2862
/**
29-
* Encode a value with multihash.
30-
*
31-
* @param value a value to encode
32-
* @return an encoded value
33-
*
34-
* @throws IllegalArgumentException if the value cannot be encoded
63+
* Encodes a value as a multihash.
64+
*
65+
* <p>
66+
* The resulting byte array consists of:
67+
* <ol>
68+
* <li>the varint-encoded multicodec code,</li>
69+
* <li>the varint-encoded digest length,</li>
70+
* <li>the hash digest bytes.</li>
71+
* </ol>
72+
*
73+
* @param value the hash digest to encode
74+
* @return the encoded multihash
75+
* @throws NullPointerException if {@code value} is {@code null}
76+
* @throws IllegalArgumentException if {@code value} is empty
3577
*/
3678
@Override
3779
public byte[] encode(final byte[] value) {
3880

3981
Objects.requireNonNull(value);
4082

4183
if (value.length == 0) {
42-
throw new IllegalArgumentException("The value to encode must be non empty byte array.");
84+
throw new IllegalArgumentException("The value to encode must be a non-empty byte array.");
4385
}
4486

4587
final byte[] sizeVarint = UVarInt.encode(value.length);
@@ -53,29 +95,51 @@ public byte[] encode(final byte[] value) {
5395
return encoded;
5496
}
5597

98+
/**
99+
* Decodes a multihash value starting from the given index.
100+
*
101+
* <p>
102+
* Validates that the encoded data matches this multihash's code, then reads the
103+
* declared digest length and verifies it matches the actual remaining data
104+
* length before returning the digest bytes.
105+
* </p>
106+
*
107+
* @param encoded the multihash-encoded byte array
108+
* @param index the starting index (inclusive)
109+
* @return the decoded hash digest bytes
110+
* @throws NullPointerException if {@code encoded} is {@code null}
111+
* @throws IllegalArgumentException if the encoded data is too short, not
112+
* encoded with this multihash's code, or the
113+
* declared digest length does not match the
114+
* actual data length
115+
*/
56116
@Override
57117
public byte[] decode(byte[] encoded, int index) {
58118

59119
Objects.requireNonNull(encoded);
60120

61121
if ((encoded.length - index) < (codeVarint.length + 2)) {
62-
throw new IllegalArgumentException("The value to decode must be non empty byte array, min length = " + (codeVarint.length + 2) + ", actual = " + encoded.length + ".");
122+
throw new IllegalArgumentException(
123+
"The value to decode must be a non-empty byte array with a minimum length of "
124+
+ (codeVarint.length + 2) + " bytes, but the actual length is " + encoded.length + " bytes.");
63125
}
64126

65127
if (!IntStream.range(0, codeVarint.length).allMatch(i -> codeVarint[i] == encoded[i + index])) {
66-
throw new IllegalArgumentException("The value to decode is not encoded with " + toString() + ".");
128+
throw new IllegalArgumentException(
129+
"The provided value is not encoded with this multihash: " + toString() + ".");
67130
}
68131

69-
// digest size
132+
// Get digest size
70133
long size = UVarInt.decode(encoded, index + codeVarint.length);
71134
int sizeVarintLength = UVarInt.byteLength(size);
72135

73136
if (size != (encoded.length - index - codeVarint.length - sizeVarintLength)) {
74137
throw new IllegalArgumentException(
75-
"The declared digest size = " + size + " and the actual hash digest size = " + (encoded.length - index - codeVarint.length - sizeVarintLength) + " do not match.");
138+
"Digest size mismatch: declared size is " + size + " bytes, but the actual digest size is " +
139+
(encoded.length - index - codeVarint.length - sizeVarintLength) + " bytes.");
76140
}
77141

78-
// digest
142+
// Extract digest
79143
return Arrays.copyOfRange(encoded, index + codeVarint.length + sizeVarintLength, encoded.length - index);
80144
}
81145

0 commit comments

Comments
 (0)