Skip to content

Commit

Permalink
implement sf-date (#8)
Browse files Browse the repository at this point in the history
* minimal support for parsing dates

* minimal support for parsing dates

* wire date into list parser

* test date-typed param

* fix parsing of '@' with missing digits

* extend test runner to handle sfdate and integrate date.json
  • Loading branch information
reschke authored Nov 14, 2023
1 parent 2896f89 commit e32becd
Show file tree
Hide file tree
Showing 6 changed files with 152 additions and 3 deletions.
78 changes: 78 additions & 0 deletions src/main/java/org/greenbytes/http/sfv/DateItem.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package org.greenbytes.http.sfv;

import java.util.Objects;

/**
* Represents a Date.
*/
public class DateItem implements NumberItem<Long> {

private final long value;
private final Parameters params;

private static final long MIN = -999999999999999L;
private static final long MAX = 999999999999999L;

private DateItem(long value, Parameters params) {
if (value < MIN || value > MAX) {
throw new IllegalArgumentException("value must be in the range from " + MIN + " to " + MAX);
}
this.value = value;
this.params = Objects.requireNonNull(params, "params must not be null");
}

/**
* Creates an {@link DateItem} instance representing the specified
* {@code long} value.
*
* @param value
* a {@code long} value.
* @return a {@link DateItem} representing {@code value}.
*/
public static DateItem valueOf(long value) {
return new DateItem(value, Parameters.EMPTY);
}

@Override
public DateItem withParams(Parameters params) {
if (Objects.requireNonNull(params, "params must not be null").isEmpty()) {
return this;
} else {
return new DateItem(this.value, params);
}
}

@Override
public Parameters getParams() {
return params;
}

@Override
public StringBuilder serializeTo(StringBuilder sb)
{
sb.append('@');
sb.append(Long.toString(value));
params.serializeTo(sb);
return sb;
}

@Override
public String serialize() {
return serializeTo(new StringBuilder()).toString();
}

@Override
public Long get() {
return value;
}

@Override
public long getAsLong() {
return value;
}

@Override
public int getDivisor() {
return 1;
}
}
56 changes: 56 additions & 0 deletions src/main/java/org/greenbytes/http/sfv/Parser.java
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,43 @@ private static String checkASCII(String value) {
return value;
}

private DateItem internalParseBareDate() {
int sign = 1;
StringBuilder inputNumber = new StringBuilder(21);

if (!checkNextChar("@")) {
throw complaint("Illegal start for Date: '" + input + "'");
}
advance();

if (checkNextChar('-')) {
sign = -1;
advance();
}

if (!checkNextChar("0123456789")) {
throw complaint("Illegal start inside a Date: '" + input + "'");
}

boolean done = false;
while (hasRemaining() && !done) {
char c = peek();
if (Utils.isDigit(c)) {
inputNumber.append(c);
advance();
} else {
done = true;
}
if (inputNumber.length() > 15) {
backout();
throw complaint("Date too long: " + inputNumber.length() + " characters");
}
}

long l = Long.parseLong(inputNumber.toString());
return DateItem.valueOf(sign * l);
}

private NumberItem<? extends Object> internalParseBareIntegerOrDecimal() {
boolean isDecimal = false;
int sign = 1;
Expand Down Expand Up @@ -154,6 +191,12 @@ private NumberItem<? extends Object> internalParseBareIntegerOrDecimal() {
}
}

private DateItem internalParseDate() {
DateItem result = internalParseBareDate();
Parameters params = internalParseParameters();
return result.withParams(params);
}

private NumberItem<? extends Object> internalParseIntegerOrDecimal() {
NumberItem<? extends Object> result = internalParseBareIntegerOrDecimal();
Parameters params = internalParseParameters();
Expand Down Expand Up @@ -373,6 +416,8 @@ private Item<? extends Object> internalParseBareItem() {
return internalParseBareToken();
} else if (c == ':') {
return internalParseBareByteSequence();
} else if (c == '@') {
return internalParseBareDate();
} else {
throw complaint("Unexpected start character in Bare Item: " + format(c));
}
Expand Down Expand Up @@ -497,6 +542,17 @@ private Dictionary internalParseDictionary() {

// protected methods unit testing

protected static DateItem parseDate(String input) {
Parser p = new Parser(input);
Item<? extends Object> result = p.internalParseDate();
if (!(result instanceof DateItem)) {
throw p.complaint("String parsed as Date '" + input + "' is not a Date");
} else {
p.assertEmpty("Extra characters in string parsed as Date");
return (DateItem) result;
}
}

protected static IntegerItem parseInteger(String input) {
Parser p = new Parser(input);
Item<? extends Object> result = p.internalParseIntegerOrDecimal();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,9 @@ private static void match(JsonValue value, JsonValue params, Type<? extends Obje
} else if ("token".equals(type)) {
CharSequence expectedString = ((JsonString) container.get("value")).getChars();
assertEquals(expectedString, item.get());
} else if ("date".equals(type)) {
JsonNumber expectedNumber = (JsonNumber) container.get("value");
assertEquals(expectedNumber.longValueExact(), item.get());
} else {
fail("unexpected type: " + type);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public void diagnostics() {
new TestCase("(1 2#", "list", 4, "Expected SP or ')' in Inner List, got: '#' (\\u0023)"),
new TestCase("a b", "dictionary", 2, "Expected COMMA in Dictionary, found: 'b' (\\u0062)"),
new TestCase("a,b,", "dictionary", 4, "Found trailing COMMA in Dictionary"),
new TestCase("@1234", "item", 0, "Unexpected start character in Bare Item: '@' (\\u0040)")
new TestCase("@12.34", "item", 3, "Extra characters in string parsed as Item")
};

for (TestCase test : tests) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public class SpecificationTests extends AbstractSpecificationTests {

@Parameterized.Parameters(name = "{0}")
public static Collection<Object[]> parameters() {
return AbstractSpecificationTests.makeParameters("binary.json", "boolean.json", "dictionary.json", "examples.json",
return AbstractSpecificationTests.makeParameters("binary.json", "boolean.json", "date.json", "dictionary.json", "examples.json",
"item.json", "key-generated.json", "large-generated.json", "list.json", "listlist.json", "number.json",
"number-generated.json", "param-dict.json", "param-list.json", "param-listlist.json", "string.json",
"string-generated.json", "token.json", "token-generated.json");
Expand Down
14 changes: 13 additions & 1 deletion src/test/java/org/greenbytes/http/sfv/Tests.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,16 @@ public void testValidIntegers() {
}
}

@Test
public void testValidDates() {
String tests[] = new String[] { "@0", "@1", "@-1", "@999999999999", "@-999999999999", "@3;a=b" };

for (String s : tests) {
DateItem i = Parser.parseDate(s);
assertEquals("should round-trip", i.serialize(), s);
}
}

@Test
public void testInvalidIntegers() {
String tests[] = new String[] { "a", "1a", "1.", "9999999999999999", "-9999999999999999", "0999999999999999", "1-2",
Expand Down Expand Up @@ -179,11 +189,13 @@ public void testValidLists() {
Boolean.FALSE, EMPTY, "a2", EMPTY, ByteSequenceItem.valueOf("f".getBytes()).get(), EMPTY });
tests.put("1, ();a", new Object[] { 1L, EMPTY, Collections.emptyList(), ";a" });

tests.put("@12345, 123;created=@-1, 12.3", new Object[] { 12345L, EMPTY, 123L, ";created=@-1", BigDecimal.valueOf(12300, 3), EMPTY });

for (Map.Entry<String, Object[]> e : tests.entrySet()) {
OuterList list = Parser.parseList(e.getKey());
Object[] expected = e.getValue();
assertTrue(list instanceof OuterList);
assertEquals(list.get().size(), expected.length / 2);
assertEquals("unexpected list length for: " + e.getKey(), list.get().size(), expected.length / 2);
for (int i = 0; i < expected.length / 2; i++) {
assertEquals(expected[i * 2], list.get().get(i).get());
Parameters p = list.get().get(i).getParams();
Expand Down

0 comments on commit e32becd

Please sign in to comment.