JSONLDParserCustomTest.java
/*******************************************************************************
* Copyright (c) 2023 Eclipse RDF4J contributors.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Distribution License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
******************************************************************************/
package org.eclipse.rdf4j.rio.jsonld.legacy;
import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import java.io.Reader;
import java.io.StringReader;
import org.eclipse.rdf4j.model.IRI;
import org.eclipse.rdf4j.model.Literal;
import org.eclipse.rdf4j.model.Model;
import org.eclipse.rdf4j.model.Resource;
import org.eclipse.rdf4j.model.Value;
import org.eclipse.rdf4j.model.impl.LinkedHashModel;
import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
import org.eclipse.rdf4j.model.vocabulary.XSD;
import org.eclipse.rdf4j.rio.RDFFormat;
import org.eclipse.rdf4j.rio.RDFParseException;
import org.eclipse.rdf4j.rio.RDFParser;
import org.eclipse.rdf4j.rio.Rio;
import org.eclipse.rdf4j.rio.helpers.ContextStatementCollector;
import org.eclipse.rdf4j.rio.helpers.JSONLDSettings;
import org.eclipse.rdf4j.rio.helpers.JSONSettings;
import org.eclipse.rdf4j.rio.helpers.ParseErrorCollector;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.io.ContentReference;
import com.github.jsonldjava.core.DocumentLoader;
/**
* Custom (non-manifest) tests for JSON-LD parser.
*
* @author Peter Ansell
*/
public class JSONLDParserCustomTest {
/**
* Backslash escaped "h" in "http"
*/
private static final String BACKSLASH_ESCAPED_TEST_STRING = "[{\"@id\": \"\\http://example.com/Subj1\",\"http://example.com/prop1\": [{\"@id\": \"http://example.com/Obj1\"}]}]";
/**
* Java/C++ style comments
*/
private static final String COMMENTS_TEST_STRING = "[{/*This is a non-standard java/c++ style comment\n*/\"@id\": \"http://example.com/Subj1\",\"http://example.com/prop1\": [{\"@id\": \"http://example.com/Obj1\"}]}]";
/**
* Tests for NaN
*/
private static final String NON_NUMERIC_NUMBERS_TEST_STRING = "[{\"@id\": \"http://example.com/Subj1\",\"http://example.com/prop1\": NaN}]";
/**
* Tests for numeric leading zeroes
*/
private static final String NUMERIC_LEADING_ZEROES_TEST_STRING = "[{\"@id\": \"http://example.com/Subj1\",\"http://example.com/prop1\": 000042}]";
/**
* Tests for single-quotes
*/
private static final String SINGLE_QUOTES_TEST_STRING = "[{\'@id\': \"http://example.com/Subj1\",\'http://example.com/prop1\': 42}]";
/**
* Tests for unquoted control char
*/
private static final String UNQUOTED_CONTROL_CHARS_TEST_STRING = "[{\"@id\": \"http://example.com/Subj1\",\"http://example.com/prop1\": \"42\u0009\"}]";
/**
* Tests for unquoted field names
*/
private static final String UNQUOTED_FIELD_NAMES_TEST_STRING = "[{@id: \"http://example.com/Subj1\",\"http://example.com/prop1\": 42}]";
/**
* YAML style comments
*/
private static final String YAML_COMMENTS_TEST_STRING = "[\n{#This is a non-standard yaml style comment/*\n\"@id\": \"http://example.com/Subj1\",\"http://example.com/prop1\": [{\"@id\": \"http://example.com/Obj1\"}]}]";
/**
* Trailing comma
*/
private static final String TRAILING_COMMA_TEST_STRING = "[{\"@id\": \"http://example.com/Subj1\",\"http://example.com/prop1\": [{\"@id\": \"http://example.com/Obj1\"},]}]";
/**
* Strict duplicate detection
*/
private static final String STRICT_DUPLICATE_DETECTION_TEST_STRING = "[{\"@context\": {}, \"@context\": {}, \"@id\": \"http://example.com/Subj1\",\"http://example.com/prop1\": [{\"@id\": \"http://example.com/Obj1\"}]}]";
/**
* Used for custom document loader
*/
private static final String LOADER_CONTEXT = "{ \"@context\": {\"prop\": \"http://example.com/prop1\"} }";
private static final String LOADER_JSONLD = "{ \"@context\": \"http://example.com/context.jsonld\", \"@id\": \"http://example.com/Subj1\", \"prop\": \"Property\" }";
private RDFParser parser;
private ParseErrorCollector errors;
private Model model;
private final SimpleValueFactory F = SimpleValueFactory.getInstance();
private final IRI testSubjectIRI = F.createIRI("http://example.com/Subj1");
private final IRI testPredicate = F.createIRI("http://example.com/prop1");
private final IRI testObjectIRI = F.createIRI("http://example.com/Obj1");
private final Literal testObjectLiteralNotANumber = F.createLiteral("NaN", XSD.DOUBLE);
private final Literal testObjectLiteralNumber = F.createLiteral("42", XSD.INTEGER);
private final Literal testObjectLiteralUnquotedControlChar = F.createLiteral("42\u0009", XSD.STRING);
@BeforeEach
public void setUp() {
parser = Rio.createParser(RDFFormat.JSONLD);
errors = new ParseErrorCollector();
model = new LinkedHashModel();
parser.setParseErrorListener(errors);
parser.setRDFHandler(new ContextStatementCollector(model, F));
}
private void verifyParseResults(Resource nextSubject, IRI nextPredicate, Value nextObject) {
assertEquals(0, errors.getWarnings().size());
assertEquals(0, errors.getErrors().size());
assertEquals(0, errors.getFatalErrors().size());
assertEquals(1, model.size());
assertTrue(model.contains(nextSubject, nextPredicate, nextObject),
"model was not as expected: " + model.toString());
}
@Test
public void testSupportedSettings() {
// 13 supported in JSONLDParser + 12 from AbstractRDFParser
assertEquals(25, parser.getSupportedSettings().size());
}
@Test
public void testAllowBackslashEscapingAnyCharacterDefault() {
assertThatThrownBy(() -> parser.parse(new StringReader(BACKSLASH_ESCAPED_TEST_STRING), ""))
.isInstanceOf(RDFParseException.class)
.hasMessageContaining("Could not parse JSONLD");
}
@Test
public void testAllowBackslashEscapingAnyCharacterEnabled() throws Exception {
parser.set(JSONSettings.ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER, true);
parser.parse(new StringReader(BACKSLASH_ESCAPED_TEST_STRING), "");
verifyParseResults(testSubjectIRI, testPredicate, testObjectIRI);
}
@Test
public void testAllowBackslashEscapingAnyCharacterDisabled() {
parser.set(JSONSettings.ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER, false);
assertThatThrownBy(() -> parser.parse(new StringReader(BACKSLASH_ESCAPED_TEST_STRING), ""))
.isInstanceOf(RDFParseException.class)
.hasMessageContaining("Could not parse JSONLD");
}
@Test
public void testAllowCommentsDefault() {
assertThatThrownBy(() -> parser.parse(new StringReader(COMMENTS_TEST_STRING), ""))
.isInstanceOf(RDFParseException.class)
.hasMessageContaining("Could not parse JSONLD");
}
@Test
public void testAllowCommentsEnabled() throws Exception {
parser.set(JSONSettings.ALLOW_COMMENTS, true);
parser.parse(new StringReader(COMMENTS_TEST_STRING), "");
verifyParseResults(testSubjectIRI, testPredicate, testObjectIRI);
}
@Test
public void testAllowCommentsDisabled() {
parser.set(JSONSettings.ALLOW_COMMENTS, false);
assertThatThrownBy(() -> parser.parse(new StringReader(COMMENTS_TEST_STRING), ""))
.isInstanceOf(RDFParseException.class)
.hasMessageContaining("Could not parse JSONLD");
}
@Test
public void testAllowNonNumericNumbersDefault() {
assertThatThrownBy(() -> parser.parse(new StringReader(NON_NUMERIC_NUMBERS_TEST_STRING), ""))
.isInstanceOf(RDFParseException.class)
.hasMessageContaining("Could not parse JSONLD");
}
@Test
@Disabled("temporarily disabled due to breaking on command line but not in Eclipse")
public void testAllowNonNumericNumbersEnabled() throws Exception {
parser.set(JSONSettings.ALLOW_NON_NUMERIC_NUMBERS, true);
parser.parse(new StringReader(NON_NUMERIC_NUMBERS_TEST_STRING), "");
// FIXME: The literal being created has the replacement character as its label,
// indicating it is failing somewhere in the pipeline
verifyParseResults(testSubjectIRI, testPredicate, testObjectLiteralNotANumber);
}
@Test
public void testAllowNonNumericNumbersDisabled() {
parser.set(JSONSettings.ALLOW_NON_NUMERIC_NUMBERS, false);
assertThatThrownBy(() -> parser.parse(new StringReader(NON_NUMERIC_NUMBERS_TEST_STRING), ""))
.isInstanceOf(RDFParseException.class)
.hasMessageContaining("Could not parse JSONLD");
}
@Test
public void testAllowNumericLeadingZeroesDefault() {
assertThatThrownBy(() -> parser.parse(new StringReader(NUMERIC_LEADING_ZEROES_TEST_STRING), ""))
.isInstanceOf(RDFParseException.class)
.hasMessageContaining("Could not parse JSONLD");
}
@Test
public void testAllowNumericLeadingZeroesEnabled() throws Exception {
parser.set(JSONSettings.ALLOW_NUMERIC_LEADING_ZEROS, true);
parser.parse(new StringReader(NUMERIC_LEADING_ZEROES_TEST_STRING), "");
verifyParseResults(testSubjectIRI, testPredicate, testObjectLiteralNumber);
}
@Test
public void testAllowNumericLeadingZeroesDisabled() {
parser.set(JSONSettings.ALLOW_NUMERIC_LEADING_ZEROS, false);
assertThatThrownBy(() -> parser.parse(new StringReader(NUMERIC_LEADING_ZEROES_TEST_STRING), ""))
.isInstanceOf(RDFParseException.class)
.hasMessageContaining("Could not parse JSONLD");
}
@Test
public void testAllowSingleQuotesDefault() {
assertThatThrownBy(() -> parser.parse(new StringReader(SINGLE_QUOTES_TEST_STRING), ""))
.isInstanceOf(RDFParseException.class)
.hasMessageContaining("Could not parse JSONLD");
}
@Test
public void testAllowSingleQuotesEnabled() throws Exception {
parser.set(JSONSettings.ALLOW_SINGLE_QUOTES, true);
parser.parse(new StringReader(SINGLE_QUOTES_TEST_STRING), "");
verifyParseResults(testSubjectIRI, testPredicate, testObjectLiteralNumber);
}
@Test
public void testAllowSingleQuotesDisabled() {
parser.set(JSONSettings.ALLOW_SINGLE_QUOTES, false);
assertThatThrownBy(() -> parser.parse(new StringReader(SINGLE_QUOTES_TEST_STRING), ""))
.isInstanceOf(RDFParseException.class)
.hasMessageContaining("Could not parse JSONLD");
}
@Test
public void testAllowUnquotedControlCharactersDefault() {
assertThatThrownBy(() -> parser.parse(new StringReader(UNQUOTED_CONTROL_CHARS_TEST_STRING), ""))
.isInstanceOf(RDFParseException.class)
.hasMessageContaining("Could not parse JSONLD");
}
@Test
public void testAllowUnquotedControlCharactersEnabled() throws Exception {
parser.set(JSONSettings.ALLOW_UNQUOTED_CONTROL_CHARS, true);
parser.parse(new StringReader(UNQUOTED_CONTROL_CHARS_TEST_STRING), "");
verifyParseResults(testSubjectIRI, testPredicate, testObjectLiteralUnquotedControlChar);
}
@Test
public void testAllowUnquotedControlCharactersDisabled() {
parser.set(JSONSettings.ALLOW_UNQUOTED_CONTROL_CHARS, false);
assertThatThrownBy(() -> parser.parse(new StringReader(UNQUOTED_CONTROL_CHARS_TEST_STRING), ""))
.isInstanceOf(RDFParseException.class)
.hasMessageContaining("Could not parse JSONLD");
}
@Test
public void testAllowUnquotedFieldNamesDefault() {
assertThatThrownBy(() -> parser.parse(new StringReader(UNQUOTED_FIELD_NAMES_TEST_STRING), ""))
.isInstanceOf(RDFParseException.class)
.hasMessageContaining("Could not parse JSONLD");
}
@Test
public void testAllowUnquotedFieldNamesEnabled() throws Exception {
parser.set(JSONSettings.ALLOW_UNQUOTED_FIELD_NAMES, true);
parser.parse(new StringReader(UNQUOTED_FIELD_NAMES_TEST_STRING), "");
verifyParseResults(testSubjectIRI, testPredicate, testObjectLiteralNumber);
}
@Test
public void testAllowUnquotedFieldNamesDisabled() {
parser.set(JSONSettings.ALLOW_UNQUOTED_FIELD_NAMES, false);
assertThatThrownBy(() -> parser.parse(new StringReader(UNQUOTED_FIELD_NAMES_TEST_STRING), ""))
.isInstanceOf(RDFParseException.class)
.hasMessageContaining("Could not parse JSONLD");
}
@Test
public void testAllowYamlCommentsDefault() {
assertThatThrownBy(() -> parser.parse(new StringReader(YAML_COMMENTS_TEST_STRING), ""))
.isInstanceOf(RDFParseException.class)
.hasMessageContaining("Could not parse JSONLD");
}
@Test
public void testAllowYamlCommentsEnabled() throws Exception {
parser.set(JSONSettings.ALLOW_YAML_COMMENTS, true);
parser.parse(new StringReader(YAML_COMMENTS_TEST_STRING), "");
verifyParseResults(testSubjectIRI, testPredicate, testObjectIRI);
}
@Test
public void testAllowYamlCommentsDisabled() {
parser.set(JSONSettings.ALLOW_YAML_COMMENTS, false);
assertThatThrownBy(() -> parser.parse(new StringReader(YAML_COMMENTS_TEST_STRING), ""))
.isInstanceOf(RDFParseException.class)
.hasMessageContaining("Could not parse JSONLD");
}
@Test
public void testAllowTrailingCommaDefault() {
assertThatThrownBy(() -> parser.parse(new StringReader(TRAILING_COMMA_TEST_STRING), ""))
.isInstanceOf(RDFParseException.class)
.hasMessageContaining("Could not parse JSONLD");
}
@Test
public void testAllowTrailingCommaEnabled() throws Exception {
parser.set(JSONSettings.ALLOW_TRAILING_COMMA, true);
parser.parse(new StringReader(TRAILING_COMMA_TEST_STRING), "");
verifyParseResults(testSubjectIRI, testPredicate, testObjectIRI);
}
@Test
public void testAllowTrailingCommaDisabled() {
parser.set(JSONSettings.ALLOW_TRAILING_COMMA, false);
assertThatThrownBy(() -> parser.parse(new StringReader(TRAILING_COMMA_TEST_STRING), ""))
.isInstanceOf(RDFParseException.class)
.hasMessageContaining("Could not parse JSONLD");
}
@Test
public void testIncludeSourceLocationDefault() throws Exception {
final Reader source = new StringReader(YAML_COMMENTS_TEST_STRING);
try {
parser.parse(source, "");
fail("Expected to find an exception");
} catch (RDFParseException e) {
assertNotNull(e.getCause());
assertTrue(e.getCause() instanceof JsonProcessingException);
JsonProcessingException cause = (JsonProcessingException) e.getCause();
assertEquals(2, cause.getLocation().getLineNr());
assertEquals(3, cause.getLocation().getColumnNr());
assertNotEquals(ContentReference.unknown(), cause.getLocation().contentReference());
assertEquals(source, cause.getLocation().contentReference().getRawContent());
}
}
@Test
public void testIncludeSourceLocationEnabled() throws Exception {
final Reader source = new StringReader(YAML_COMMENTS_TEST_STRING);
try {
parser.set(JSONSettings.INCLUDE_SOURCE_IN_LOCATION, true);
parser.parse(source, "");
fail("Expected to find an exception");
} catch (RDFParseException e) {
assertNotNull(e.getCause());
assertTrue(e.getCause() instanceof JsonProcessingException);
JsonProcessingException cause = (JsonProcessingException) e.getCause();
assertEquals(2, cause.getLocation().getLineNr());
assertEquals(3, cause.getLocation().getColumnNr());
assertNotEquals(ContentReference.unknown(), cause.getLocation().contentReference());
assertEquals(source, cause.getLocation().contentReference().getRawContent());
}
}
@Test
public void testIncludeSourceLocationDisabled() throws Exception {
try {
parser.set(JSONSettings.INCLUDE_SOURCE_IN_LOCATION, false);
parser.parse(new StringReader(YAML_COMMENTS_TEST_STRING), "");
fail("Expected to find an exception");
} catch (RDFParseException e) {
assertNotNull(e.getCause());
assertTrue(e.getCause() instanceof JsonProcessingException);
JsonProcessingException cause = (JsonProcessingException) e.getCause();
assertEquals(2, cause.getLocation().getLineNr());
assertEquals(3, cause.getLocation().getColumnNr());
assertEquals(ContentReference.unknown(), cause.getLocation().contentReference());
}
}
@Test
public void testStrictDuplicateDetectionDefault() throws Exception {
parser.set(JSONSettings.STRICT_DUPLICATE_DETECTION, false);
parser.parse(new StringReader(STRICT_DUPLICATE_DETECTION_TEST_STRING), "");
verifyParseResults(testSubjectIRI, testPredicate, testObjectIRI);
}
@Test
public void testStrictDuplicateDetectionEnabled() {
parser.set(JSONSettings.STRICT_DUPLICATE_DETECTION, true);
assertThatThrownBy(() -> parser.parse(new StringReader(STRICT_DUPLICATE_DETECTION_TEST_STRING), ""))
.isInstanceOf(RDFParseException.class)
.hasMessageContaining("Could not parse JSONLD");
}
@Test
public void testStrictDuplicateDetectionDisabled() throws Exception {
parser.set(JSONSettings.STRICT_DUPLICATE_DETECTION, false);
parser.parse(new StringReader(STRICT_DUPLICATE_DETECTION_TEST_STRING), "");
verifyParseResults(testSubjectIRI, testPredicate, testObjectIRI);
}
@Test
public void testDocumentLoader() throws Exception {
DocumentLoader loader = new DocumentLoader();
loader.addInjectedDoc("http://example.com/context.jsonld", LOADER_CONTEXT);
parser.getParserConfig().set(JSONLDSettings.DOCUMENT_LOADER, loader);
parser.parse(new StringReader(LOADER_JSONLD), "");
assertTrue(model.predicates().contains(testPredicate));
}
}