package org.shedlang.compiler.config.json; import org.junit.jupiter.api.Test; import org.shedlang.compiler.config.errors.ConfigError; import org.shedlang.compiler.sources.SourceTexts; import java.util.Optional; import java.util.OptionalLong; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.zwobble.precisely.AssertThat.assertThat; import static org.zwobble.precisely.Matchers.equalTo; public class Json5ObjectWithSourceTests { @Test public void whenMemberIsMissingThenGetLongThrowsError() { var source = """ { a: { b: [ {x: 1}, {}, ], }, } """; var object = parseJson5Object(source); var error = assertThrows( ConfigError.class, () -> object .getObject("a") .orElseThrow() .getArrayOfObjects("b") .orElseThrow() .get(1) .getLong("x") ); assertThat(error.getMessage(), equalTo("$.a.b[1] is missing x")); assertThat(error.source().describe(), equalTo(""" :5:13 {}, ^^""" )); } @Test public void whenMemberIsNotNumberFiniteThenGetLongThrowsError() { var source = """ { a: { b: [ {x: 1}, {x: true}, ], }, } """; var object = parseJson5Object(source); var error = assertThrows( ConfigError.class, () -> object .getObject("a") .orElseThrow() .getArrayOfObjects("b") .orElseThrow() .get(1) .getLong("x") ); assertThat(error.getMessage(), equalTo("Expected $.a.b[1].x to be finite number, but was boolean")); assertThat(error.source().describe(), equalTo(""" :5:17 {x: true}, ^^^^""" )); } @Test public void whenMemberIsFiniteNumberButIsNotLongThenGetLongThrowsError() { var source = """ { a: { b: [ {x: 1}, {x: 1.2}, ], }, } """; var object = parseJson5Object(source); var error = assertThrows( ConfigError.class, () -> object .getObject("a") .orElseThrow() .getArrayOfObjects("b") .orElseThrow() .get(1) .getLong("x") ); assertThat(error.getMessage(), equalTo("$.a.b[1].x must be a 64-bit integer")); assertThat(error.source().describe(), equalTo(""" :5:17 {x: 1.2}, ^^^""" )); } @Test public void whenMemberIsLongThenGetLongReturnsValue() { var source = """ { a: { b: [ {x: 1}, {x : 2}, ], }, } """; var object = parseJson5Object(source); var result = object .getObject("a") .orElseThrow() .getArrayOfObjects("b") .orElseThrow() .get(1) .getLong("x"); assertThat(result, equalTo(2L)); } @Test public void whenMemberIsMissingThenGetLongOrNoneReturnsEmptyOptional() { var source = """ { a: { b: [ {x: 1}, {}, ], }, } """; var object = parseJson5Object(source); var result = object .getObject("a") .orElseThrow() .getArrayOfObjects("b") .orElseThrow() .get(1) .getLongOrNone("x"); assertThat(result, equalTo(OptionalLong.empty())); } @Test public void whenMemberIsNotLongThenGetLongOrNoneThrowsError() { var source = """ { a: { b: [ {x: 1}, {x: true}, ], }, } """; var object = parseJson5Object(source); var error = assertThrows( ConfigError.class, () -> object .getObject("a") .orElseThrow() .getArrayOfObjects("b") .orElseThrow() .get(1) .getLongOrNone("x") ); assertThat(error.getMessage(), equalTo("Expected $.a.b[1].x to be finite number, but was boolean")); assertThat(error.source().describe(), equalTo(""" :5:17 {x: true}, ^^^^""" )); } @Test public void whenMemberIsLongOrNoneThenGetLongReturnsValue() { var source = """ { a: { b: [ {x: 1}, {x: 2}, ], }, } """; var object = parseJson5Object(source); var result = object .getObject("a") .orElseThrow() .getArrayOfObjects("b") .orElseThrow() .get(1) .getLongOrNone("x"); assertThat(result, equalTo(OptionalLong.of(2L))); } @Test public void whenMemberIsMissingThenGetStringThrowsError() { var source = """ { shed: { bins: [ {name: "one"}, {}, ], }, } """; var object = parseJson5Object(source); var error = assertThrows( ConfigError.class, () -> object .getObject("shed") .orElseThrow() .getArrayOfObjects("bins") .orElseThrow() .get(1) .getString("name") ); assertThat(error.getMessage(), equalTo("$.shed.bins[1] is missing name")); assertThat(error.source().describe(), equalTo(""" :5:13 {}, ^^""" )); } @Test public void whenMemberIsNotStringThenGetStringThrowsError() { var source = """ { shed: { bins: [ {name: "one"}, {name: true}, ], }, } """; var object = parseJson5Object(source); var error = assertThrows( ConfigError.class, () -> object .getObject("shed") .orElseThrow() .getArrayOfObjects("bins") .orElseThrow() .get(1) .getString("name") ); assertThat(error.getMessage(), equalTo("Expected $.shed.bins[1].name to be string, but was boolean")); assertThat(error.source().describe(), equalTo(""" :5:20 {name: true}, ^^^^""" )); } @Test public void whenMemberIsStringThenGetStringReturnsValue() { var source = """ { shed: { bins: [ {name: "one"}, {name: "two"}, ], }, } """; var object = parseJson5Object(source); var result = object .getObject("shed") .orElseThrow() .getArrayOfObjects("bins") .orElseThrow() .get(1) .getString("name"); assertThat(result.value(), equalTo("two")); assertThat(result.sourceRange().describe(), equalTo( """ :5:20 {name: "two"}, ^^^^^""" )); } @Test public void whenMemberIsMissingThenGetStringOrNoneReturnsEmptyOptional() { var source = """ { shed: { bins: [ {name: "one"}, {}, ], }, } """; var object = parseJson5Object(source); var result = object .getObject("shed") .orElseThrow() .getArrayOfObjects("bins") .orElseThrow() .get(1) .getStringOrNone("name"); assertThat(result, equalTo(Optional.empty())); } @Test public void whenMemberIsNotStringThenGetStringOrNoneThrowsError() { var source = """ { shed: { bins: [ {name: "one"}, {name: true}, ], }, } """; var object = parseJson5Object(source); var error = assertThrows( ConfigError.class, () -> object .getObject("shed") .orElseThrow() .getArrayOfObjects("bins") .orElseThrow() .get(1) .getStringOrNone("name") ); assertThat(error.getMessage(), equalTo("Expected $.shed.bins[1].name to be string, but was boolean")); assertThat(error.source().describe(), equalTo(""" :5:20 {name: true}, ^^^^""" )); } @Test public void whenMemberIsStringThenGetStringOrNoneReturnsValue() { var source = """ { shed: { bins: [ {name: "one"}, {name: "two"}, ], }, } """; var object = parseJson5Object(source); var result = object .getObject("shed") .orElseThrow() .getArrayOfObjects("bins") .orElseThrow() .get(1) .getStringOrNone("name"); assertThat(result.orElseThrow().value(), equalTo("two")); assertThat(result.orElseThrow().sourceRange().describe(), equalTo( """ :5:20 {name: "two"}, ^^^^^""" )); } private Json5ObjectWithSource parseJson5Object(String text) { var sourceText = SourceTexts.string(text); return Json5ObjectWithSource.parse(sourceText); } }