Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package org.everit.json.schema.internal;

import java.util.Optional;

import org.everit.json.schema.FormatValidator;
import org.json.JSONPointer;

public class RelativeJsonPointerFormatValidator implements FormatValidator {

private static class ParseException extends Exception {

public ParseException(String input) {
super(String.format("[%s] is not a valid relative JSON Pointer", input));
}
}

private static final class Parser {

public static final int EOF = 26;

private static boolean isDigit(char c) {
return '0' <= c && c <= '9';
}

private String input;

private int pos = 0;

public Parser(String input) {
this.input = input;
}

public void parse() throws ParseException {
parseUpwardsStepCount();
parseJsonPointer();
parseTrailingHashmark();
}

private void parseTrailingHashmark() throws ParseException {
if (pos == input.length()) {
return;
}
if (pos == input.length() - 1 && input.charAt(pos) == '#') {
return;
}
fail();
}

private char next() {
++pos;
if (pos == input.length()) {
return 26;
}
return curr();
}

private char curr() {
if (pos == input.length()) {
return EOF;
}
return input.charAt(pos);
}

private void parseUpwardsStepCount() throws ParseException {
if (!isDigit(curr())) {
fail();
} else if (curr() == '0') {
next();
if (curr() == '/' || curr() == '#' || curr() == EOF) {
pos--;
} else {
fail();
}
}
for (char current = next(); isDigit(current) && pos < input.length(); current = next())
;
}

private void fail() throws ParseException {
throw new ParseException(input);
}

private void parseJsonPointer() throws ParseException {
StringBuilder sb = new StringBuilder();
char current = curr();
while (pos < input.length() && current != '#') {
sb.append(current);
current = next();
}
String pointer = sb.toString();
if (pointer.length() == 0) {
return;
}
if (pointer.startsWith("#")) {
fail();
}
try {
new JSONPointer(pointer);
} catch (IllegalArgumentException e) {
fail();
}
}
}

@Override

public Optional<String> validate(String subject) {
try {
new Parser(subject).parse();
} catch (ParseException e) {
return Optional.of(e.getMessage());
}
return Optional.empty();
}

@Override public String formatName() {
return "relative-json-pointer";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import org.everit.json.schema.internal.IPV4Validator;
import org.everit.json.schema.internal.IPV6Validator;
import org.everit.json.schema.internal.JsonPointerFormatValidator;
import org.everit.json.schema.internal.RelativeJsonPointerFormatValidator;
import org.everit.json.schema.internal.TimeFormatValidator;
import org.everit.json.schema.internal.URIFormatValidator;
import org.everit.json.schema.internal.URIReferenceFormatValidator;
Expand Down Expand Up @@ -157,6 +158,7 @@ private static final List<String> keywords(String... keywords) {
Map<String, FormatValidator> formatValidators = new HashMap<>(V6_VALIDATORS);
formatValidators.put("date", new DateFormatValidator());
formatValidators.put("time", new TimeFormatValidator());
formatValidators.put("relative-json-pointer", new RelativeJsonPointerFormatValidator());
V7_VALIDATORS = unmodifiableMap(formatValidators);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package org.everit.json.schema.internal;

import static org.everit.json.schema.internal.ValidatorTestSupport.assertFailure;
import static org.everit.json.schema.internal.ValidatorTestSupport.assertSuccess;
import static org.junit.Assert.assertEquals;

import org.junit.Test;

public class RelativeJsonPointerFormatValidatorTest {

private static final RelativeJsonPointerFormatValidator SUBJECT = new RelativeJsonPointerFormatValidator();

@Test
public void formatNameTest() {
assertEquals("relative-json-pointer", SUBJECT.formatName());
}

@Test
public void onlyUpwardsStepCount() {
assertSuccess("1", SUBJECT);
}

@Test
public void onlyUpwardsCount_multipleDigits() {
assertSuccess("234", SUBJECT);
}

@Test
public void upwardsStepCountWithJsonPointer() {
assertSuccess("23/foo/bar", SUBJECT);
}

@Test
public void multipleDigitsFollowedByPointer() {
assertSuccess("123/a/b", SUBJECT);
}

@Test
public void multipleDigitsFollowedByHashmark() {
assertSuccess("123#", SUBJECT);
}

@Test
public void upwardsStepCountWithHashmark() {
assertSuccess("2#", SUBJECT);
}

@Test
public void negativeUpwardsStepCount() {
assertFailure("-123", SUBJECT, "[-123] is not a valid relative JSON Pointer");
}

@Test
public void hashmarkIsNotTheLastChar() {
assertFailure("3/ab/c#d", SUBJECT, "[3/ab/c#d] is not a valid relative JSON Pointer");
}

@Test
public void nonIntegerBeginning() {
assertFailure("abc/d/e/f", SUBJECT, "[abc/d/e/f] is not a valid relative JSON Pointer");
}

@Test
public void upwardsStepCountFollowedByInvalidJsonPointer() {
assertFailure("123asd~~b", SUBJECT, "[123asd~~b] is not a valid relative JSON Pointer");
}

@Test
public void upwardsStepCountFollowedByURLFormJsonPointer() {
assertFailure("123#/a/b", SUBJECT, "[123#/a/b] is not a valid relative JSON Pointer");
}

@Test
public void noUpwardsStepCount() {
assertFailure("/foo/bar", SUBJECT, "[/foo/bar] is not a valid relative JSON Pointer");
}

@Test
public void leadingZeroFailure() {
assertFailure("0123", SUBJECT, "[0123] is not a valid relative JSON Pointer");
}

@Test
public void onlyLeadingZero() {
assertSuccess("0", SUBJECT);
}

@Test
public void upwardsStepCountIsZeroFollowedByPointer() {
assertSuccess("0/a/b", SUBJECT);
}

@Test
public void upwardsStepCountIsZeroFollowedByHashmark() {
assertSuccess("0#", SUBJECT);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.everit.json.schema.internal;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import org.junit.Test;

public class URIV4FormatValidatorTest {

@Test
public void relativeURI() {
assertFalse(new URIV4FormatValidator().validate("abc").isPresent());
}

@Test
public void absoluteURI() {
assertFalse(new URIV4FormatValidator().validate("http://a.b.c").isPresent());
}

@Test
public void notURI() {
assertTrue(new URIV4FormatValidator().validate("\\\\\\\\WINDOWS\\\\fileshare").isPresent());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,13 @@

public final class ValidatorTestSupport {

static void assertSuccess(final String subject, final FormatValidator format) {
static void assertSuccess(String subject, FormatValidator format) {
Optional<String> opt = format.validate(subject);
Assert.assertNotNull("the optional is not null", opt);
Assert.assertFalse("failure not exist", opt.isPresent());
}

static void assertFailure(final String subject, final FormatValidator format,
final String expectedFailure) {
static void assertFailure(String subject, FormatValidator format, String expectedFailure) {
Optional<String> opt = format.validate(subject);
Assert.assertNotNull("the optional is not null", opt);
assertTrue("failure exists", opt.isPresent());
Expand Down