Skip to content

Commit

Permalink
feat(key): try to include additional information in error messages wh…
Browse files Browse the repository at this point in the history
…en an invalid character has been detected in a Key

Co-authored-by: Jason <[email protected]>
  • Loading branch information
kashike and jpenilla committed Apr 28, 2023
1 parent 7b1a5a6 commit ca25bdf
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 6 deletions.
32 changes: 28 additions & 4 deletions key/src/main/java/net/kyori/adventure/key/Key.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
package net.kyori.adventure.key;

import java.util.Comparator;
import java.util.OptionalInt;
import java.util.function.IntConsumer;
import java.util.stream.Stream;
import net.kyori.examination.Examinable;
import net.kyori.examination.ExaminableProperty;
Expand Down Expand Up @@ -174,12 +176,23 @@ static boolean parseable(final @Nullable String string) {
* @since 4.12.0
*/
static boolean parseableNamespace(final @NotNull String namespace) {
return !checkNamespace(namespace).isPresent();
}

/**
* Checks if {@code value} is a valid namespace.
*
* @param namespace the string to check
* @return {@link OptionalInt#empty()} if {@code value} is a valid namespace, otherwise an {@code OptionalInt} containing the index of an invalid character
* @since 4.14.0
*/
static @NotNull OptionalInt checkNamespace(final @NotNull String namespace) {
for (int i = 0, length = namespace.length(); i < length; i++) {
if (!allowedInNamespace(namespace.charAt(i))) {
return false;
return OptionalInt.of(i);
}
}
return true;
return OptionalInt.empty();
}

/**
Expand All @@ -190,12 +203,23 @@ static boolean parseableNamespace(final @NotNull String namespace) {
* @since 4.12.0
*/
static boolean parseableValue(final @NotNull String value) {
return !checkValue(value).isPresent();
}

/**
* Checks if {@code value} is a valid value.
*
* @param value the string to check
* @return {@link OptionalInt#empty()} if {@code value} is a valid value, otherwise an {@code OptionalInt} containing the index of an invalid character
* @since 4.14.0
*/
static @NotNull OptionalInt checkValue(final @NotNull String value) {
for (int i = 0, length = value.length(); i < length; i++) {
if (!allowedInValue(value.charAt(i))) {
return false;
return OptionalInt.of(i);
}
}
return true;
return OptionalInt.empty();
}

/**
Expand Down
22 changes: 20 additions & 2 deletions key/src/main/java/net/kyori/adventure/key/KeyImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,11 @@
*/
package net.kyori.adventure.key;

import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Objects;
import java.util.OptionalInt;
import java.util.stream.Stream;
import net.kyori.examination.ExaminableProperty;
import org.jetbrains.annotations.NotNull;
Expand All @@ -41,12 +44,27 @@ final class KeyImpl implements Key {
private final String value;

KeyImpl(final @NotNull String namespace, final @NotNull String value) {
if (!Key.parseableNamespace(namespace)) throw new InvalidKeyException(namespace, value, String.format("Non [a-z0-9_.-] character in namespace of Key[%s]", asString(namespace, value)));
if (!Key.parseableValue(value)) throw new InvalidKeyException(namespace, value, String.format("Non [a-z0-9/._-] character in value of Key[%s]", asString(namespace, value)));
checkError("namespace", namespace, value, Key.checkNamespace(namespace));
checkError("value", namespace, value, Key.checkValue(value));
this.namespace = requireNonNull(namespace, "namespace");
this.value = requireNonNull(value, "value");
}

private static void checkError(final String name, final String namespace, final String value, final OptionalInt index) {
if (index.isPresent()) {
final int indexValue = index.getAsInt();
final char character = value.charAt(indexValue);
throw new InvalidKeyException(namespace, value, String.format(
"Non [a-z0-9_.-] character in %s of Key[%s] at index %d ('%s', bytes: %s)",
name,
asString(namespace, value),
indexValue,
character,
Arrays.toString(String.valueOf(character).getBytes(StandardCharsets.UTF_8))
));
}
}

static boolean allowedInNamespace(final char character) {
return character == '_' || character == '-' || (character >= 'a' && character <= 'z') || (character >= '0' && character <= '9') || character == '.';
}
Expand Down
5 changes: 5 additions & 0 deletions key/src/test/java/net/kyori/adventure/key/KeyTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -107,4 +107,9 @@ void testParseableValue() {
assertTrue(Key.parseableValue("empty"));
assertTrue(Key.parseableValue("some/path"));
}

@Test
void testNulChar() {
assertThrows(InvalidKeyException.class, () -> Key.key("carbon:global\0\0\0"));
}
}

0 comments on commit ca25bdf

Please sign in to comment.