Fix #516 - Handle STU3 invalid enum values with an appropriate exception

This commit is contained in:
James 2016-12-10 14:14:22 -05:00
parent 1fba4ff265
commit ee63bbea74
20 changed files with 524 additions and 173 deletions

View File

@ -13,69 +13,68 @@ import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.server.IBundleProvider; import ca.uhn.fhir.rest.server.IBundleProvider;
import ca.uhn.fhir.rest.server.IResourceProvider; import ca.uhn.fhir.rest.server.IResourceProvider;
@SuppressWarnings("null") @SuppressWarnings("null")
//START SNIPPET: provider // START SNIPPET: provider
public class PagingPatientProvider implements IResourceProvider { public class PagingPatientProvider implements IResourceProvider {
/** /**
* Search for Patient resources matching a given family name * Search for Patient resources matching a given family name
*/ */
@Search @Search
public IBundleProvider search(@RequiredParam(name = Patient.SP_FAMILY) StringParam theFamily) { public IBundleProvider search(@RequiredParam(name = Patient.SP_FAMILY) StringParam theFamily) {
final InstantDt searchTime = InstantDt.withCurrentTime(); final InstantDt searchTime = InstantDt.withCurrentTime();
/* /**
* First, we'll search the database for a set of database row IDs that * First, we'll search the database for a set of database row IDs that
* match the given search criteria. That way we can keep just the * match the given search criteria. That way we can keep just the row IDs
* row IDs around, and load the actual resources on demand later * around, and load the actual resources on demand later as the client
* as the client pages through them. * pages through them.
*/ */
final List<Long> matchingResourceIds = null; // <-- implement this final List<Long> matchingResourceIds = null; // <-- implement this
/* /**
* Return a bundle provider which can page through the IDs and * Return a bundle provider which can page through the IDs and return the
* return the resources that go with them. * resources that go with them.
*/ */
return new IBundleProvider() { return new IBundleProvider() {
@Override @Override
public int size() { public int size() {
return matchingResourceIds.size(); return matchingResourceIds.size();
} }
@Override @Override
public List<IBaseResource> getResources(int theFromIndex, int theToIndex) { public List<IBaseResource> getResources(int theFromIndex, int theToIndex) {
int end = Math.max(theToIndex, matchingResourceIds.size() - 1); int end = Math.max(theToIndex, matchingResourceIds.size() - 1);
List<Long> idsToReturn = matchingResourceIds.subList(theFromIndex, end); List<Long> idsToReturn = matchingResourceIds.subList(theFromIndex, end);
return loadResourcesByIds(idsToReturn); return loadResourcesByIds(idsToReturn);
} }
@Override @Override
public InstantDt getPublished() { public InstantDt getPublished() {
return searchTime; return searchTime;
} }
@Override @Override
public Integer preferredPageSize() { public Integer preferredPageSize() {
// Typically this method just returns null // Typically this method just returns null
return null; return null;
} }
}; };
} }
/** /**
* Load a list of patient resources given their IDs * Load a list of patient resources given their IDs
*/ */
private List<IBaseResource> loadResourcesByIds(List<Long> theIdsToReturn) { private List<IBaseResource> loadResourcesByIds(List<Long> theIdsToReturn) {
// .. implement this search against the database .. // .. implement this search against the database ..
return null; return null;
} }
@Override @Override
public Class<? extends IResource> getResourceType() { public Class<? extends IResource> getResourceType() {
return Patient.class; return Patient.class;
} }
} }
//END SNIPPET: provider // END SNIPPET: provider

View File

