Updates to GraphQL

This commit is contained in:
jamesagnew 2022-01-30 17:04:10 -05:00
parent f69a30421f
commit 846866472b
8 changed files with 379 additions and 273 deletions

View File

@ -328,7 +328,7 @@ public class GraphQLEngine implements IGraphQLEngine {
return obj.primitiveValue() != ""; return obj.primitiveValue() != "";
} }
private List<Base> filter(Resource context, Property prop, List<Argument> arguments, List<Base> values, boolean extensionMode) throws FHIRException, EGraphQLException { private List<Base> filter(Resource context, Property prop, String fieldName, List<Argument> arguments, List<Base> values, boolean extensionMode) throws FHIRException, EGraphQLException {
List<Base> result = new ArrayList<Base>(); List<Base> result = new ArrayList<Base>();
if (values.size() > 0) { if (values.size() > 0) {
int count = Integer.MAX_VALUE; int count = Integer.MAX_VALUE;
@ -353,10 +353,27 @@ public class GraphQLEngine implements IGraphQLEngine {
fp.append(" and "+arg.getName()+" = '"+vl.get(0).toString()+"'"); fp.append(" and "+arg.getName()+" = '"+vl.get(0).toString()+"'");
} }
} }
// Account for situations where the GraphQL expression selected e.g.
// effectiveDateTime but the field contains effectivePeriod
String propName = prop.getName();
List<Base> newValues = new ArrayList<>(values.size());
for (Base value : values) {
if (propName.endsWith("[x]")) {
String propNameShortened = propName.substring(0, propName.length() - 3);
if (fieldName.startsWith(propNameShortened) && fieldName.length() > propNameShortened.length()) {
if (!value.fhirType().equalsIgnoreCase(fieldName.substring(propNameShortened.length()))) {
continue;
}
}
}
newValues.add(value);
}
int i = 0; int i = 0;
int t = 0; int t = 0;
if (fp.length() == 0) if (fp.length() == 0)
for (Base v : values) { for (Base v : newValues) {
if ((i >= offset) && passesExtensionMode(v, extensionMode)) { if ((i >= offset) && passesExtensionMode(v, extensionMode)) {
result.add(v); result.add(v);
t++; t++;
@ -365,8 +382,8 @@ public class GraphQLEngine implements IGraphQLEngine {
} }
i++; i++;
} else { } else {
ExpressionNode node = fpe.parse(fp.toString().substring(5)); ExpressionNode node = fpe.parse(fp.substring(5));
for (Base v : values) { for (Base v : newValues) {
if ((i >= offset) && passesExtensionMode(v, extensionMode) && fpe.evaluateToBoolean(null, context, v, node)) { if ((i >= offset) && passesExtensionMode(v, extensionMode) && fpe.evaluateToBoolean(null, context, v, node)) {
result.add(v); result.add(v);
t++; t++;
@ -526,7 +543,7 @@ public class GraphQLEngine implements IGraphQLEngine {
if (!isPrimitive(prop.getTypeCode()) && sel.getField().getName().startsWith("_")) if (!isPrimitive(prop.getTypeCode()) && sel.getField().getName().startsWith("_"))
throw new EGraphQLException("Unknown property "+sel.getField().getName()+" on "+source.fhirType()); throw new EGraphQLException("Unknown property "+sel.getField().getName()+" on "+source.fhirType());
List<Base> vl = filter(context, prop, sel.getField().getArguments(), prop.getValues(), sel.getField().getName().startsWith("_")); List<Base> vl = filter(context, prop, sel.getField().getName(), sel.getField().getArguments(), prop.getValues(), sel.getField().getName().startsWith("_"));
if (!vl.isEmpty()) if (!vl.isEmpty())
processValues(context, sel, prop, target, vl, sel.getField().getName().startsWith("_"), inheritedList, suffix); processValues(context, sel, prop, target, vl, sel.getField().getName().startsWith("_"), inheritedList, suffix);
} }

View File

@ -190,7 +190,7 @@ public class GraphQLEngine implements IGraphQLEngine {
return obj.primitiveValue() != ""; return obj.primitiveValue() != "";
} }
private List<Base> filter(Resource context, Property prop, List<Argument> arguments, List<Base> values, boolean extensionMode) throws FHIRException, EGraphQLException { private List<Base> filter(Resource context, Property prop, String fieldName, List<Argument> arguments, List<Base> values, boolean extensionMode) throws FHIRException, EGraphQLException {
List<Base> result = new ArrayList<Base>(); List<Base> result = new ArrayList<Base>();
if (values.size() > 0) { if (values.size() > 0) {
int count = Integer.MAX_VALUE; int count = Integer.MAX_VALUE;
@ -215,10 +215,28 @@ public class GraphQLEngine implements IGraphQLEngine {
fp.append(" and " + arg.getName() + " = '" + vl.get(0).toString() + "'"); fp.append(" and " + arg.getName() + " = '" + vl.get(0).toString() + "'");
} }
} }
// Account for situations where the GraphQL expression selected e.g.
// effectiveDateTime but the field contains effectivePeriod
String propName = prop.getName();
List<Base> newValues = new ArrayList<>(values.size());
for (Base value : values) {
if (propName.endsWith("[x]")) {
String propNameShortened = propName.substring(0, propName.length() - 3);
if (fieldName.startsWith(propNameShortened) && fieldName.length() > propNameShortened.length()) {
if (!value.fhirType().equalsIgnoreCase(fieldName.substring(propNameShortened.length()))) {
continue;
}
}
}
newValues.add(value);
}
int i = 0; int i = 0;
int t = 0; int t = 0;
if (fp.length() == 0) if (fp.length() == 0)
for (Base v : values) { for (Base v : newValues) {
if ((i >= offset) && passesExtensionMode(v, extensionMode)) { if ((i >= offset) && passesExtensionMode(v, extensionMode)) {
result.add(v); result.add(v);
t++; t++;
@ -228,8 +246,8 @@ public class GraphQLEngine implements IGraphQLEngine {
i++; i++;
} }
else { else {
ExpressionNode node = fpe.parse(fp.toString().substring(5)); ExpressionNode node = fpe.parse(fp.substring(5));
for (Base v : values) { for (Base v : newValues) {
if ((i >= offset) && passesExtensionMode(v, extensionMode) && fpe.evaluateToBoolean(null, context, v, node)) { if ((i >= offset) && passesExtensionMode(v, extensionMode) && fpe.evaluateToBoolean(null, context, v, node)) {
result.add(v); result.add(v);
t++; t++;
@ -389,7 +407,7 @@ public class GraphQLEngine implements IGraphQLEngine {
if (!isPrimitive(prop.getTypeCode()) && sel.getField().getName().startsWith("_")) if (!isPrimitive(prop.getTypeCode()) && sel.getField().getName().startsWith("_"))
throw new EGraphQLException("Unknown property " + sel.getField().getName() + " on " + source.fhirType()); throw new EGraphQLException("Unknown property " + sel.getField().getName() + " on " + source.fhirType());
List<Base> vl = filter(context, prop, sel.getField().getArguments(), prop.getValues(), sel.getField().getName().startsWith("_")); List<Base> vl = filter(context, prop, sel.getField().getName(), sel.getField().getArguments(), prop.getValues(), sel.getField().getName().startsWith("_"));
if (!vl.isEmpty()) if (!vl.isEmpty())
processValues(context, sel, prop, target, vl, sel.getField().getName().startsWith("_"), inheritedList, suffix); processValues(context, sel, prop, target, vl, sel.getField().getName().startsWith("_"), inheritedList, suffix);
} }

View File

@ -3,6 +3,7 @@
<test name="filter-fhirpath" source="filter-fhirpath.gql" output="filter-fhirpath.json" context="Patient/example/$graphql"/> <test name="filter-fhirpath" source="filter-fhirpath.gql" output="filter-fhirpath.json" context="Patient/example/$graphql"/>
<test name="using wrong field" source="wrong-field.gql" output="$error" context="Patient/example/$graphql"/> <test name="using wrong field" source="wrong-field.gql" output="$error" context="Patient/example/$graphql"/>
<test name="polymorphic" source="polymorphic.gql" output="polymorphic.json" context="Observation/example/$graphql"/> <test name="polymorphic" source="polymorphic.gql" output="polymorphic.json" context="Observation/example/$graphql"/>
<test name="polymorphic-difftype" source="polymorphic-difftype.gql" output="polymorphic-difftype.json" context="Observation/example/$graphql"/>
<test name="reference" source="reference.gql" output="reference.json" context="Observation/example/$graphql"/> <test name="reference" source="reference.gql" output="reference.json" context="Observation/example/$graphql"/>
<test name="reference-type-in" source="reference-type-in.gql" output="reference-type-in.json" context="Observation/example/$graphql"/> <test name="reference-type-in" source="reference-type-in.gql" output="reference-type-in.json" context="Observation/example/$graphql"/>
<test name="reference-type-out" source="reference-type-out.gql" output="reference-type-out.json" context="Observation/example/$graphql"/> <test name="reference-type-out" source="reference-type-out.gql" output="reference-type-out.json" context="Observation/example/$graphql"/>

View File

@ -0,0 +1,5 @@
{
subject{reference}
valueQuantity {value unit}
}

View File

@ -0,0 +1,6 @@
{
"subject" : {
"reference" : "Patient/example"
},
"valueString": "FOO"
}

View File

@ -356,7 +356,7 @@ public class GraphQLEngine implements IGraphQLEngine {
return obj.primitiveValue() != ""; return obj.primitiveValue() != "";
} }
private List<Base> filter(Resource context, Property prop, List<Argument> arguments, List<Base> values, boolean extensionMode) throws FHIRException, EGraphQLException { private List<Base> filter(Resource context, Property prop, String fieldName, List<Argument> arguments, List<Base> values, boolean extensionMode) throws FHIRException, EGraphQLException {
List<Base> result = new ArrayList<Base>(); List<Base> result = new ArrayList<Base>();
if (values.size() > 0) { if (values.size() > 0) {
int count = Integer.MAX_VALUE; int count = Integer.MAX_VALUE;
@ -381,10 +381,28 @@ public class GraphQLEngine implements IGraphQLEngine {
fp.append(" and "+arg.getName()+" = '"+vl.get(0).toString()+"'"); fp.append(" and "+arg.getName()+" = '"+vl.get(0).toString()+"'");
} }
} }
// Account for situations where the GraphQL expression selected e.g.
// effectiveDateTime but the field contains effectivePeriod
String propName = prop.getName();
List<Base> newValues = new ArrayList<>(values.size());
for (Base value : values) {
if (propName.endsWith("[x]")) {
String propNameShortened = propName.substring(0, propName.length() - 3);
if (fieldName.startsWith(propNameShortened) && fieldName.length() > propNameShortened.length()) {
if (!value.fhirType().equalsIgnoreCase(fieldName.substring(propNameShortened.length()))) {
continue;
}
}
}
newValues.add(value);
}
int i = 0; int i = 0;
int t = 0; int t = 0;
if (fp.length() == 0) if (fp.length() == 0)
for (Base v : values) { for (Base v : newValues) {
if ((i >= offset) && passesExtensionMode(v, extensionMode)) { if ((i >= offset) && passesExtensionMode(v, extensionMode)) {
result.add(v); result.add(v);
t++; t++;
@ -393,8 +411,8 @@ public class GraphQLEngine implements IGraphQLEngine {
} }
i++; i++;
} else { } else {
ExpressionNode node = fpe.parse(fp.toString().substring(5)); ExpressionNode node = fpe.parse(fp.substring(5));
for (Base v : values) { for (Base v : newValues) {
if ((i >= offset) && passesExtensionMode(v, extensionMode) && fpe.evaluateToBoolean(null, context, v, node)) { if ((i >= offset) && passesExtensionMode(v, extensionMode) && fpe.evaluateToBoolean(null, context, v, node)) {
result.add(v); result.add(v);
t++; t++;
@ -554,7 +572,7 @@ public class GraphQLEngine implements IGraphQLEngine {
if (!isPrimitive(prop.getTypeCode()) && sel.getField().getName().startsWith("_")) if (!isPrimitive(prop.getTypeCode()) && sel.getField().getName().startsWith("_"))
throw new EGraphQLException("Unknown property "+sel.getField().getName()+" on "+source.fhirType()); throw new EGraphQLException("Unknown property "+sel.getField().getName()+" on "+source.fhirType());
List<Base> vl = filter(context, prop, sel.getField().getArguments(), prop.getValues(), sel.getField().getName().startsWith("_")); List<Base> vl = filter(context, prop, sel.getField().getName(), sel.getField().getArguments(), prop.getValues(), sel.getField().getName().startsWith("_"));
if (!vl.isEmpty()) if (!vl.isEmpty())
processValues(context, sel, prop, target, vl, sel.getField().getName().startsWith("_"), inheritedList, suffix); processValues(context, sel, prop, target, vl, sel.getField().getName().startsWith("_"), inheritedList, suffix);
} }

View File

@ -356,7 +356,7 @@ public class GraphQLEngine implements IGraphQLEngine {
return obj.primitiveValue() != ""; return obj.primitiveValue() != "";
} }
private List<Base> filter(Resource context, Property prop, List<Argument> arguments, List<Base> values, boolean extensionMode) throws FHIRException, EGraphQLException { private List<Base> filter(Resource context, Property prop, String fieldName, List<Argument> arguments, List<Base> values, boolean extensionMode) throws FHIRException, EGraphQLException {
List<Base> result = new ArrayList<Base>(); List<Base> result = new ArrayList<Base>();
if (values.size() > 0) { if (values.size() > 0) {
int count = Integer.MAX_VALUE; int count = Integer.MAX_VALUE;
@ -381,10 +381,27 @@ public class GraphQLEngine implements IGraphQLEngine {
fp.append(" and "+arg.getName()+" = '"+vl.get(0).toString()+"'"); fp.append(" and "+arg.getName()+" = '"+vl.get(0).toString()+"'");
} }
} }
// Account for situations where the GraphQL expression selected e.g.
// effectiveDateTime but the field contains effectivePeriod
String propName = prop.getName();
List<Base> newValues = new ArrayList<>(values.size());
for (Base value : values) {
if (propName.endsWith("[x]")) {
String propNameShortened = propName.substring(0, propName.length() - 3);
if (fieldName.startsWith(propNameShortened) && fieldName.length() > propNameShortened.length()) {
if (!value.fhirType().equalsIgnoreCase(fieldName.substring(propNameShortened.length()))) {
continue;
}
}
}
newValues.add(value);
}
int i = 0; int i = 0;
int t = 0; int t = 0;
if (fp.length() == 0) if (fp.length() == 0)
for (Base v : values) { for (Base v : newValues) {
if ((i >= offset) && passesExtensionMode(v, extensionMode)) { if ((i >= offset) && passesExtensionMode(v, extensionMode)) {
result.add(v); result.add(v);
t++; t++;
@ -393,8 +410,8 @@ public class GraphQLEngine implements IGraphQLEngine {
} }
i++; i++;
} else { } else {
ExpressionNode node = fpe.parse(fp.toString().substring(5)); ExpressionNode node = fpe.parse(fp.substring(5));
for (Base v : values) { for (Base v : newValues) {
if ((i >= offset) && passesExtensionMode(v, extensionMode) && fpe.evaluateToBoolean(null, context, v, node)) { if ((i >= offset) && passesExtensionMode(v, extensionMode) && fpe.evaluateToBoolean(null, context, v, node)) {
result.add(v); result.add(v);
t++; t++;
@ -554,7 +571,7 @@ public class GraphQLEngine implements IGraphQLEngine {
if (!isPrimitive(prop.getTypeCode()) && sel.getField().getName().startsWith("_")) if (!isPrimitive(prop.getTypeCode()) && sel.getField().getName().startsWith("_"))
throw new EGraphQLException("Unknown property "+sel.getField().getName()+" on "+source.fhirType()); throw new EGraphQLException("Unknown property "+sel.getField().getName()+" on "+source.fhirType());
List<Base> vl = filter(context, prop, sel.getField().getArguments(), prop.getValues(), sel.getField().getName().startsWith("_")); List<Base> vl = filter(context, prop, sel.getField().getName(), sel.getField().getArguments(), prop.getValues(), sel.getField().getName().startsWith("_"));
if (!vl.isEmpty()) if (!vl.isEmpty())
processValues(context, sel, prop, target, vl, sel.getField().getName().startsWith("_"), inheritedList, suffix); processValues(context, sel, prop, target, vl, sel.getField().getName().startsWith("_"), inheritedList, suffix);
} }

View File

@ -30,24 +30,10 @@ package org.hl7.fhir.r5.utils;
*/ */
// todo: // todo:
// - generate sort order parameters // - generate sort order parameters
// - generate inherited search parameters // - generate inherited search parameters
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r5.conformance.ProfileUtilities; import org.hl7.fhir.r5.conformance.ProfileUtilities;
import org.hl7.fhir.r5.context.IWorkerContext; import org.hl7.fhir.r5.context.IWorkerContext;
@ -60,21 +46,32 @@ import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind;
import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule; import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule;
import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.Utilities;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static java.util.Objects.requireNonNull;
public class GraphQLSchemaGenerator { public class GraphQLSchemaGenerator {
public enum FHIROperationType {READ, SEARCH, CREATE, UPDATE, DELETE};
private static final String INNER_TYPE_NAME = "gql.type.name";
private static final Set<String> JSON_NUMBER_TYPES = new HashSet<String>() {{ private static final Set<String> JSON_NUMBER_TYPES = new HashSet<String>() {{
add("decimal"); add("decimal");
add("positiveInt"); add("positiveInt");
add("unsignedInt"); add("unsignedInt");
}}; }};
private final ProfileUtilities profileUtilities;
private final String version;
IWorkerContext context; IWorkerContext context;
private ProfileUtilities profileUtilities;
private String version;
public GraphQLSchemaGenerator(IWorkerContext context, String version) { public GraphQLSchemaGenerator(IWorkerContext context, String version) {
super(); super();
this.context = context; this.context = context;
@ -84,9 +81,15 @@ public class GraphQLSchemaGenerator {
public void generateTypes(OutputStream stream) throws IOException, FHIRException { public void generateTypes(OutputStream stream) throws IOException, FHIRException {
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(stream)); BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(stream));
generateTypes(writer);
writer.flush();
writer.close();
}
Map<String, StructureDefinition> pl = new HashMap<String, StructureDefinition>(); public void generateTypes(Writer writer) throws IOException {
Map<String, StructureDefinition> tl = new HashMap<String, StructureDefinition>(); Map<String, StructureDefinition> pl = new HashMap<>();
Map<String, StructureDefinition> tl = new HashMap<>();
Map<String, String> existingTypeNames = new HashMap<>();
for (StructureDefinition sd : context.allStructures()) { for (StructureDefinition sd : context.allStructures()) {
if (sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION) { if (sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION) {
pl.put(sd.getName(), sd); pl.put(sd.getName(), sd);
@ -102,22 +105,32 @@ public class GraphQLSchemaGenerator {
writer.write("\r\n"); writer.write("\r\n");
writer.write("# FHIR Defined Search Parameter Types\r\n"); writer.write("# FHIR Defined Search Parameter Types\r\n");
for (SearchParamType dir : SearchParamType.values()) { for (SearchParamType dir : SearchParamType.values()) {
if (pl.containsKey(dir.toCode())) {
// Don't double create String and URI
continue;
}
if (dir != SearchParamType.NULL) if (dir != SearchParamType.NULL)
generateSearchParamType(writer, dir.toCode()); generateSearchParamType(writer, dir.toCode());
} }
writer.write("\r\n"); writer.write("\r\n");
generateElementBase(writer); generateElementBase(writer);
for (String n : sorted(tl.keySet())) for (String n : sorted(tl.keySet()))
generateType(writer, tl.get(n)); generateType(existingTypeNames, writer, tl.get(n));
writer.flush();
writer.close();
} }
public void generateResource(OutputStream stream, StructureDefinition sd, List<SearchParameter> parameters, EnumSet<FHIROperationType> operations) throws IOException, FHIRException { public void generateResource(OutputStream stream, StructureDefinition sd, List<SearchParameter> parameters, EnumSet<FHIROperationType> operations) throws IOException, FHIRException {
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(stream)); BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(stream));
generateResource(writer, sd, parameters, operations);
writer.flush();
writer.close();
}
public void generateResource(Writer writer, StructureDefinition sd, List<SearchParameter> parameters, EnumSet<FHIROperationType> operations) throws IOException {
Map<String, String> existingTypeNames = new HashMap<>();
writer.write("# FHIR GraphQL Schema. Version " + version + "\r\n\r\n"); writer.write("# FHIR GraphQL Schema. Version " + version + "\r\n\r\n");
writer.write("# import * from 'types.graphql'\r\n\r\n"); writer.write("# import * from 'types.graphql'\r\n\r\n");
generateType(writer, sd); generateType(existingTypeNames, writer, sd);
if (operations.contains(FHIROperationType.READ)) if (operations.contains(FHIROperationType.READ))
generateIdAccess(writer, sd.getName()); generateIdAccess(writer, sd.getName());
if (operations.contains(FHIROperationType.SEARCH)) { if (operations.contains(FHIROperationType.SEARCH)) {
@ -130,11 +143,9 @@ public class GraphQLSchemaGenerator {
generateUpdate(writer, sd.getName()); generateUpdate(writer, sd.getName());
if (operations.contains(FHIROperationType.DELETE)) if (operations.contains(FHIROperationType.DELETE))
generateDelete(writer, sd.getName()); generateDelete(writer, sd.getName());
writer.flush();
writer.close();
} }
private void generateCreate(BufferedWriter writer, String name) throws IOException { private void generateCreate(Writer writer, String name) throws IOException {
writer.write("type " + name + "CreateType {\r\n"); writer.write("type " + name + "CreateType {\r\n");
writer.write(" " + name + "Create("); writer.write(" " + name + "Create(");
param(writer, "resource", name + "Input", false, false); param(writer, "resource", name + "Input", false, false);
@ -149,7 +160,7 @@ public class GraphQLSchemaGenerator {
writer.write("\r\n"); writer.write("\r\n");
} }
private void generateUpdate(BufferedWriter writer, String name) throws IOException { private void generateUpdate(Writer writer, String name) throws IOException {
writer.write("type " + name + "UpdateType {\r\n"); writer.write("type " + name + "UpdateType {\r\n");
writer.write(" " + name + "Update("); writer.write(" " + name + "Update(");
param(writer, "id", "ID", false, false); param(writer, "id", "ID", false, false);
@ -165,7 +176,7 @@ public class GraphQLSchemaGenerator {
writer.write("\r\n"); writer.write("\r\n");
} }
private void generateDelete(BufferedWriter writer, String name) throws IOException { private void generateDelete(Writer writer, String name) throws IOException {
writer.write("type " + name + "DeleteType {\r\n"); writer.write("type " + name + "DeleteType {\r\n");
writer.write(" " + name + "Delete("); writer.write(" " + name + "Delete(");
param(writer, "id", "ID", false, false); param(writer, "id", "ID", false, false);
@ -178,21 +189,26 @@ public class GraphQLSchemaGenerator {
writer.write("\r\n"); writer.write("\r\n");
} }
private void generateListAccess(BufferedWriter writer, List<SearchParameter> parameters, String name) throws IOException { private void generateListAccess(Writer writer, List<SearchParameter> parameters, String name) throws IOException {
writer.write("type " + name + "ListType {\r\n"); writer.write("type " + name + "ListType {\r\n");
writer.write(" "+name+"List("); writer.write(" ");
param(writer, "_filter", "String", false, false); generateListAccessQuery(writer, parameters, name);
for (SearchParameter sp : parameters)
param(writer, sp.getName().replace("-", "_"), getGqlname(sp.getType().toCode()), true, true);
param(writer, "_sort", "String", false, true);
param(writer, "_count", "Int", false, true);
param(writer, "_cursor", "String", false, true);
writer.write("): ["+name+"]\r\n");
writer.write("}\r\n"); writer.write("}\r\n");
writer.write("\r\n"); writer.write("\r\n");
} }
private void param(BufferedWriter writer, String name, String type, boolean list, boolean line) throws IOException { public void generateListAccessQuery(Writer writer, List<SearchParameter> parameters, String name) throws IOException {
writer.write(name + "List(");
param(writer, "_filter", "String", false, false);
for (SearchParameter sp : parameters)
param(writer, sp.getName().replace("-", "_"), getGqlname(requireNonNull(sp.getType().toCode())), true, true);
param(writer, "_sort", "String", false, true);
param(writer, "_count", "Int", false, true);
param(writer, "_cursor", "String", false, true);
writer.write("): [" + name + "]\r\n");
}
private void param(Writer writer, String name, String type, boolean list, boolean line) throws IOException {
if (line) if (line)
writer.write("\r\n "); writer.write("\r\n ");
writer.write(name); writer.write(name);
@ -204,16 +220,10 @@ public class GraphQLSchemaGenerator {
writer.write("]"); writer.write("]");
} }
private void generateConnectionAccess(BufferedWriter writer, List<SearchParameter> parameters, String name) throws IOException { private void generateConnectionAccess(Writer writer, List<SearchParameter> parameters, String name) throws IOException {
writer.write("type " + name + "ConnectionType {\r\n"); writer.write("type " + name + "ConnectionType {\r\n");
writer.write(" "+name+"Conection("); writer.write(" ");
param(writer, "_filter", "String", false, false); generateConnectionAccessQuery(writer, parameters, name);
for (SearchParameter sp : parameters)
param(writer, sp.getName().replace("-", "_"), getGqlname(sp.getType().toCode()), true, true);
param(writer, "_sort", "String", false, true);
param(writer, "_count", "Int", false, true);
param(writer, "_cursor", "String", false, true);
writer.write("): "+name+"Connection\r\n");
writer.write("}\r\n"); writer.write("}\r\n");
writer.write("\r\n"); writer.write("\r\n");
writer.write("type " + name + "Connection {\r\n"); writer.write("type " + name + "Connection {\r\n");
@ -235,15 +245,25 @@ public class GraphQLSchemaGenerator {
writer.write("\r\n"); writer.write("\r\n");
} }
public void generateConnectionAccessQuery(Writer writer, List<SearchParameter> parameters, String name) throws IOException {
writer.write(name + "Conection(");
param(writer, "_filter", "String", false, false);
for (SearchParameter sp : parameters)
param(writer, sp.getName().replace("-", "_"), getGqlname(requireNonNull(sp.getType().toCode())), true, true);
param(writer, "_sort", "String", false, true);
param(writer, "_count", "Int", false, true);
param(writer, "_cursor", "String", false, true);
writer.write("): " + name + "Connection\r\n");
}
private void generateIdAccess(BufferedWriter writer, String name) throws IOException { private void generateIdAccess(Writer writer, String name) throws IOException {
writer.write("type " + name + "ReadType {\r\n"); writer.write("type " + name + "ReadType {\r\n");
writer.write(" " + name + "(id: ID!): " + name + "\r\n"); writer.write(" " + name + "(id: ID!): " + name + "\r\n");
writer.write("}\r\n"); writer.write("}\r\n");
writer.write("\r\n"); writer.write("\r\n");
} }
private void generateElementBase(BufferedWriter writer) throws IOException { private void generateElementBase(Writer writer) throws IOException {
writer.write("type ElementBase {\r\n"); writer.write("type ElementBase {\r\n");
writer.write(" id: ID\r\n"); writer.write(" id: ID\r\n");
writer.write(" extension: [Extension]\r\n"); writer.write(" extension: [Extension]\r\n");
@ -257,18 +277,18 @@ public class GraphQLSchemaGenerator {
writer.write("\r\n"); writer.write("\r\n");
} }
private void generateType(BufferedWriter writer, StructureDefinition sd) throws IOException { private void generateType(Map<String, String> existingTypeNames, Writer writer, StructureDefinition sd) throws IOException {
if (sd.getAbstract()) if (sd.getAbstract())
return; return;
List<StringBuilder> list = new ArrayList<StringBuilder>(); List<StringBuilder> list = new ArrayList<>();
StringBuilder b = new StringBuilder(); StringBuilder b = new StringBuilder();
list.add(b); list.add(b);
b.append("type "); b.append("type ");
b.append(sd.getName()); b.append(sd.getName());
b.append(" {\r\n"); b.append(" {\r\n");
ElementDefinition ed = sd.getSnapshot().getElementFirstRep(); ElementDefinition ed = sd.getSnapshot().getElementFirstRep();
generateProperties(list, b, sd.getName(), sd, ed, "type", ""); generateProperties(existingTypeNames, list, b, sd.getName(), sd, ed, "type", "");
b.append("}"); b.append("}");
b.append("\r\n"); b.append("\r\n");
b.append("\r\n"); b.append("\r\n");
@ -281,7 +301,7 @@ public class GraphQLSchemaGenerator {
b.append(sd.getName()); b.append(sd.getName());
b.append("Input {\r\n"); b.append("Input {\r\n");
ed = sd.getSnapshot().getElementFirstRep(); ed = sd.getSnapshot().getElementFirstRep();
generateProperties(list, b, sd.getName(), sd, ed, "input", "Input"); generateProperties(existingTypeNames, list, b, sd.getName(), sd, ed, "input", "Input");
b.append("}"); b.append("}");
b.append("\r\n"); b.append("\r\n");
b.append("\r\n"); b.append("\r\n");
@ -289,22 +309,22 @@ public class GraphQLSchemaGenerator {
writer.write(bs.toString()); writer.write(bs.toString());
} }
private void generateProperties(List<StringBuilder> list, StringBuilder b, String typeName, StructureDefinition sd, ElementDefinition ed, String mode, String suffix) throws IOException { private void generateProperties(Map<String, String> existingTypeNames, List<StringBuilder> list, StringBuilder b, String typeName, StructureDefinition sd, ElementDefinition ed, String mode, String suffix) throws IOException {
List<ElementDefinition> children = profileUtilities.getChildList(sd, ed); List<ElementDefinition> children = profileUtilities.getChildList(sd, ed);
for (ElementDefinition child : children) { for (ElementDefinition child : children) {
if (child.hasContentReference()) { if (child.hasContentReference()) {
ElementDefinition ref = resolveContentReference(sd, child.getContentReference()); ElementDefinition ref = resolveContentReference(sd, child.getContentReference());
generateProperty(list, b, typeName, sd, child, ref.getType().get(0), false, ref, mode, suffix); generateProperty(existingTypeNames, list, b, typeName, sd, child, ref.getType().get(0), false, ref, mode, suffix);
} else if (child.getType().size() == 1) { } else if (child.getType().size() == 1) {
generateProperty(list, b, typeName, sd, child, child.getType().get(0), false, null, mode, suffix); generateProperty(existingTypeNames, list, b, typeName, sd, child, child.getType().get(0), false, null, mode, suffix);
} else { } else {
boolean ref = false; boolean ref = false;
for (TypeRefComponent t : child.getType()) { for (TypeRefComponent t : child.getType()) {
if (!t.hasTarget()) if (!t.hasTarget())
generateProperty(list, b, typeName, sd, child, t, true, null, mode, suffix); generateProperty(existingTypeNames, list, b, typeName, sd, child, t, true, null, mode, suffix);
else if (!ref) { else if (!ref) {
ref = true; ref = true;
generateProperty(list, b, typeName, sd, child, t, true, null, mode, suffix); generateProperty(existingTypeNames, list, b, typeName, sd, child, t, true, null, mode, suffix);
} }
} }
} }
@ -320,7 +340,7 @@ public class GraphQLSchemaGenerator {
throw new Error("Unable to find " + id); throw new Error("Unable to find " + id);
} }
private void generateProperty(List<StringBuilder> list, StringBuilder b, String typeName, StructureDefinition sd, ElementDefinition child, TypeRefComponent typeDetails, boolean suffix, ElementDefinition cr, String mode, String suffixS) throws IOException { private void generateProperty(Map<String, String> existingTypeNames, List<StringBuilder> list, StringBuilder b, String typeName, StructureDefinition sd, ElementDefinition child, TypeRefComponent typeDetails, boolean suffix, ElementDefinition cr, String mode, String suffixS) throws IOException {
if (isPrimitive(typeDetails)) { if (isPrimitive(typeDetails)) {
String n = getGqlname(typeDetails.getWorkingCode()); String n = getGqlname(typeDetails.getWorkingCode());
b.append(" "); b.append(" ");
@ -355,11 +375,11 @@ public class GraphQLSchemaGenerator {
b.append("["); b.append("[");
String type = typeDetails.getWorkingCode(); String type = typeDetails.getWorkingCode();
if (cr != null) if (cr != null)
b.append(generateInnerType(list, sd, typeName, cr, mode, suffixS)); b.append(generateInnerType(existingTypeNames, list, sd, typeName, cr, mode, suffixS));
else if (Utilities.existsInList(type, "Element", "BackboneElement")) else if (Utilities.existsInList(type, "Element", "BackboneElement"))
b.append(generateInnerType(list, sd, typeName, child, mode, suffixS)); b.append(generateInnerType(existingTypeNames, list, sd, typeName, child, mode, suffixS));
else else
b.append(type+suffixS); b.append(type).append(suffixS);
if (!child.getMax().equals("1")) if (!child.getMax().equals("1"))
b.append("]"); b.append("]");
if (child.getMin() != 0 && !suffix) if (child.getMin() != 0 && !suffix)
@ -368,12 +388,15 @@ public class GraphQLSchemaGenerator {
} }
} }
private String generateInnerType(List<StringBuilder> list, StructureDefinition sd, String name, ElementDefinition child, String mode, String suffix) throws IOException { private String generateInnerType(Map<String, String> existingTypeNames, List<StringBuilder> list, StructureDefinition sd, String name, ElementDefinition child, String mode, String suffix) throws IOException {
if (child.hasUserData(INNER_TYPE_NAME+"."+mode)) String key = child.getName() + "." + mode;
return child.getUserString(INNER_TYPE_NAME+"."+mode); if (existingTypeNames.containsKey(key)) {
return existingTypeNames.get(key);
}
String typeName = name + Utilities.capitalize(tail(child.getPath(), false)) + suffix;
existingTypeNames.put(key, typeName + suffix);
String typeName = name+Utilities.capitalize(tail(child.getPath(), false));
child.setUserData(INNER_TYPE_NAME+"."+mode, typeName);
StringBuilder b = new StringBuilder(); StringBuilder b = new StringBuilder();
list.add(b); list.add(b);
b.append(mode); b.append(mode);
@ -381,7 +404,7 @@ public class GraphQLSchemaGenerator {
b.append(typeName); b.append(typeName);
b.append(suffix); b.append(suffix);
b.append(" {\r\n"); b.append(" {\r\n");
generateProperties(list, b, typeName, sd, child, mode, suffix); generateProperties(existingTypeNames, list, b, typeName, sd, child, mode, suffix);
b.append("}"); b.append("}");
b.append("\r\n"); b.append("\r\n");
b.append("\r\n"); b.append("\r\n");
@ -404,13 +427,12 @@ public class GraphQLSchemaGenerator {
} }
private List<String> sorted(Set<String> keys) { private List<String> sorted(Set<String> keys) {
List<String> sl = new ArrayList<>(); List<String> sl = new ArrayList<>(keys);
sl.addAll(keys);
Collections.sort(sl); Collections.sort(sl);
return sl; return sl;
} }
private void generatePrimitive(BufferedWriter writer, StructureDefinition sd) throws IOException, FHIRException { private void generatePrimitive(Writer writer, StructureDefinition sd) throws IOException, FHIRException {
String gqlName = getGqlname(sd.getName()); String gqlName = getGqlname(sd.getName());
if (gqlName.equals(sd.getName())) { if (gqlName.equals(sd.getName())) {
writer.write("scalar "); writer.write("scalar ");
@ -426,7 +448,7 @@ public class GraphQLSchemaGenerator {
writer.write("\r\n"); writer.write("\r\n");
} }
private void generateSearchParamType(BufferedWriter writer, String name) throws IOException, FHIRException { private void generateSearchParamType(Writer writer, String name) throws IOException, FHIRException {
String gqlName = getGqlname(name); String gqlName = getGqlname(name);
if (gqlName.equals("date")) { if (gqlName.equals("date")) {
writer.write("# Search Param "); writer.write("# Search Param ");
@ -469,4 +491,6 @@ public class GraphQLSchemaGenerator {
return "ID"; return "ID";
return name; return name;
} }
public enum FHIROperationType {READ, SEARCH, CREATE, UPDATE, DELETE}
} }