Skip to content

Commit

Permalink
Merge pull request #31 from MeasureAuthoringTool/MAT-6401_noContextError
Browse files Browse the repository at this point in the history
MAT-6401 missing context error
  • Loading branch information
sb-cecilialiu authored Dec 6, 2024
2 parents efad3df + 7cb6be3 commit 63a2eab
Show file tree
Hide file tree
Showing 7 changed files with 265 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public CqlConversionPayload cqlToElmJson(
Boolean disableMethodInvocation,
@RequestParam(value = "validate-units", defaultValue = "true") Boolean validateUnits,
@RequestParam(value = "result-types", defaultValue = "true") Boolean resultTypes,
@RequestParam(value = "checkContext", defaultValue = "false") Boolean checkContext,
@RequestHeader("Authorization") String accessToken) {

RequestData requestData =
Expand All @@ -66,7 +67,7 @@ public CqlConversionPayload cqlToElmJson(
cqlLibraryService.setUpLibrarySourceProvider(cqlData, accessToken);

CqlConversionPayload cqlConversionPayload =
cqlConversionService.processCqlDataWithErrors(requestData);
cqlConversionService.processCqlDataWithErrors(requestData, checkContext);
// Todo Do we need to remove empty annotations from library object, Also why are we removing
// translatorOptions from annotations, Could be MAT specific.
TranslatorOptionsRemover remover = new TranslatorOptionsRemover(cqlConversionPayload.getJson());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package gov.cms.mat.cql_elm_translation.exceptions;

import org.cqframework.cql.cql2elm.CqlCompilerException;
import org.cqframework.cql.elm.tracking.TrackBack;
import org.hl7.elm.r1.VersionedIdentifier;

public class MissingContextException extends CqlCompilerException {
private static final long serialVersionUID = -8499197087358679644L;
private static final String MESSAGE = "Measure CQL must contain a Context.";

public MissingContextException(VersionedIdentifier identifier, int lineNumber) {
super(
MESSAGE,
CqlCompilerException.ErrorSeverity.Error,
new TrackBack(identifier, lineNumber, 0, lineNumber, 0));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@

import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.JsonPath;

import gov.cms.madie.models.dto.TranslatedLibrary;
import gov.cms.mat.cql.dto.CqlConversionPayload;
import gov.cms.madie.cql_elm_translator.utils.MadieCqlValidator;
import gov.cms.madie.cql_elm_translator.utils.cql.data.RequestData;
import gov.cms.madie.cql_elm_translator.utils.cql.data.SimpleIncludeDef;
import gov.cms.madie.cql_elm_translator.exceptions.InternalServerException;
import gov.cms.mat.cql_elm_translation.exceptions.MissingContextException;
import gov.cms.mat.cql_elm_translation.exceptions.MissingLibraryCqlCompilerException;
import gov.cms.mat.cql_elm_translation.service.filters.AnnotationErrorFilter;
import gov.cms.mat.cql_elm_translation.service.filters.CqlTranslatorExceptionFilter;
Expand All @@ -17,18 +19,30 @@
import lombok.extern.slf4j.Slf4j;
import net.minidev.json.JSONArray;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.cqframework.cql.cql2elm.CqlCompilerException;
import org.cqframework.cql.cql2elm.CqlTranslator;
import org.cqframework.cql.cql2elm.LibraryContentType;
import org.cqframework.cql.cql2elm.model.CompiledLibrary;
import org.cqframework.cql.elm.serializing.ElmLibraryWriterFactory;
import org.cqframework.cql.elm.tracking.TrackBack;
import org.hl7.elm.r1.CodeDef;
import org.hl7.elm.r1.CodeSystemDef;
import org.hl7.elm.r1.Element;
import org.hl7.elm.r1.ExpressionDef;
import org.hl7.elm.r1.Library;
import org.hl7.elm.r1.ParameterDef;
import org.hl7.elm.r1.UsingDef;
import org.hl7.elm.r1.ValueSetDef;
import org.hl7.elm.r1.VersionedIdentifier;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
Expand All @@ -41,7 +55,8 @@ public class CqlConversionService extends CqlTooling {

private static final String LOG_MESSAGE_TEMPLATE = "ErrorSeverity: %s, Message: %s";

public CqlConversionPayload processCqlDataWithErrors(RequestData requestData) {
public CqlConversionPayload processCqlDataWithErrors(
RequestData requestData, boolean checkContext) {
// verify the presence of ^using .*version '[0-9]\.[0-9]\.[0-9]'$ on the cql
Pattern pattern = Pattern.compile("using .*version '[0-9]\\.[0-9](\\.[0-9])?'");
Matcher matcher = pattern.matcher(requestData.getCqlData());
Expand All @@ -58,6 +73,10 @@ public CqlConversionPayload processCqlDataWithErrors(RequestData requestData) {
// measure CQL
processForLibraryRulesExceptions(cqlTranslator, requestData.getCqlData());

if (checkContext) {
processNoContextError(cqlTranslator, requestData.getCqlData());
}

List<CqlCompilerException> cqlTranslatorExceptions =
processErrors(
requestData.getCqlData(), requestData.isShowWarnings(), cqlTranslator.getExceptions());
Expand Down Expand Up @@ -180,4 +199,107 @@ private String formatMessage(CqlCompilerException e) {
e.getSeverity() != null ? e.getSeverity().name() : null,
e.getMessage());
}

public void processNoContextError(CqlTranslator cqlTranslator, String cql) {
if (StringUtils.isNotBlank(cql)
&& cqlTranslator.getTranslatedLibrary().getLibrary().getContexts() == null) {
VersionedIdentifier identifier =
cqlTranslator.getTranslatedLibrary().getLibrary().getIdentifier();

List<Integer> allLines = new ArrayList<>();
int startLineForContext = 0;
Library.Statements statements =
cqlTranslator.getTranslatedLibrary().getLibrary().getStatements();
if (statements != null) {
List<ExpressionDef> defs = statements.getDef();
allLines = getLines(defs, "start");
Collections.sort(allLines);
startLineForContext = allLines.size() > 0 ? allLines.get(0) - 1 : 0;
} else {
allLines.addAll(getUsingEndLines(cqlTranslator));
allLines.addAll(getParameterEndLines(cqlTranslator));
allLines.addAll(getValueSetsEndLines(cqlTranslator));
allLines.addAll(getCodeSystemEndLines(cqlTranslator));
allLines.addAll(getCodeEndLines(cqlTranslator));
allLines.sort(Comparator.reverseOrder());
startLineForContext = allLines.size() > 0 ? allLines.get(0) + 1 : 0;
}
log.debug("Missing context at line: " + startLineForContext);

cqlTranslator
.getExceptions()
.add(new MissingContextException(identifier, startLineForContext));
}
}

protected List<Integer> getLines(List<? extends Element> defs, String type) {
List<Integer> lines = new ArrayList<>();
if (CollectionUtils.isNotEmpty(defs)) {
for (Element def : defs) {
List<TrackBack> trackBacks = def.getTrackbacks();
if (CollectionUtils.isNotEmpty(trackBacks)) {
for (TrackBack trackBack : trackBacks) {
if ("start".equalsIgnoreCase(type)) {
lines.add(trackBack.getStartLine());
} else {
lines.add(trackBack.getEndLine());
}
}
}
}
}
return lines;
}

protected List<Integer> getUsingEndLines(CqlTranslator cqlTranslator) {
List<Integer> endLines = new ArrayList<>();
Library.Usings usings = cqlTranslator.getTranslatedLibrary().getLibrary().getUsings();
if (usings != null) {
List<UsingDef> defs = usings.getDef();
endLines = getLines(defs, "end");
}
return endLines;
}

protected List<Integer> getParameterEndLines(CqlTranslator cqlTranslator) {
List<Integer> endLines = new ArrayList<>();
Library.Parameters parameters =
cqlTranslator.getTranslatedLibrary().getLibrary().getParameters();
if (parameters != null) {
List<ParameterDef> defs = parameters.getDef();
endLines = getLines(defs, "end");
}
return endLines;
}

protected List<Integer> getValueSetsEndLines(CqlTranslator cqlTranslator) {
List<Integer> endLines = new ArrayList<>();
Library.ValueSets valuesets = cqlTranslator.getTranslatedLibrary().getLibrary().getValueSets();
if (valuesets != null) {
List<ValueSetDef> defs = valuesets.getDef();
endLines = getLines(defs, "end");
}
return endLines;
}

protected List<Integer> getCodeSystemEndLines(CqlTranslator cqlTranslator) {
List<Integer> endLines = new ArrayList<>();
Library.CodeSystems codeSystems =
cqlTranslator.getTranslatedLibrary().getLibrary().getCodeSystems();
if (codeSystems != null) {
List<CodeSystemDef> defs = codeSystems.getDef();
endLines = getLines(defs, "end");
}
return endLines;
}

protected List<Integer> getCodeEndLines(CqlTranslator cqlTranslator) {
List<Integer> endLines = new ArrayList<>();
Library.Codes codes = cqlTranslator.getTranslatedLibrary().getLibrary().getCodes();
if (codes != null) {
List<CodeDef> defs = codes.getDef();
endLines = getLines(defs, "end");
}
return endLines;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;

import java.io.UncheckedIOException;

Expand Down Expand Up @@ -40,15 +41,16 @@ void cqlToElmJson() {
String cqlData = getData("/cv_populations.cql");
String result = getData("/cv_populations.json");
CqlConversionPayload payload = CqlConversionPayload.builder().json(result).build();
Mockito.when(cqlConversionService.processCqlDataWithErrors(any(RequestData.class)))
Mockito.when(
cqlConversionService.processCqlDataWithErrors(any(RequestData.class), anyBoolean()))
.thenReturn(payload);

CqlConversionPayload cqlConversionPayload =
cqlConversionController.cqlToElmJson(
cqlData, null, true, true, true, true, true, true, true, true, "test");
cqlData, null, true, true, true, true, true, true, true, true, true, "test");

assertEquals(result, cqlConversionPayload.getJson());
Mockito.verify(cqlConversionService).processCqlDataWithErrors(any());
Mockito.verify(cqlConversionService).processCqlDataWithErrors(any(), anyBoolean());
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ void testProcessCqlDataWithErrors() throws JsonProcessingException {
cqlData = getData("/cv_populations.cql");
RequestData requestData = buildRequestData();
CqlConversionPayload cqlConversionPayload =
cqlConversionService.processCqlDataWithErrors(requestData);
cqlConversionService.processCqlDataWithErrors(requestData, true);
String elmJson = cqlConversionPayload.getJson();
ObjectMapper objectMapper = new ObjectMapper();
JsonNode rootNode = objectMapper.readTree(elmJson);
Expand Down
Loading

0 comments on commit 63a2eab

Please sign in to comment.