@ -1,5 +1,8 @@
package ca.uhn.fhir.parser; package ca.uhn.fhir.parser;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.parser.json.JsonLikeValue.ValueType; import ca.uhn.fhir.parser.json.JsonLikeValue.ValueType;
@ -24,7 +27,13 @@ import ca.uhn.fhir.parser.json.JsonLikeValue.ValueType;
*/ */
/** /**
* The default error handler, which logs issues but does not abort parsing * The default error handler, which logs issues but does not abort parsing, with only one exception:
* <p>
* The {@link #invalidValue(ca.uhn.fhir.parser.IParserErrorHandler.IParseLocation, String, String)}
* method will throw a {@link DataFormatException} by default since ignoring this type of error
* can lead to data loss (since invalid values are silently ignored). See
* {@link #setErrorOnInvalidValue(boolean)} for information on this.
* </p>
* *
* @see IParser#setParserErrorHandler(IParserErrorHandler) * @see IParser#setParserErrorHandler(IParserErrorHandler)
* @see FhirContext#setParserErrorHandler(IParserErrorHandler) * @see FhirContext#setParserErrorHandler(IParserErrorHandler)
@ -32,6 +41,8 @@ import ca.uhn.fhir.parser.json.JsonLikeValue.ValueType;
public class LenientErrorHandler implements IParserErrorHandler { public class LenientErrorHandler implements IParserErrorHandler {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(LenientErrorHandler.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(LenientErrorHandler.class);
private static final StrictErrorHandler STRICT_ERROR_HANDLER = new StrictErrorHandler();
private boolean myErrorOnInvalidValue = true;
private boolean myLogErrors; private boolean myLogErrors;
/** /**
@ -67,11 +78,30 @@ public class LenientErrorHandler implements IParserErrorHandler {
@Override @Override
public void invalidValue(IParseLocation theLocation, String theValue, String theError) { public void invalidValue(IParseLocation theLocation, String theValue, String theError) {
if (myLogErrors) { if (isBlank(theValue) || myErrorOnInvalidValue == false) {
ourLog.warn("Invalid attribute value \"{}\": {}", theValue, theError); if (myLogErrors) {
ourLog.warn("Invalid attribute value \"{}\": {}", theValue, theError);
}
} else {
STRICT_ERROR_HANDLER.invalidValue(theLocation, theValue, theError);
} }
} }
/**
* If set to <code>false</code> (default is <code>true</code>) invalid values will be logged. By
* default invalid attribute values cause this error handler to throw a {@link DataFormatException} (unlike
* other methods in this class which default to simply logging errors).
* <p>
* Note that empty values (e.g. <code>""</code>) will not lead to an error when this is set to
* <code>true</code>, only invalid values (e.g. a gender code of <code>foo</code>)
* </p>
*
* @see #setErrorOnInvalidValue(boolean)
*/
public boolean isErrorOnInvalidValue() {
return myErrorOnInvalidValue;
}
@Override @Override
public void missingRequiredElement(IParseLocation theLocation, String theElementName) { public void missingRequiredElement(IParseLocation theLocation, String theElementName) {
if (myLogErrors) { if (myLogErrors) {
@ -79,6 +109,22 @@ public class LenientErrorHandler implements IParserErrorHandler {
} }
} }
/**
* If set to <code>false</code> (default is <code>true</code>) invalid values will be logged. By
* default invalid attribute values cause this error handler to throw a {@link DataFormatException} (unlike
* other methods in this class which default to simply logging errors).
* <p>
* Note that empty values (e.g. <code>""</code>) will not lead to an error when this is set to
* <code>true</code>, only invalid values (e.g. a gender code of <code>foo</code>)
* </p>
* @return Returns a reference to <code>this</code> for easy method chaining
* @see #isErrorOnInvalidValue()
*/
public LenientErrorHandler setErrorOnInvalidValue(boolean theErrorOnInvalidValue) {
myErrorOnInvalidValue = theErrorOnInvalidValue;
return this;
}
@Override @Override
public void unexpectedRepeatingElement(IParseLocation theLocation, String theElementName) { public void unexpectedRepeatingElement(IParseLocation theLocation, String theElementName) {
if (myLogErrors) { if (myLogErrors) {

View File

@ -2311,6 +2311,8 @@ class ParserState<T> {
} else { } else {
try { try {
myInstance.setValueAsString(theValue); myInstance.setValueAsString(theValue);
} catch (DataFormatException e) {
myErrorHandler.invalidValue(null, theValue, e.getMessage());
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
myErrorHandler.invalidValue(null, theValue, e.getMessage()); myErrorHandler.invalidValue(null, theValue, e.getMessage());
} }

View File

@ -25,6 +25,8 @@ import org.hl7.fhir.instance.model.api.IBaseResource;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.Bundle; import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.parser.LenientErrorHandler;
import ca.uhn.fhir.rest.method.MethodUtil; import ca.uhn.fhir.rest.method.MethodUtil;
import ca.uhn.fhir.rest.server.EncodingEnum; import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
@ -146,7 +148,11 @@ public class ValidationContext<T> extends BaseValidationContext<T> implements IV
@Override @Override
public IBaseResource getResource() { public IBaseResource getResource() {
if (myParsed == null) { if (myParsed == null) {
myParsed = getResourceAsStringEncoding().newParser(getFhirContext()).parseResource(getResourceAsString()); IParser parser = getResourceAsStringEncoding().newParser(getFhirContext());
LenientErrorHandler errorHandler = new LenientErrorHandler();
errorHandler.setErrorOnInvalidValue(false);
parser.setParserErrorHandler(errorHandler);
myParsed = parser.parseResource(getResourceAsString());
} }
return myParsed; return myParsed;
} }

View File

@ -11,7 +11,13 @@ import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets;
import org.apache.commons.io.IOUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.hl7.fhir.dstu3.model.BooleanType; import org.hl7.fhir.dstu3.model.BooleanType;
import org.hl7.fhir.dstu3.model.CodeSystem; import org.hl7.fhir.dstu3.model.CodeSystem;
import org.hl7.fhir.dstu3.model.CodeType; import org.hl7.fhir.dstu3.model.CodeType;
@ -34,6 +40,7 @@ import ca.uhn.fhir.jpa.entity.ResourceTable;
import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion;
import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.entity.TermConcept;
import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum; import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.util.TestUtil;
@ -54,6 +61,30 @@ public class ResourceProviderDstu3ValueSetTest extends BaseResourceProviderDstu3
myExtensionalVsId = myValueSetDao.create(upload, mySrd).getId().toUnqualifiedVersionless(); myExtensionalVsId = myValueSetDao.create(upload, mySrd).getId().toUnqualifiedVersionless();
} }
/**
* #516
*/
@Test
public void testInvalidFilter() throws Exception {
String string = IOUtils.toString(getClass().getResourceAsStream("/bug_516_invalid_expansion.json"), StandardCharsets.UTF_8);
HttpPost post = new HttpPost(ourServerBase+"/ValueSet/%24expand");
post.setEntity(new StringEntity(string, ContentType.parse(Constants.CT_FHIR_JSON_NEW)));
CloseableHttpResponse resp = ourHttpClient.execute(post);
try {
String respString = IOUtils.toString(resp.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(respString);
ourLog.info(resp.toString());
assertEquals(400, resp.getStatusLine().getStatusCode());
assertThat(respString, containsString("Unknown FilterOperator code 'n'"));
}finally {
IOUtils.closeQuietly(resp);
}
}
private CodeSystem createExternalCs() { private CodeSystem createExternalCs() {
CodeSystem codeSystem = new CodeSystem(); CodeSystem codeSystem = new CodeSystem();

View File

@ -0,0 +1,47 @@
{
"resourceType": "ValueSet",
"url": "http://sample/ValueSet/education-levels",
"version": "1",
"name": "Education Levels",
"status": "draft",
"compose": {
"include": [
{
"filter": [
{
"property": "n",
"op": "n",
"value": "365460000"
}
],
"system": "http://snomed.info/sct"
}
],
"exclude": [
{
"concept": [
{
"code": "224298008"
},
{
"code": "365460000"
},
{
"code": "473462005"
},
{
"code": "424587006"
}
],
"system": "http://snomed.info/sct"
}
]
},
"description": "A\nselection of Education Levels",
"text": {
"status": "generated",
"div": "<div\nxmlns=\"http://www.w3.org/1999/xhtml\"><h2>Education\nLevels</h2><tt>http://csiro.au/ValueSet/education-levels</tt><p>A selection of\nEducation Levels</p></div>"
},
"experimental": true,
"date": "2016-07-26"
}

View File

@ -1019,14 +1019,12 @@
<content type="text/xml"> <content type="text/xml">
<Patient xmlns="http://hl7.org/fhir"> <Patient xmlns="http://hl7.org/fhir">
<identifier> <identifier>
<system value=""/>
<value value="NEED"/> <value value="NEED"/>
<assigner> <assigner>
<display value="FHIR"/> <display value="FHIR"/>
</assigner> </assigner>
</identifier> </identifier>
<identifier> <identifier>
<system value=""/>
<value value="E3289"/> <value value="E3289"/>
<assigner> <assigner>
<display value="EPIC"/> <display value="EPIC"/>

View File

@ -27,6 +27,7 @@ import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu2.composite.MetaDt; import ca.uhn.fhir.model.dstu2.composite.MetaDt;
import ca.uhn.fhir.model.dstu2.resource.Bundle; import ca.uhn.fhir.model.dstu2.resource.Bundle;
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator; import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
import ca.uhn.fhir.parser.StrictErrorHandler;
import ca.uhn.fhir.rest.server.ETagSupportEnum; import ca.uhn.fhir.rest.server.ETagSupportEnum;
import ca.uhn.fhir.rest.server.EncodingEnum; import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.IResourceProvider; import ca.uhn.fhir.rest.server.IResourceProvider;
@ -49,10 +50,13 @@ public class JpaServerDemo extends RestfulServer {
* We want to support FHIR DSTU2 format. This means that the server * We want to support FHIR DSTU2 format. This means that the server
* will use the DSTU2 bundle format and other DSTU2 encoding changes. * will use the DSTU2 bundle format and other DSTU2 encoding changes.
* *
* If you want to use DSTU1 instead, change the following line, and change the 2 occurrences of dstu2 in web.xml to dstu1 * If you want to use DSTU1 instead, change the following line, and
* change the 2 occurrences of dstu2 in web.xml to dstu1
*/ */
FhirVersionEnum fhirVersion = FhirVersionEnum.DSTU2; FhirVersionEnum fhirVersion = FhirVersionEnum.DSTU2;
setFhirContext(new FhirContext(fhirVersion)); FhirContext context = new FhirContext(fhirVersion);
setFhirContext(context);
// Get the spring context from the web container (it's declared in web.xml) // Get the spring context from the web container (it's declared in web.xml)
myAppCtx = ContextLoaderListener.getCurrentWebApplicationContext(); myAppCtx = ContextLoaderListener.getCurrentWebApplicationContext();

View File

@ -20,6 +20,8 @@ import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.model.dstu.valueset.ContactSystemEnum; import ca.uhn.fhir.model.dstu.valueset.ContactSystemEnum;
import ca.uhn.fhir.model.primitive.DateTimeDt; import ca.uhn.fhir.model.primitive.DateTimeDt;
import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.parser.StrictErrorHandler;
import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.util.TestUtil;
public class ResourceValidatorTest { public class ResourceValidatorTest {
@ -72,23 +74,34 @@ public class ResourceValidatorTest {
/** /**
* See issue #50 * See issue #50
*/ */
@Test(expected=DataFormatException.class) @Test()
public void testOutOfBoundsDate() { public void testOutOfBoundsDate() {
Patient p = new Patient(); Patient p = new Patient();
p.setBirthDate(new DateTimeDt("2000-12-31")); p.setBirthDate(new DateTimeDt("2000-12-31"));
// Put in an invalid date // Put in an invalid date
String encoded = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(p).replace("2000-12-31", "2000-15-31"); IParser parser = ourCtx.newXmlParser();
parser.setParserErrorHandler(new StrictErrorHandler());
String encoded = parser.setPrettyPrint(true).encodeResourceToString(p).replace("2000-12-31", "2000-15-31");
ourLog.info(encoded); ourLog.info(encoded);
assertThat(encoded, StringContains.containsString("2000-15-31")); assertThat(encoded, StringContains.containsString("2000-15-31"));
ValidationResult result = ourCtx.newValidator().validateWithResult(encoded); FhirValidator validator = ourCtx.newValidator();
String resultString = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(result.toOperationOutcome()); ValidationResult result = validator.validateWithResult(encoded);
String resultString = parser.setPrettyPrint(true).encodeResourceToString(result.toOperationOutcome());
ourLog.info(resultString); ourLog.info(resultString);
assertEquals(2, ((OperationOutcome)result.toOperationOutcome()).getIssue().size()); assertEquals(2, ((OperationOutcome)result.toOperationOutcome()).getIssue().size());
assertThat(resultString, StringContains.containsString("cvc-datatype-valid.1.2.3")); assertThat(resultString, StringContains.containsString("cvc-datatype-valid.1.2.3"));
try {
parser.parseResource(encoded);
fail();
} catch (DataFormatException e) {
assertEquals("DataFormatException at [[row,col {unknown-source}]: [2,4]]: Invalid attribute value \"2000-15-31\": Invalid date/time format: \"2000-15-31\"", e.getMessage());
}
} }
@Test @Test

View File

@ -170,7 +170,10 @@ public class JsonParserDstu2Test {
@Test @Test
public void testParseEmptyValue() { public void testParseEmptyValue() {
String input = "{\"resourceType\":\"QuestionnaireResponse\",\"id\":\"123\",\"authored\":\"\",\"group\":{\"linkId\":\"\"}}"; String input = "{\"resourceType\":\"QuestionnaireResponse\",\"id\":\"123\",\"authored\":\"\",\"group\":{\"linkId\":\"\"}}";
QuestionnaireResponse qr = ourCtx.newJsonParser().parseResource(QuestionnaireResponse.class, input); IParser parser = ourCtx.newJsonParser();
parser.setParserErrorHandler(new LenientErrorHandler().setErrorOnInvalidValue(false));
QuestionnaireResponse qr = parser.parseResource(QuestionnaireResponse.class, input);
assertEquals("QuestionnaireResponse/123", qr.getIdElement().getValue()); assertEquals("QuestionnaireResponse/123", qr.getIdElement().getValue());
assertEquals(null, qr.getAuthored()); assertEquals(null, qr.getAuthored());

View File

@ -2524,7 +2524,10 @@ public class XmlParserDstu2Test {
"</Patient>"; "</Patient>";
//@formatter:on //@formatter:on
ourCtx.newXmlParser().parseResource(resource);
IParser parser = ourCtx.newXmlParser();
parser.setParserErrorHandler(new StrictErrorHandler());
parser.parseResource(resource);
} }
@Test @Test

View File

@ -40,10 +40,12 @@ import ca.uhn.fhir.model.dstu2.valueset.ContactPointSystemEnum;
import ca.uhn.fhir.model.dstu2.valueset.NarrativeStatusEnum; import ca.uhn.fhir.model.dstu2.valueset.NarrativeStatusEnum;
import ca.uhn.fhir.model.dstu2.valueset.UnitsOfTimeEnum; import ca.uhn.fhir.model.dstu2.valueset.UnitsOfTimeEnum;
import ca.uhn.fhir.model.primitive.DateDt; import ca.uhn.fhir.model.primitive.DateDt;
import ca.uhn.fhir.model.primitive.DateTimeDt;
import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.model.primitive.StringDt; import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.parser.StrictErrorHandler;
import ca.uhn.fhir.parser.XmlParserDstu2Test.TestPatientFor327; import ca.uhn.fhir.parser.XmlParserDstu2Test.TestPatientFor327;
import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.util.TestUtil;
import ca.uhn.fhir.validation.schematron.SchematronBaseValidator; import ca.uhn.fhir.validation.schematron.SchematronBaseValidator;
@ -71,26 +73,33 @@ public class ResourceValidatorDstu2Test {
/** /**
* See issue #50 * See issue #50
*/ */
@Test(expected=DataFormatException.class) @Test()
public void testOutOfBoundsDate() { public void testOutOfBoundsDate() {
Patient p = new Patient(); Patient p = new Patient();
p.setBirthDate(new DateDt("2000-15-31")); p.setBirthDate(new DateDt("2000-12-31"));
String encoded = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(p); // Put in an invalid date
IParser parser = ourCtx.newXmlParser();
parser.setParserErrorHandler(new StrictErrorHandler());
String encoded = parser.setPrettyPrint(true).encodeResourceToString(p).replace("2000-12-31", "2000-15-31");
ourLog.info(encoded); ourLog.info(encoded);
assertThat(encoded, StringContains.containsString("2000-15-31")); assertThat(encoded, StringContains.containsString("2000-15-31"));
p = ourCtx.newXmlParser().parseResource(Patient.class, encoded); ValidationResult result = ourCtx.newValidator().validateWithResult(encoded);
assertEquals("2000-15-31", p.getBirthDateElement().getValueAsString()); String resultString = parser.setPrettyPrint(true).encodeResourceToString(result.toOperationOutcome());
assertEquals("2001-03-31", new SimpleDateFormat("yyyy-MM-dd").format(p.getBirthDate()));
ValidationResult result = ourCtx.newValidator().validateWithResult(p);
String resultString = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(result.toOperationOutcome());
ourLog.info(resultString); ourLog.info(resultString);
assertEquals(2, ((OperationOutcome) result.toOperationOutcome()).getIssue().size()); assertEquals(2, ((OperationOutcome)result.toOperationOutcome()).getIssue().size());
assertThat(resultString, StringContains.containsString("2000-15-31")); assertThat(resultString, StringContains.containsString("cvc-pattern-valid"));
try {
parser.parseResource(encoded);
fail();
} catch (DataFormatException e) {
assertEquals("DataFormatException at [[row,col {unknown-source}]: [2,4]]: Invalid attribute value \"2000-15-31\": Invalid date/time format: \"2000-15-31\"", e.getMessage());
}
} }
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")

View File

@ -1,6 +1,7 @@
package org.hl7.fhir.dstu3.model; package org.hl7.fhir.dstu3.model;
import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.hamcrest.Matchers.emptyOrNullString;
import java.util.Calendar; import java.util.Calendar;
import java.util.Date; import java.util.Date;
@ -334,7 +335,7 @@ public abstract class BaseDateTimeType extends PrimitiveType<Date> {
myFractionalSeconds = ""; myFractionalSeconds = "";
} }
setPrecision(precision); myPrecision = precision;
return cal.getTime(); return cal.getTime();
} }
@ -372,8 +373,8 @@ public abstract class BaseDateTimeType extends PrimitiveType<Date> {
if (isBlank(theValue)) { if (isBlank(theValue)) {
throwBadDateFormat(theWholeValue); throwBadDateFormat(theWholeValue);
} else if (theValue.charAt(0) == 'Z') { } else if (theValue.charAt(0) == 'Z') {
clearTimeZone(); myTimeZone = null;
setTimeZoneZulu(true); myTimeZoneZulu = true;
} else if (theValue.length() != 6) { } else if (theValue.length() != 6) {
throwBadDateFormat(theWholeValue, "Timezone offset must be in the form \"Z\", \"-HH:mm\", or \"+HH:mm\""); throwBadDateFormat(theWholeValue, "Timezone offset must be in the form \"Z\", \"-HH:mm\", or \"+HH:mm\"");
} else if (theValue.charAt(3) != ':' || !(theValue.charAt(0) == '+' || theValue.charAt(0) == '-')) { } else if (theValue.charAt(3) != ':' || !(theValue.charAt(0) == '+' || theValue.charAt(0) == '-')) {
@ -381,8 +382,8 @@ public abstract class BaseDateTimeType extends PrimitiveType<Date> {
} else { } else {
parseInt(theWholeValue, theValue.substring(1, 3), 0, 23); parseInt(theWholeValue, theValue.substring(1, 3), 0, 23);
parseInt(theWholeValue, theValue.substring(4, 6), 0, 59); parseInt(theWholeValue, theValue.substring(4, 6), 0, 59);
clearTimeZone(); myTimeZoneZulu = false;
setTimeZone(TimeZone.getTimeZone("GMT" + theValue)); myTimeZone = TimeZone.getTimeZone("GMT" + theValue);
} }
return this; return this;
@ -390,12 +391,14 @@ public abstract class BaseDateTimeType extends PrimitiveType<Date> {
public BaseDateTimeType setTimeZone(TimeZone theTimeZone) { public BaseDateTimeType setTimeZone(TimeZone theTimeZone) {
myTimeZone = theTimeZone; myTimeZone = theTimeZone;
myTimeZoneZulu = false;
updateStringValue(); updateStringValue();
return this; return this;
} }
public BaseDateTimeType setTimeZoneZulu(boolean theTimeZoneZulu) { public BaseDateTimeType setTimeZoneZulu(boolean theTimeZoneZulu) {
myTimeZoneZulu = theTimeZoneZulu; myTimeZoneZulu = theTimeZoneZulu;
myTimeZone = null;
updateStringValue(); updateStringValue();
return this; return this;
} }

View File

@ -70,13 +70,13 @@ public abstract class PrimitiveType<T> extends Type implements IPrimitiveType<T>
} }
public void fromStringValue(String theValue) { public void fromStringValue(String theValue) {
myStringValue = theValue;
if (theValue == null) { if (theValue == null) {
myCoercedValue = null; myCoercedValue = null;
} else { } else {
// NB this might be null // NB this might be null
myCoercedValue = parse(theValue); myCoercedValue = parse(theValue);
} }
myStringValue = theValue;
} }
public T getValue() { public T getValue() {

View File

@ -1,5 +1,7 @@
package ca.uhn.fhir.parser; package ca.uhn.fhir.parser;
import static org.junit.Assert.*;
import org.junit.Test; import org.junit.Test;
import ca.uhn.fhir.parser.json.JsonLikeValue.ValueType; import ca.uhn.fhir.parser.json.JsonLikeValue.ValueType;
@ -46,7 +48,14 @@ public class ErrorHandlerTest {
new LenientErrorHandler().containedResourceWithNoId(null); new LenientErrorHandler().containedResourceWithNoId(null);
new LenientErrorHandler().unknownReference(null, null); new LenientErrorHandler().unknownReference(null, null);
new LenientErrorHandler().incorrectJsonType(null, null, ValueType.ARRAY, ValueType.SCALAR); new LenientErrorHandler().incorrectJsonType(null, null, ValueType.ARRAY, ValueType.SCALAR);
new LenientErrorHandler().invalidValue(null, null, null); new LenientErrorHandler().setErrorOnInvalidValue(false).invalidValue(null, "FOO", "");
new LenientErrorHandler().invalidValue(null, null, "");
try {
new LenientErrorHandler().invalidValue(null, "FOO", "");
fail();
} catch (DataFormatException e) {
// good, this one method defaults to causing an error
}
} }
@Test(expected = DataFormatException.class) @Test(expected = DataFormatException.class)

View File

@ -13,35 +13,78 @@ import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.isNull;
import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.ObjectInputStream;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.*; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.UUID;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.hamcrest.Matchers; import org.hamcrest.Matchers;
import org.hamcrest.core.StringContains; import org.hamcrest.core.StringContains;
import org.hl7.fhir.dstu3.model.*;
import org.hl7.fhir.dstu3.model.Address.AddressUse; import org.hl7.fhir.dstu3.model.Address.AddressUse;
import org.hl7.fhir.dstu3.model.Address.AddressUseEnumFactory; import org.hl7.fhir.dstu3.model.Address.AddressUseEnumFactory;
import org.hl7.fhir.dstu3.model.Attachment;
import org.hl7.fhir.dstu3.model.AuditEvent;
import org.hl7.fhir.dstu3.model.Basic;
import org.hl7.fhir.dstu3.model.Binary;
import org.hl7.fhir.dstu3.model.Bundle;
import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent; import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent;
import org.hl7.fhir.dstu3.model.Bundle.BundleType; import org.hl7.fhir.dstu3.model.Bundle.BundleType;
import org.hl7.fhir.dstu3.model.Claim;
import org.hl7.fhir.dstu3.model.Coding;
import org.hl7.fhir.dstu3.model.Communication;
import org.hl7.fhir.dstu3.model.Condition;
import org.hl7.fhir.dstu3.model.Condition.ConditionVerificationStatus; import org.hl7.fhir.dstu3.model.Condition.ConditionVerificationStatus;
import org.hl7.fhir.dstu3.model.Conformance;
import org.hl7.fhir.dstu3.model.Conformance.UnknownContentCode; import org.hl7.fhir.dstu3.model.Conformance.UnknownContentCode;
import org.hl7.fhir.dstu3.model.Coverage;
import org.hl7.fhir.dstu3.model.DateTimeType;
import org.hl7.fhir.dstu3.model.DateType;
import org.hl7.fhir.dstu3.model.DecimalType;
import org.hl7.fhir.dstu3.model.DiagnosticReport;
import org.hl7.fhir.dstu3.model.EnumFactory;
import org.hl7.fhir.dstu3.model.Enumeration; import org.hl7.fhir.dstu3.model.Enumeration;
import org.hl7.fhir.dstu3.model.Enumerations.AdministrativeGender; import org.hl7.fhir.dstu3.model.Enumerations.AdministrativeGender;
import org.hl7.fhir.dstu3.model.ExplanationOfBenefit;
import org.hl7.fhir.dstu3.model.Extension;
import org.hl7.fhir.dstu3.model.HumanName;
import org.hl7.fhir.dstu3.model.IdType;
import org.hl7.fhir.dstu3.model.Identifier.IdentifierUse; import org.hl7.fhir.dstu3.model.Identifier.IdentifierUse;
import org.hl7.fhir.dstu3.model.Linkage;
import org.hl7.fhir.dstu3.model.Medication;
import org.hl7.fhir.dstu3.model.MedicationRequest;
import org.hl7.fhir.dstu3.model.Observation;
import org.hl7.fhir.dstu3.model.Observation.ObservationStatus; import org.hl7.fhir.dstu3.model.Observation.ObservationStatus;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.dstu3.model.Parameters;
import org.hl7.fhir.dstu3.model.Patient;
import org.hl7.fhir.dstu3.model.PrimitiveType;
import org.hl7.fhir.dstu3.model.Quantity;
import org.hl7.fhir.dstu3.model.QuestionnaireResponse;
import org.hl7.fhir.dstu3.model.Reference;
import org.hl7.fhir.dstu3.model.RelatedPerson;
import org.hl7.fhir.dstu3.model.SampledData;
import org.hl7.fhir.dstu3.model.SimpleQuantity;
import org.hl7.fhir.dstu3.model.StringType;
import org.hl7.fhir.dstu3.model.UriType;
import org.hl7.fhir.dstu3.model.ValueSet;
import org.hl7.fhir.utilities.xhtml.XhtmlNode; import org.hl7.fhir.utilities.xhtml.XhtmlNode;
import org.junit.*; import org.junit.After;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import org.mockito.Mockito; import org.mockito.Mockito;
@ -49,7 +92,6 @@ import com.google.common.collect.Sets;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator; import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.parser.IParserErrorHandler.IParseLocation; import ca.uhn.fhir.parser.IParserErrorHandler.IParseLocation;
import ca.uhn.fhir.parser.PatientWithExtendedContactDstu3.CustomContactComponent; import ca.uhn.fhir.parser.PatientWithExtendedContactDstu3.CustomContactComponent;
import ca.uhn.fhir.parser.XmlParserDstu3Test.TestPatientFor327; import ca.uhn.fhir.parser.XmlParserDstu3Test.TestPatientFor327;
@ -93,7 +135,7 @@ public class JsonParserDstu3Test {
IParser p = ourCtx.newJsonParser().setPrettyPrint(true); IParser p = ourCtx.newJsonParser().setPrettyPrint(true);
String encoded = p.encodeResourceToString(resource); String encoded = p.encodeResourceToString(resource);
ourLog.info(encoded); ourLog.info(encoded);
assertEquals(3, countMatches(encoded, "resourceType")); assertEquals(3, countMatches(encoded, "resourceType"));
} }
@ -122,15 +164,18 @@ public class JsonParserDstu3Test {
*/ */
@Test @Test
public void testParseEmptyValue() { public void testParseEmptyValue() {
String input = "{\"resourceType\":\"QuestionnaireResponse\",\"id\":\"123\",\"authored\":\"\",\"item\":[{\"item\":[{\"linkId\":\"\"}]}]}"; String input = "{\"resourceType\":\"QuestionnaireResponse\",\"id\":\"123\",\"authored\":\"\",\"group\":{\"linkId\":\"\"}}";
QuestionnaireResponse qr = ourCtx.newJsonParser().parseResource(QuestionnaireResponse.class, input); IParser parser = ourCtx.newJsonParser();
parser.setParserErrorHandler(new LenientErrorHandler().setErrorOnInvalidValue(false));
QuestionnaireResponse qr = parser.parseResource(QuestionnaireResponse.class, input);
assertEquals("QuestionnaireResponse/123", qr.getIdElement().getValue()); assertEquals("QuestionnaireResponse/123", qr.getIdElement().getValue());
assertEquals(null, qr.getAuthored()); assertEquals(null, qr.getAuthored());
assertEquals(null, qr.getAuthoredElement().getValue()); assertEquals(null, qr.getAuthoredElement().getValue());
assertEquals(null, qr.getAuthoredElement().getValueAsString()); assertEquals(null, qr.getAuthoredElement().getValueAsString());
assertEquals(null, qr.getItemFirstRep().getItemFirstRep().getLinkId()); assertEquals(null, qr.getItemFirstRep().getLinkId());
assertEquals(null, qr.getItemFirstRep().getItemFirstRep().getLinkIdElement().getValue()); assertEquals(null, qr.getItemFirstRep().getLinkIdElement().getValue());
} }
/** /**
@ -1194,17 +1239,77 @@ public class JsonParserDstu3Test {
assertEquals("{\"resourceType\":\"Observation\",\"valueQuantity\":{\"value\":0.0000000000000001}}", str); assertEquals("{\"resourceType\":\"Observation\",\"valueQuantity\":{\"value\":0.0000000000000001}}", str);
} }
/** /**
* #516 * #516
*/ */
@Test(expected=DataFormatException.class) @Test(expected = DataFormatException.class)
public void testInvalidEnumValue() { public void testInvalidEnumValue() {
String res = "{ \"resourceType\": \"ValueSet\", \"url\": \"http://sample/ValueSet/education-levels\", \"version\": \"1\", \"name\": \"Education Levels\", \"status\": \"draft\", \"compose\": { \"include\": [ { \"filter\": [ { \"property\": \"n\", \"op\": \"n\", \"value\": \"365460000\" } ], \"system\": \"http://snomed.info/sct\" } ], \"exclude\": [ { \"concept\": [ { \"code\": \"224298008\" }, { \"code\": \"365460000\" }, { \"code\": \"473462005\" }, { \"code\": \"424587006\" } ], \"system\": \"http://snomed.info/sct\" } ] }, \"description\": \"A selection of Education Levels\", \"text\": { \"status\": \"generated\", \"div\": \"<div xmlns=\\\"http://www.w3.org/1999/xhtml\\\"><h2>Education Levels</h2><tt>http://csiro.au/ValueSet/education-levels</tt><p>A selection of Education Levels</p></div>\" }, \"experimental\": true, \"date\": \"2016-07-26\" }"; String res = "{ \"resourceType\": \"ValueSet\", \"url\": \"http://sample/ValueSet/education-levels\", \"version\": \"1\", \"name\": \"Education Levels\", \"status\": \"draft\", \"compose\": { \"include\": [ { \"filter\": [ { \"property\": \"n\", \"op\": \"n\", \"value\": \"365460000\" } ], \"system\": \"http://snomed.info/sct\" } ], \"exclude\": [ { \"concept\": [ { \"code\": \"224298008\" }, { \"code\": \"365460000\" }, { \"code\": \"473462005\" }, { \"code\": \"424587006\" } ], \"system\": \"http://snomed.info/sct\" } ] }, \"description\": \"A selection of Education Levels\", \"text\": { \"status\": \"generated\", \"div\": \"<div xmlns=\\\"http://www.w3.org/1999/xhtml\\\"><h2>Education Levels</h2><tt>http://csiro.au/ValueSet/education-levels</tt><p>A selection of Education Levels</p></div>\" }, \"experimental\": true, \"date\": \"2016-07-26\" }";
IParser parser = ourCtx.newJsonParser(); IParser parser = ourCtx.newJsonParser();
parser.setParserErrorHandler(new StrictErrorHandler()); parser.setParserErrorHandler(new StrictErrorHandler());
ValueSet parsed = parser.parseResource(ValueSet.class, res); ValueSet parsed = parser.parseResource(ValueSet.class, res);
fail("DataFormat Invalid attribute exception should be thrown"); fail("DataFormat Invalid attribute exception should be thrown");
} }
@Test
public void testInvalidEnumValueBlank() {
IParserErrorHandler errorHandler = mock(IParserErrorHandler.class);
String res = "{ \"resourceType\": \"Patient\", \"gender\": \"\" }";
IParser parser = ourCtx.newJsonParser();
parser.setParserErrorHandler(errorHandler);
Patient parsed = parser.parseResource(Patient.class, res);
assertEquals(null, parsed.getGenderElement().getValue());
assertEquals(null, parsed.getGenderElement().getValueAsString());
ArgumentCaptor<String> msgCaptor = ArgumentCaptor.forClass(String.class);
verify(errorHandler, times(1)).invalidValue(isNull(IParseLocation.class), eq(""), msgCaptor.capture());
assertEquals("Attribute values must not be empty (\"\")", msgCaptor.getValue());
String encoded = ourCtx.newJsonParser().encodeResourceToString(parsed);
assertEquals("{\"resourceType\":\"Patient\"}", encoded);
}
@Test
public void testInvalidEnumValueInvalid() {
IParserErrorHandler errorHandler = mock(IParserErrorHandler.class);
String res = "{ \"resourceType\": \"Patient\", \"gender\": \"foo\" }";
IParser parser = ourCtx.newJsonParser();
parser.setParserErrorHandler(errorHandler);
Patient parsed = parser.parseResource(Patient.class, res);
assertEquals(null, parsed.getGenderElement().getValue());
assertEquals("foo", parsed.getGenderElement().getValueAsString());
ArgumentCaptor<String> msgCaptor = ArgumentCaptor.forClass(String.class);
verify(errorHandler, times(1)).invalidValue(isNull(IParseLocation.class), eq("foo"), msgCaptor.capture());
assertEquals("Unknown AdministrativeGender code 'foo'", msgCaptor.getValue());
String encoded = ourCtx.newJsonParser().encodeResourceToString(parsed);
assertEquals("{\"resourceType\":\"Patient\",\"gender\":\"foo\"}", encoded);
}
@Test
public void testInvalidDateTimeValueInvalid() throws Exception {
IParserErrorHandler errorHandler = mock(IParserErrorHandler.class);
String res = "{ \"resourceType\": \"Observation\", \"valueDateTime\": \"foo\" }";
IParser parser = ourCtx.newJsonParser();
parser.setParserErrorHandler(errorHandler);
Observation parsed = parser.parseResource(Observation.class, res);
assertEquals(null, parsed.getValueDateTimeType().getValue());
assertEquals("foo", parsed.getValueDateTimeType().getValueAsString());
ArgumentCaptor<String> msgCaptor = ArgumentCaptor.forClass(String.class);
verify(errorHandler, times(1)).invalidValue(isNull(IParseLocation.class), eq("foo"), msgCaptor.capture());
assertEquals("Invalid date/time format: \"foo\"", msgCaptor.getValue());
String encoded = ourCtx.newJsonParser().encodeResourceToString(parsed);
assertEquals("{\"resourceType\":\"Observation\",\"valueDateTime\":\"foo\"}", encoded);
}
/** /**
* #65 * #65

View File

@ -2855,7 +2855,7 @@ public class XmlParserDstu3Test {
/** /**
* See #366 * See #366
*/ */
@Test(expected = DataFormatException.class) @Test()
public void testParseInvalidBoolean() { public void testParseInvalidBoolean() {
//@formatter:off //@formatter:off
String resource = "<Patient xmlns=\"http://hl7.org/fhir\">\n" + String resource = "<Patient xmlns=\"http://hl7.org/fhir\">\n" +
@ -2863,7 +2863,19 @@ public class XmlParserDstu3Test {
"</Patient>"; "</Patient>";
//@formatter:on //@formatter:on
ourCtx.newXmlParser().parseResource(resource); IParser p = ourCtx.newXmlParser();
try {
p.parseResource(resource);
fail();
} catch (DataFormatException e) {
assertEquals("DataFormatException at [[row,col {unknown-source}]: [2,4]]: Invalid attribute value \"1\": Invalid boolean string: '1'", e.getMessage());
}
LenientErrorHandler errorHandler = new LenientErrorHandler();
assertEquals(true, errorHandler.isErrorOnInvalidValue());
errorHandler.setErrorOnInvalidValue(false);
p.setParserErrorHandler(errorHandler);
} }
@Test @Test

View File

@ -3,8 +3,10 @@ package org.hl7.fhir.dstu3.hapi.validation;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.stringContainsInOrder; import static org.hamcrest.Matchers.stringContainsInOrder;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
@ -12,14 +14,24 @@ import java.util.Date;
import java.util.List; import java.util.List;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.hl7.fhir.dstu3.model.*; import org.hamcrest.core.StringContains;
import org.hl7.fhir.dstu3.model.CodeableConcept;
import org.hl7.fhir.dstu3.model.Coding;
import org.hl7.fhir.dstu3.model.Condition;
import org.hl7.fhir.dstu3.model.Condition.ConditionVerificationStatus; import org.hl7.fhir.dstu3.model.Condition.ConditionVerificationStatus;
import org.hl7.fhir.dstu3.model.DateType;
import org.hl7.fhir.dstu3.model.Narrative.NarrativeStatus; import org.hl7.fhir.dstu3.model.Narrative.NarrativeStatus;
import org.hl7.fhir.dstu3.model.OperationOutcome;
import org.hl7.fhir.dstu3.model.Patient;
import org.hl7.fhir.dstu3.model.Reference;
import org.hl7.fhir.dstu3.model.StringType;
import org.junit.AfterClass; import org.junit.AfterClass;
import org.junit.Test; import org.junit.Test;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.parser.StrictErrorHandler;
import ca.uhn.fhir.parser.XmlParserDstu3Test; import ca.uhn.fhir.parser.XmlParserDstu3Test;
import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.util.TestUtil;
import ca.uhn.fhir.validation.FhirValidator; import ca.uhn.fhir.validation.FhirValidator;
@ -37,6 +49,39 @@ public class ResourceValidatorDstu3Test {
TestUtil.clearAllStaticFieldsForUnitTest(); TestUtil.clearAllStaticFieldsForUnitTest();
} }
/**
* See issue #50
*/
@Test()
public void testOutOfBoundsDate() {
Patient p = new Patient();
p.setBirthDateElement(new DateType("2000-12-31"));
// Put in an invalid date
IParser parser = ourCtx.newXmlParser();
parser.setParserErrorHandler(new StrictErrorHandler());
String encoded = parser.setPrettyPrint(true).encodeResourceToString(p).replace("2000-12-31", "2000-15-31");
ourLog.info(encoded);
assertThat(encoded, StringContains.containsString("2000-15-31"));
ValidationResult result = ourCtx.newValidator().validateWithResult(encoded);
String resultString = parser.setPrettyPrint(true).encodeResourceToString(result.toOperationOutcome());
ourLog.info(resultString);
assertEquals(2, ((OperationOutcome)result.toOperationOutcome()).getIssue().size());
assertThat(resultString, StringContains.containsString("cvc-pattern-valid"));
try {
parser.parseResource(encoded);
fail();
} catch (DataFormatException e) {
assertEquals("DataFormatException at [[row,col {unknown-source}]: [2,4]]: Invalid attribute value \"2000-15-31\": Invalid date/time format: \"2000-15-31\"", e.getMessage());
}
}
/** /**
* Make sure that the elements that appear in all resources (meta, language, extension, etc) all appear in the correct order * Make sure that the elements that appear in all resources (meta, language, extension, etc) all appear in the correct order
*/ */

View File

@ -99,6 +99,21 @@
not by throwing an exception. Thanks to not by throwing an exception. Thanks to
Michael Lawley for the pull request! Michael Lawley for the pull request!
</action> </action>
<action type="add" issue="516">
When parsing DSTU3 resources with enumerated
types that contain invalid values, the parser will now
invoke the parserErrorHandler. For example, when parsing
<![CDATA[
<code>{"resourceType":"Patient", "gender":"foo"}</code>
]]>
the previous behaviour was to throw an InvalidArgumentException.
Now, the parserErrorHandler is invoked. In addition, thw
LenientErrorHandler has been modified so that this one case
will result in a DataFormatException. This has the effect
that servers which receive an invalid enum velue will return
an HTTP 400 instead of an HTTP 500. Thanks to Jim
Steel for reporting!
</action>
</release> </release>
<release version="2.1" date="2016-11-11"> <release version="2.1" date="2016-11-11">
<action type="add"> <action type="add">

View File

@ -14,12 +14,6 @@
sections below. sections below.
</p> </p>
<ul> <ul>
<li>
<b>Resource Validation</b>
is validation of the raw or parsed resource against
the official FHIR validation rules (e.g. Schema/Schematron/Profile/StructureDefinition/ValueSet)
as well as against custom profiles which have been developed.
</li>
<li> <li>
<b>Parser Validation</b> <b>Parser Validation</b>
is validation at runtime during the parsing is validation at runtime during the parsing
@ -31,10 +25,77 @@
that no data is being lost during parsing, but is less comprehensive than resource validation that no data is being lost during parsing, but is less comprehensive than resource validation
against raw text data. against raw text data.
</li> </li>
<li>
<b>Resource Validation</b>
is validation of the raw or parsed resource against
the official FHIR validation rules (e.g. Schema/Schematron/Profile/StructureDefinition/ValueSet)
as well as against custom profiles which have been developed.
</li>
</ul> </ul>
</section> </section>
<!-- Parser Validation -->
<section name="Parser Validation">
<p>
Parser validation is controlled by calling
<code>setParserErrorHandler(IParserErrorHandler)</code>
on
either the FhirContext or on individual parser instances. This method
takes an
<code>IParserErrorHandler</code>
, which is a callback that
will be invoked any time a parse issue is detected.
</p>
<p>
There are two implementations of
<code>IParserErrorHandler</code>
worth
mentioning. You can also supply your own implementation if you want.
</p>
<ul>
<li>
<a href="./apidocs/ca/uhn/fhir/parser/LenientErrorHandler.html">LenientErrorHandler</a>
logs any errors but does not abort parsing. By default this handler is used, and it
logs errors at "warning" level. It can also be configured to silently
ignore issues.
</li>
<li>
<a href="./apidocs/ca/uhn/fhir/parser/StrictErrorHandler.html">StrictErrorHandler</a>
throws a
<code>DataFormatException</code>
if any errors are detected.
</li>
</ul>
<p>
The following example shows how to configure a parser to use strict validation.
</p>
<macro name="snippet">
<param name="id" value="parserValidation" />
<param name="file" value="examples/src/main/java/example/ValidatorExamples.java" />
</macro>
<p>
You can also configure the error handler at the FhirContext level, which is useful
for clients.
</p>
<macro name="snippet">
<param name="id" value="clientValidation" />
<param name="file" value="examples/src/main/java/example/ValidatorExamples.java" />
</macro>
<p>
FhirContext level validators can also be useful on servers.
</p>
<macro name="snippet">
<param name="id" value="serverValidation" />
<param name="file" value="examples/src/main/java/example/ValidatorExamples.java" />
</macro>
</section>
<!-- RESOURCE VALIDATION --> <!-- RESOURCE VALIDATION -->
<section name="Resource Validation"> <section name="Resource Validation">
@ -223,66 +284,6 @@
</section> </section>
<section name="Parser Validation">
<p>
Parser validation is controlled by calling
<code>setParserErrorHandler(IParserErrorHandler)</code>
on
either the FhirContext or on individual parser instances. This method
takes an
<code>IParserErrorHandler</code>
, which is a callback that
will be invoked any time a parse issue is detected.
</p>
<p>
There are two implementations of
<code>IParserErrorHandler</code>
worth
mentioning. You can also supply your own implementation if you want.
</p>
<ul>
<li>
<a href="./apidocs/ca/uhn/fhir/parser/LenientErrorHandler.html">LenientErrorHandler</a>
logs any errors but does not abort parsing. By default this handler is used, and it
logs errors at "warning" level. It can also be configured to silently
ignore issues.
</li>
<li>
<a href="./apidocs/ca/uhn/fhir/parser/StrictErrorHandler.html">StrictErrorHandler</a>
throws a
<code>DataFormatException</code>
if any errors are detected.
</li>
</ul>
<p>
The following example shows how to configure a parser to use strict validation.
</p>
<macro name="snippet">
<param name="id" value="parserValidation" />
<param name="file" value="examples/src/main/java/example/ValidatorExamples.java" />
</macro>
<p>
You can also configure the error handler at the FhirContext level, which is useful
for clients.
</p>
<macro name="snippet">
<param name="id" value="clientValidation" />
<param name="file" value="examples/src/main/java/example/ValidatorExamples.java" />
</macro>
<p>
FhirContext level validators can also be useful on servers.
</p>
<macro name="snippet">
<param name="id" value="serverValidation" />
<param name="file" value="examples/src/main/java/example/ValidatorExamples.java" />
</macro>
</section>
</body> </body>
</document> </document>