Add support for Attachment validation + max size and max decimal places validation

This commit is contained in:
Grahame Grieve 2020-05-21 13:22:21 +10:00
parent 5f1889765d
commit 130caef348
8 changed files with 141 additions and 12 deletions

View File

@ -33,6 +33,7 @@ package org.hl7.fhir.r5.utils;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.util.List;
import java.util.Locale;
@ -81,6 +82,8 @@ public interface IResourceValidator {
ReferenceValidationPolicy validationPolicy(Object appContext, String path, String url);
boolean resolveURL(Object appContext, String path, String url) throws IOException, FHIRException;
byte[] fetchRaw(String url) throws MalformedURLException, IOException; // for attachment checking
void setLocale(Locale locale);
}

View File

@ -76,6 +76,7 @@ import org.hl7.fhir.r5.model.CodeType;
import org.hl7.fhir.r5.model.CodeableConcept;
import org.hl7.fhir.r5.model.Coding;
import org.hl7.fhir.r5.model.DataType;
import org.hl7.fhir.r5.model.DecimalType;
import org.hl7.fhir.r5.model.DomainResource;
import org.hl7.fhir.r5.model.Element;
import org.hl7.fhir.r5.model.ElementDefinition;
@ -83,6 +84,7 @@ import org.hl7.fhir.r5.model.Extension;
import org.hl7.fhir.r5.model.ExtensionHelper;
import org.hl7.fhir.r5.model.Factory;
import org.hl7.fhir.r5.model.Identifier;
import org.hl7.fhir.r5.model.Integer64Type;
import org.hl7.fhir.r5.model.IntegerType;
import org.hl7.fhir.r5.model.MarkdownType;
import org.hl7.fhir.r5.model.OperationOutcome.OperationOutcomeIssueComponent;
@ -327,6 +329,10 @@ public class ToolingExtensions {
return ((CodeType) ex.getValue()).getValue();
if (ex.getValue() instanceof IntegerType)
return ((IntegerType) ex.getValue()).asStringValue();
if (ex.getValue() instanceof Integer64Type)
return ((Integer64Type) ex.getValue()).asStringValue();
if (ex.getValue() instanceof DecimalType)
return ((DecimalType) ex.getValue()).asStringValue();
if ((ex.getValue() instanceof MarkdownType))
return ((MarkdownType) ex.getValue()).getValue();
if (!(ex.getValue() instanceof StringType))
@ -346,6 +352,10 @@ public class ToolingExtensions {
return ((CodeType) ex.getValue()).getValue();
if (ex.getValue() instanceof IntegerType)
return ((IntegerType) ex.getValue()).asStringValue();
if (ex.getValue() instanceof Integer64Type)
return ((Integer64Type) ex.getValue()).asStringValue();
if (ex.getValue() instanceof DecimalType)
return ((DecimalType) ex.getValue()).asStringValue();
if ((ex.getValue() instanceof MarkdownType))
return ((MarkdownType) ex.getValue()).getValue();
return null;

View File

@ -171,6 +171,7 @@ public class I18nConstants {
public final static String TERMINOLOGY_TX_VALUESET_NOTFOUND = "Terminology_TX_ValueSet_NotFound";
public final static String TERMINOLOGY_TX_VALUESET_NOTFOUND2 = "Terminology_TX_ValueSet_NotFound2";
public final static String TYPE_SPECIFIC_CHECKS_DT_BASE64_VALID = "Type_Specific_Checks_DT_Base64_Valid";
public final static String TYPE_SPECIFIC_CHECKS_DT_BASE64_TOO_LONG = "TYPE_SPECIFIC_CHECKS_DT_BASE64_TOO_LONG";
public final static String TYPE_SPECIFIC_CHECKS_DT_BOOLEAN_VALUE = "Type_Specific_Checks_DT_Boolean_Value";
public final static String TYPE_SPECIFIC_CHECKS_DT_CODE_WS = "Type_Specific_Checks_DT_Code_WS";
public final static String TYPE_SPECIFIC_CHECKS_DT_DATETIME_REASONABLE = "Type_Specific_Checks_DT_DateTime_Reasonable";
@ -180,6 +181,7 @@ public class I18nConstants {
public final static String TYPE_SPECIFIC_CHECKS_DT_DATE_VALID = "Type_Specific_Checks_DT_Date_Valid";
public final static String TYPE_SPECIFIC_CHECKS_DT_DECIMAL_RANGE = "Type_Specific_Checks_DT_Decimal_Range";
public final static String TYPE_SPECIFIC_CHECKS_DT_DECIMAL_VALID = "Type_Specific_Checks_DT_Decimal_Valid";
public final static String TYPE_SPECIFIC_CHECKS_DT_DECIMAL_CHARS = "TYPE_SPECIFIC_CHECKS_DT_DECIMAL_CHARS";
public final static String TYPE_SPECIFIC_CHECKS_DT_IDENTIFIER_SYSTEM = "Type_Specific_Checks_DT_Identifier_System";
public final static String TYPE_SPECIFIC_CHECKS_DT_IDENTIFIER_IETF_SYSTEM_VALUE = "TYPE_SPECIFIC_CHECKS_DT_IDENTIFIER_IETF_SYSTEM_VALUE";
public final static String TYPE_SPECIFIC_CHECKS_DT_ID_VALID = "Type_Specific_Checks_DT_ID_Valid";
@ -206,6 +208,13 @@ public class I18nConstants {
public final static String TYPE_SPECIFIC_CHECKS_DT_URL_RESOLVE = "Type_Specific_Checks_DT_URL_Resolve";
public final static String TYPE_SPECIFIC_CHECKS_DT_UUID_STRAT = "Type_Specific_Checks_DT_UUID_Strat";
public final static String TYPE_SPECIFIC_CHECKS_DT_UUID_VAID = "Type_Specific_Checks_DT_UUID_Vaid";
public final static String TYPE_SPECIFIC_CHECKS_DT_ATT_SIZE_CORRECT = "TYPE_SPECIFIC_CHECKS_DT_ATT_SIZE_CORRECT";
public final static String TYPE_SPECIFIC_CHECKS_DT_ATT_SIZE_INVALID = "TYPE_SPECIFIC_CHECKS_DT_ATT_SIZE_INVALID";
public final static String TYPE_SPECIFIC_CHECKS_DT_ATT_NO_FETCHER = "TYPE_SPECIFIC_CHECKS_DT_ATT_NO_FETCHER";
public final static String TYPE_SPECIFIC_CHECKS_DT_ATT_UNKNOWN_URL_SCHEME = "TYPE_SPECIFIC_CHECKS_DT_ATT_UNKNOWN_URL_SCHEME";
public final static String TYPE_SPECIFIC_CHECKS_DT_ATT_URL_ERROR = "TYPE_SPECIFIC_CHECKS_DT_ATT_URL_ERROR";
public final static String TYPE_SPECIFIC_CHECKS_DT_ATT_TOO_LONG = "TYPE_SPECIFIC_CHECKS_DT_ATT_TOO_LONG";
public final static String TYPE_SPECIFIC_CHECKS_DT_ATT_NO_CONTENT = "TYPE_SPECIFIC_CHECKS_DT_ATT_NO_CONTENT";
public final static String VALIDATION_BUNDLE_MESSAGE = "Validation_BUNDLE_Message";
public final static String VALIDATION_VAL_CONTENT_UNKNOWN = "Validation_VAL_Content_Unknown";
public final static String VALIDATION_VAL_NOTYPE = "Validation_VAL_NoType";

View File

@ -485,4 +485,13 @@ XHTML_URL_EMPTY = URL is empty
XHTML_URL_INVALID_CHARS = URL contains Invalid Characters ({0})
TERMINOLOGY_TX_SYSTEM_HTTPS = The system URL ''{0}'' wrongly starts with https: not http:
CODESYSTEM_CS_NO_VS_NOTCOMPLETE = Review the All Codes Value Set - incomplete CodeSystems generally should not have an all codes value set specified
TYPE_SPECIFIC_CHECKS_DT_IDENTIFIER_IETF_SYSTEM_VALUE = if identifier.system is ''urn:ietf:rfc:3986'', then the identifier.value must be a full URI (e.g. start with a scheme)
TYPE_SPECIFIC_CHECKS_DT_IDENTIFIER_IETF_SYSTEM_VALUE = if identifier.system is ''urn:ietf:rfc:3986'', then the identifier.value must be a full URI (e.g. start with a scheme)
TYPE_SPECIFIC_CHECKS_DT_ATT_SIZE_INVALID = Stated Attachment Size {0} is not valid
TYPE_SPECIFIC_CHECKS_DT_ATT_SIZE_CORRECT = Stated Attachment Size {0} does not match actual attachment size {1}
TYPE_SPECIFIC_CHECKS_DT_ATT_NO_FETCHER = Attachment size cannot be checked because the validator has not been set up to access the network (url = {0})
TYPE_SPECIFIC_CHECKS_DT_ATT_UNKNOWN_URL_SCHEME = Attachment size cannot be checked because the validator doesn't understand how to access {0}
TYPE_SPECIFIC_CHECKS_DT_ATT_URL_ERROR = Attachment size cannot be checked because there was an error accesssing {0}: {1}
TYPE_SPECIFIC_CHECKS_DT_ATT_TOO_LONG = Attachment size is {0} bytes which exceeds the stated limit of {1} bytes
TYPE_SPECIFIC_CHECKS_DT_ATT_NO_CONTENT = Attachments have data and/or url, or else must have either contentType and/oor language
TYPE_SPECIFIC_CHECKS_DT_BASE64_TOO_LONG = Base64 size is {0} bytes which exceeds the stated limit of {1} bytes
TYPE_SPECIFIC_CHECKS_DT_DECIMAL_CHARS = Found {0} decimal places which exceeds the stated limit of {1} digits

View File

@ -42,8 +42,11 @@ import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
import org.hl7.fhir.utilities.xhtml.XhtmlComposer;
import org.xml.sax.SAXException;
import com.google.gson.JsonObject;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
@ -2068,5 +2071,12 @@ public class ValidationEngine implements IValidatorResourceFetcher {
throw new Exception("Target Version not supported yet: "+targetVer);
}
}
@Override
public byte[] fetchRaw(String source) throws IOException {
URL url = new URL(source);
URLConnection c = url.openConnection();
return TextFile.streamToBytes(c.getInputStream());
}
}

View File

@ -34,10 +34,12 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Calendar;
import java.util.Collection;
import java.util.HashMap;
@ -1232,7 +1234,6 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
}
private void checkMaxValueSet(List<ValidationMessage> errors, String path, Element element, StructureDefinition profile, String maxVSUrl, CodeableConcept cc, NodeStack stack) {
// TODO Auto-generated method stub
ValueSet valueset = resolveBindingReference(profile, maxVSUrl, profile.getUrl());
if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, valueset != null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND, describeReference(maxVSUrl))) {
try {
@ -1252,7 +1253,6 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
}
private void checkMaxValueSet(List<ValidationMessage> errors, String path, Element element, StructureDefinition profile, String maxVSUrl, Coding c, NodeStack stack) {
// TODO Auto-generated method stub
ValueSet valueset = resolveBindingReference(profile, maxVSUrl, profile.getUrl());
if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, valueset != null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND, describeReference(maxVSUrl))) {
try {
@ -1272,7 +1272,6 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
}
private void checkMaxValueSet(List<ValidationMessage> errors, String path, Element element, StructureDefinition profile, String maxVSUrl, String value, NodeStack stack) {
// TODO Auto-generated method stub
ValueSet valueset = resolveBindingReference(profile, maxVSUrl, profile.getUrl());
if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, valueset != null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND, describeReference(maxVSUrl))) {
try {
@ -1313,7 +1312,8 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
checkCodedElement(errors, path, element, profile, theElementCntext, inCodeableConcept, checkDisplay, stack, code, system, display);
}
private void checkCodedElement(List<ValidationMessage> errors, String path, Element element, StructureDefinition profile, ElementDefinition theElementCntext, boolean inCodeableConcept, boolean checkDisplay, NodeStack stack, String theCode, String theSystem, String theDisplay) {
private void checkCodedElement(List<ValidationMessage> errors, String path, Element element, StructureDefinition profile, ElementDefinition theElementCntext, boolean inCodeableConcept, boolean checkDisplay, NodeStack stack,
String theCode, String theSystem, String theDisplay) {
rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, isAbsolute(theSystem), I18nConstants.TERMINOLOGY_TX_SYSTEM_RELATIVE);
warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, Utilities.noString(theCode) || !Utilities.noString(theSystem), I18nConstants.TERMINOLOGY_TX_SYSTEM_NO_CODE);
@ -1783,7 +1783,8 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
String regex = context.getExtensionString(ToolingExtensions.EXT_REGEX);
if (regex != null)
rule(errors, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue().matches(regex), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_REGEX, e.primitiveValue(), regex);
if (type.equals("boolean")) {
rule(errors, IssueType.INVALID, e.line(), e.col(), path, "true".equals(e.primitiveValue()) || "false".equals(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_BOOLEAN_VALUE);
}
@ -1882,6 +1883,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
* 2. This code doesn't actually decode, which is much easier on memory use for big payloads
*/
int charCount = 0;
boolean ok = true;
for (int i = 0; i < encoded.length(); i++) {
char nextChar = encoded.charAt(i);
if (Character.isWhitespace(nextChar)) {
@ -1896,9 +1898,16 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
}
if (charCount > 0 && charCount % 4 != 0) {
ok = false;
String value = encoded.length() < 100 ? encoded : "(snip)";
rule(errors, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_BASE64_VALID, value);
}
if (ok && context.hasExtension("http://hl7.org/fhir/StructureDefinition/maxSize")) {
byte[] cnt = Base64.getDecoder().decode(encoded);
int size = cnt.length;
long def = Long.parseLong(ToolingExtensions.readStringExtension(context, "http://hl7.org/fhir/StructureDefinition/maxSize"));
rule(errors, IssueType.STRUCTURE, e.line(), e.col(), path, size <= def, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_BASE64_TOO_LONG, size, def);
}
}
}
if (type.equals("integer") || type.equals("unsignedInt") || type.equals("positiveInt")) {
@ -1929,6 +1938,11 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
if (rule(errors, IssueType.INVALID, e.line(), e.col(), path, ds == DecimalStatus.OK || ds == DecimalStatus.RANGE, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DECIMAL_VALID, e.primitiveValue()))
warning(errors, IssueType.VALUE, e.line(), e.col(), path, ds != DecimalStatus.RANGE, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DECIMAL_RANGE, e.primitiveValue());
}
if (context.hasExtension("http://hl7.org/fhir/StructureDefinition/maxDecimalPlaces")) {
int dp = e.primitiveValue().contains(".") ? e.primitiveValue().substring(e.primitiveValue().indexOf(".")+1).length() : 0;
int def = Integer.parseInt(ToolingExtensions.readStringExtension(context, "http://hl7.org/fhir/StructureDefinition/maxDecimalPlaces"));
rule(errors, IssueType.STRUCTURE, e.line(), e.col(), path, dp <= def, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DECIMAL_CHARS, dp, def);
}
}
if (type.equals("instant")) {
rule(errors, IssueType.INVALID, e.line(), e.col(), path,
@ -2110,12 +2124,72 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
checkFixedValue(errors, path + ".code", focus.getNamedChild("code"), fixed.getCodeElement(), fixedSource, "code", focus, pattern);
}
private void checkQuantity(List<ValidationMessage> theErrors, String thePath, Element theElement, StructureDefinition theProfile, ElementDefinition theDefinition, boolean theInCodeableConcept, boolean theCheckDisplayInContext, NodeStack theStack) {
String unit = theElement.hasChild("unit") ? theElement.getNamedChild("unit").getValue() : null;
String system = theElement.hasChild("system") ? theElement.getNamedChild("system").getValue() : null;
String code = theElement.hasChild("code") ? theElement.getNamedChild("code").getValue() : null;
private void checkQuantity(List<ValidationMessage> theErrors, String thePath, Element element, StructureDefinition theProfile, ElementDefinition definition, NodeStack theStack) {
String unit = element.hasChild("unit") ? element.getNamedChild("unit").getValue() : null;
String system = element.hasChild("system") ? element.getNamedChild("system").getValue() : null;
String code = element.hasChild("code") ? element.getNamedChild("code").getValue() : null;
checkCodedElement(theErrors, thePath, theElement, theProfile, theDefinition, theInCodeableConcept, theCheckDisplayInContext, theStack, code, system, unit);
// todo: allowedUnits http://hl7.org/fhir/StructureDefinition/elementdefinition-allowedUnits - codeableConcept, or canonical(ValueSet)
// todo: http://hl7.org/fhir/StructureDefinition/iso21090-PQ-translation
if (definition.hasExtension("http://hl7.org/fhir/StructureDefinition/maxDecimalPlaces")) {
String dec = element.getChildValue("value");
int dp = dec.contains(".") ? dec.substring(dec.indexOf(".")+1).length() : 0;
int def = Integer.parseInt(ToolingExtensions.readStringExtension(definition, "http://hl7.org/fhir/StructureDefinition/maxDecimalPlaces"));
rule(theErrors, IssueType.STRUCTURE, element.line(), element.col(), thePath, dp <= def, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DECIMAL_CHARS, dp, def);
}
if (system != null || code != null ) {
checkCodedElement(theErrors, thePath, element, theProfile, definition, false, false, theStack, code, system, unit);
}
}
private void checkAttachment(List<ValidationMessage> errors, String path, Element element, StructureDefinition theProfile, ElementDefinition definition, boolean theInCodeableConcept, boolean theCheckDisplayInContext, NodeStack theStack) {
long size = -1;
// first check size
String fetchError = null;
if (element.hasChild("data")) {
String b64 = element.getChildValue("data");
byte[] cnt = Base64.getDecoder().decode(b64.getBytes());
size = cnt.length;
if (element.hasChild("size")) {
String sz = element.getChildValue("size");
rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, Long.toString(size).equals(sz), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_ATT_SIZE_CORRECT, sz, size);
}
} else if (element.hasChild("size")) {
String sz = element.getChildValue("size");
if (rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, Utilities.isLong(sz), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_ATT_SIZE_INVALID, sz)) {
size = Long.parseLong(sz);
rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, size >= 0, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_ATT_SIZE_INVALID, sz);
}
} else if (element.hasChild("url")) {
String url = element.getChildValue("url");
if (definition.hasExtension("http://hl7.org/fhir/StructureDefinition/maxSize")) {
try {
if (url.startsWith("http://") || url.startsWith("https://")) {
if (fetcher == null) {
fetchError = context.formatMessage(I18nConstants.TYPE_SPECIFIC_CHECKS_DT_ATT_NO_FETCHER, url);
} else {
byte[] cnt = fetcher.fetchRaw(url);
size = cnt.length;
}
} else if (url.startsWith("file:")) {
size = new File(url.substring(5)).length();
} else {
fetchError = context.formatMessage(I18nConstants.TYPE_SPECIFIC_CHECKS_DT_ATT_UNKNOWN_URL_SCHEME, url); }
} catch (Exception e) {
fetchError = context.formatMessage(I18nConstants.TYPE_SPECIFIC_CHECKS_DT_ATT_URL_ERROR, url, e.getMessage());
}
}
}
if (definition.hasExtension("http://hl7.org/fhir/StructureDefinition/maxSize")) {
if (warning(errors, IssueType.STRUCTURE, element.line(), element.col(), path, size >= 0, fetchError)) {
long def = Long.parseLong(ToolingExtensions.readStringExtension(definition, "http://hl7.org/fhir/StructureDefinition/maxSize"));
rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, size <= def, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_ATT_TOO_LONG, size, def);
}
}
rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, (element.hasChild("data") || element.hasChild("url")) || (element.hasChild("contentType") || element.hasChild("language")),
I18nConstants.TYPE_SPECIFIC_CHECKS_DT_ATT_NO_CONTENT);
}
// implementation
@ -3689,7 +3763,9 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
} else if (type.equals("Coding")) {
checkCoding(errors, ei.getPath(), ei.getElement(), profile, ei.definition, inCodeableConcept, checkDisplayInContext, stack);
} else if (type.equals("Quantity")) {
checkQuantity(errors, ei.getPath(), ei.getElement(), profile, ei.definition, inCodeableConcept, checkDisplayInContext, stack);
checkQuantity(errors, ei.getPath(), ei.getElement(), profile, ei.definition, stack);
} else if (type.equals("Attachment")) {
checkAttachment(errors, ei.getPath(), ei.getElement(), profile, ei.definition, inCodeableConcept, checkDisplayInContext, stack);
} else if (type.equals("CodeableConcept")) {
checkDisplay = checkCodeableConcept(errors, ei.getPath(), ei.getElement(), profile, ei.definition, stack);
thisIsCodeableConcept = true;

View File

@ -53,6 +53,9 @@ import org.junit.jupiter.params.provider.MethodSource;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@ -460,4 +463,11 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe
TextFile.stringToFile(content, Utilities.path("[tmp]", "validator-produced-manifest.json"));
}
@Override
public byte[] fetchRaw(String source) throws MalformedURLException, IOException {
URL url = new URL(source);
URLConnection c = url.openConnection();
return TextFile.streamToBytes(c.getInputStream());
}
}

View File

@ -9,6 +9,8 @@ title: FHIR Validator Release Notes
* Fix to check invariants on Elements with type redirections (e.g. ValueSet.compose.exclude)
* fix fatal NPE validating bundles when resource is missing
* fix tests for R5 changes
* Fix to validate code units on Quantity
* Add validation for Attachment & check maxSize extension on base64Binary
## v4.2.30 (2020-05-12)