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() != "";
}
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>();
if (values.size() > 0) {
int count = Integer.MAX_VALUE;
@ -353,10 +353,27 @@ public class GraphQLEngine implements IGraphQLEngine {
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 t = 0;
if (fp.length() == 0)
for (Base v : values) {
for (Base v : newValues) {
if ((i >= offset) && passesExtensionMode(v, extensionMode)) {
result.add(v);
t++;
@ -365,8 +382,8 @@ public class GraphQLEngine implements IGraphQLEngine {
}
i++;
} else {
ExpressionNode node = fpe.parse(fp.toString().substring(5));
for (Base v : values) {
ExpressionNode node = fpe.parse(fp.substring(5));
for (Base v : newValues) {
if ((i >= offset) && passesExtensionMode(v, extensionMode) && fpe.evaluateToBoolean(null, context, v, node)) {
result.add(v);
t++;
@ -526,7 +543,7 @@ public class GraphQLEngine implements IGraphQLEngine {
if (!isPrimitive(prop.getTypeCode()) && sel.getField().getName().startsWith("_"))
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())
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() != "";
}
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>();
if (values.size() > 0) {
int count = Integer.MAX_VALUE;
@ -215,10 +215,28 @@ public class GraphQLEngine implements IGraphQLEngine {
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 t = 0;
if (fp.length() == 0)
for (Base v : values) {
for (Base v : newValues) {
if ((i >= offset) && passesExtensionMode(v, extensionMode)) {
result.add(v);
t++;
@ -228,8 +246,8 @@ public class GraphQLEngine implements IGraphQLEngine {
i++;
}
else {
ExpressionNode node = fpe.parse(fp.toString().substring(5));
for (Base v : values) {
ExpressionNode node = fpe.parse(fp.substring(5));
for (Base v : newValues) {
if ((i >= offset) && passesExtensionMode(v, extensionMode) && fpe.evaluateToBoolean(null, context, v, node)) {
result.add(v);
t++;
@ -389,7 +407,7 @@ public class GraphQLEngine implements IGraphQLEngine {
if (!isPrimitive(prop.getTypeCode()) && sel.getField().getName().startsWith("_"))
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())
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="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-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-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"/>

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() != "";
}
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>();
if (values.size() > 0) {
int count = Integer.MAX_VALUE;
@ -381,10 +381,28 @@ public class GraphQLEngine implements IGraphQLEngine {
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 t = 0;
if (fp.length() == 0)
for (Base v : values) {
for (Base v : newValues) {
if ((i >= offset) && passesExtensionMode(v, extensionMode)) {
result.add(v);
t++;
@ -393,8 +411,8 @@ public class GraphQLEngine implements IGraphQLEngine {
}
i++;
} else {
ExpressionNode node = fpe.parse(fp.toString().substring(5));
for (Base v : values) {
ExpressionNode node = fpe.parse(fp.substring(5));
for (Base v : newValues) {
if ((i >= offset) && passesExtensionMode(v, extensionMode) && fpe.evaluateToBoolean(null, context, v, node)) {
result.add(v);
t++;
@ -554,7 +572,7 @@ public class GraphQLEngine implements IGraphQLEngine {
if (!isPrimitive(prop.getTypeCode()) && sel.getField().getName().startsWith("_"))
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())
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() != "";
}
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>();
if (values.size() > 0) {
int count = Integer.MAX_VALUE;
@ -381,10 +381,27 @@ public class GraphQLEngine implements IGraphQLEngine {
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 t = 0;
if (fp.length() == 0)
for (Base v : values) {
for (Base v : newValues) {
if ((i >= offset) && passesExtensionMode(v, extensionMode)) {
result.add(v);
t++;
@ -393,8 +410,8 @@ public class GraphQLEngine implements IGraphQLEngine {
}
i++;
} else {
ExpressionNode node = fpe.parse(fp.toString().substring(5));
for (Base v : values) {
ExpressionNode node = fpe.parse(fp.substring(5));
for (Base v : newValues) {
if ((i >= offset) && passesExtensionMode(v, extensionMode) && fpe.evaluateToBoolean(null, context, v, node)) {
result.add(v);
t++;
@ -554,7 +571,7 @@ public class GraphQLEngine implements IGraphQLEngine {
if (!isPrimitive(prop.getTypeCode()) && sel.getField().getName().startsWith("_"))
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())
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:
// - generate sort order 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.r5.conformance.ProfileUtilities;
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.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 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>() {{
add("decimal");
add("positiveInt");
add("unsignedInt");
}};
private final ProfileUtilities profileUtilities;
private final String version;
IWorkerContext context;
private ProfileUtilities profileUtilities;
private String version;
public GraphQLSchemaGenerator(IWorkerContext context, String version) {
super();
this.context = context;
@ -84,9 +81,15 @@ public class GraphQLSchemaGenerator {
public void generateTypes(OutputStream stream) throws IOException, FHIRException {
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(stream));
generateTypes(writer);
writer.flush();
writer.close();
}
Map<String, StructureDefinition> pl = new HashMap<String, StructureDefinition>();
Map<String, StructureDefinition> tl = new HashMap<String, StructureDefinition>();
public void generateTypes(Writer writer) throws IOException {
Map<String, StructureDefinition> pl = new HashMap<>();
Map<String, StructureDefinition> tl = new HashMap<>();
Map<String, String> existingTypeNames = new HashMap<>();
for (StructureDefinition sd : context.allStructures()) {
if (sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION) {
pl.put(sd.getName(), sd);
@ -95,29 +98,39 @@ public class GraphQLSchemaGenerator {
tl.put(sd.getName(), sd);
}
}
writer.write("# FHIR GraphQL Schema. Version "+version+"\r\n\r\n");
writer.write("# FHIR GraphQL Schema. Version " + version + "\r\n\r\n");
writer.write("# FHIR Defined Primitive types\r\n");
for (String n : sorted(pl.keySet()))
generatePrimitive(writer, pl.get(n));
writer.write("\r\n");
writer.write("# FHIR Defined Search Parameter Types\r\n");
for (SearchParamType dir : SearchParamType.values()) {
if (pl.containsKey(dir.toCode())) {
// Don't double create String and URI
continue;
}
if (dir != SearchParamType.NULL)
generateSearchParamType(writer, dir.toCode());
}
writer.write("\r\n");
generateElementBase(writer);
for (String n : sorted(tl.keySet()))
generateType(writer, tl.get(n));
writer.flush();
writer.close();
generateType(existingTypeNames, writer, tl.get(n));
}
public void generateResource(OutputStream stream, StructureDefinition sd, List<SearchParameter> parameters, EnumSet<FHIROperationType> operations) throws IOException, FHIRException {
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(stream));
writer.write("# FHIR GraphQL Schema. Version "+version+"\r\n\r\n");
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("# import * from 'types.graphql'\r\n\r\n");
generateType(writer, sd);
generateType(existingTypeNames, writer, sd);
if (operations.contains(FHIROperationType.READ))
generateIdAccess(writer, sd.getName());
if (operations.contains(FHIROperationType.SEARCH)) {
@ -130,69 +143,72 @@ public class GraphQLSchemaGenerator {
generateUpdate(writer, sd.getName());
if (operations.contains(FHIROperationType.DELETE))
generateDelete(writer, sd.getName());
writer.flush();
writer.close();
}
private void generateCreate(BufferedWriter writer, String name) throws IOException {
writer.write("type "+name+"CreateType {\r\n");
writer.write(" "+name+"Create(");
param(writer, "resource", name+"Input", false, false);
writer.write("): "+name+"Creation\r\n");
private void generateCreate(Writer writer, String name) throws IOException {
writer.write("type " + name + "CreateType {\r\n");
writer.write(" " + name + "Create(");
param(writer, "resource", name + "Input", false, false);
writer.write("): " + name + "Creation\r\n");
writer.write("}\r\n");
writer.write("\r\n");
writer.write("type "+name+"Creation {\r\n");
writer.write("type " + name + "Creation {\r\n");
writer.write(" location: String\r\n");
writer.write(" resource: "+name+"\r\n");
writer.write(" resource: " + name + "\r\n");
writer.write(" information: OperationOutcome\r\n");
writer.write("}\r\n");
writer.write("\r\n");
}
private void generateUpdate(BufferedWriter writer, String name) throws IOException {
writer.write("type "+name+"UpdateType {\r\n");
writer.write(" "+name+"Update(");
private void generateUpdate(Writer writer, String name) throws IOException {
writer.write("type " + name + "UpdateType {\r\n");
writer.write(" " + name + "Update(");
param(writer, "id", "ID", false, false);
writer.write(", ");
param(writer, "resource", name+"Input", false, false);
writer.write("): "+name+"Update\r\n");
param(writer, "resource", name + "Input", false, false);
writer.write("): " + name + "Update\r\n");
writer.write("}\r\n");
writer.write("\r\n");
writer.write("type "+name+"Update {\r\n");
writer.write(" resource: "+name+"\r\n");
writer.write("type " + name + "Update {\r\n");
writer.write(" resource: " + name + "\r\n");
writer.write(" information: OperationOutcome\r\n");
writer.write("}\r\n");
writer.write("\r\n");
}
private void generateDelete(BufferedWriter writer, String name) throws IOException {
writer.write("type "+name+"DeleteType {\r\n");
writer.write(" "+name+"Delete(");
private void generateDelete(Writer writer, String name) throws IOException {
writer.write("type " + name + "DeleteType {\r\n");
writer.write(" " + name + "Delete(");
param(writer, "id", "ID", false, false);
writer.write("): "+name+"Delete\r\n");
writer.write("): " + name + "Delete\r\n");
writer.write("}\r\n");
writer.write("\r\n");
writer.write("type "+name+"Delete {\r\n");
writer.write("type " + name + "Delete {\r\n");
writer.write(" information: OperationOutcome\r\n");
writer.write("}\r\n");
writer.write("\r\n");
}
private void generateListAccess(BufferedWriter writer, List<SearchParameter> parameters, String name) throws IOException {
writer.write("type "+name+"ListType {\r\n");
writer.write(" "+name+"List(");
private void generateListAccess(Writer writer, List<SearchParameter> parameters, String name) throws IOException {
writer.write("type " + name + "ListType {\r\n");
writer.write(" ");
generateListAccessQuery(writer, parameters, name);
writer.write("}\r\n");
writer.write("\r\n");
}
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(sp.getType().toCode()), true, true);
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");
writer.write("}\r\n");
writer.write("\r\n");
writer.write("): [" + name + "]\r\n");
}
private void param(BufferedWriter writer, String name, String type, boolean list, boolean line) throws IOException {
private void param(Writer writer, String name, String type, boolean list, boolean line) throws IOException {
if (line)
writer.write("\r\n ");
writer.write(name);
@ -204,19 +220,13 @@ public class GraphQLSchemaGenerator {
writer.write("]");
}
private void generateConnectionAccess(BufferedWriter writer, List<SearchParameter> parameters, String name) throws IOException {
writer.write("type "+name+"ConnectionType {\r\n");
writer.write(" "+name+"Conection(");
param(writer, "_filter", "String", false, false);
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");
private void generateConnectionAccess(Writer writer, List<SearchParameter> parameters, String name) throws IOException {
writer.write("type " + name + "ConnectionType {\r\n");
writer.write(" ");
generateConnectionAccessQuery(writer, parameters, name);
writer.write("}\r\n");
writer.write("\r\n");
writer.write("type "+name+"Connection {\r\n");
writer.write("type " + name + "Connection {\r\n");
writer.write(" count: Int\r\n");
writer.write(" offset: Int\r\n");
writer.write(" pagesize: Int\r\n");
@ -224,26 +234,36 @@ public class GraphQLSchemaGenerator {
writer.write(" previous: ID\r\n");
writer.write(" next: ID\r\n");
writer.write(" last: ID\r\n");
writer.write(" edges: ["+name+"Edge]\r\n");
writer.write(" edges: [" + name + "Edge]\r\n");
writer.write("}\r\n");
writer.write("\r\n");
writer.write("type "+name+"Edge {\r\n");
writer.write("type " + name + "Edge {\r\n");
writer.write(" mode: String\r\n");
writer.write(" score: Float\r\n");
writer.write(" resource: "+name+"\r\n");
writer.write(" resource: " + name + "\r\n");
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 {
writer.write("type "+name+"ReadType {\r\n");
writer.write(" "+name+"(id: ID!): "+name+"\r\n");
private void generateIdAccess(Writer writer, String name) throws IOException {
writer.write("type " + name + "ReadType {\r\n");
writer.write(" " + name + "(id: ID!): " + name + "\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(" id: ID\r\n");
writer.write(" extension: [Extension]\r\n");
@ -257,18 +277,18 @@ public class GraphQLSchemaGenerator {
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())
return;
List<StringBuilder> list = new ArrayList<StringBuilder>();
List<StringBuilder> list = new ArrayList<>();
StringBuilder b = new StringBuilder();
list.add(b);
b.append("type ");
b.append(sd.getName());
b.append(" {\r\n");
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("\r\n");
b.append("\r\n");
@ -281,7 +301,7 @@ public class GraphQLSchemaGenerator {
b.append(sd.getName());
b.append("Input {\r\n");
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("\r\n");
b.append("\r\n");
@ -289,22 +309,22 @@ public class GraphQLSchemaGenerator {
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);
for (ElementDefinition child : children) {
if (child.hasContentReference()) {
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) {
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 {
boolean ref = false;
boolean ref = false;
for (TypeRefComponent t : child.getType()) {
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) {
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);
}
}
}
@ -317,10 +337,10 @@ public class GraphQLSchemaGenerator {
if (id.equals(ed.getId()))
return ed;
}
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)) {
String n = getGqlname(typeDetails.getWorkingCode());
b.append(" ");
@ -334,15 +354,15 @@ public class GraphQLSchemaGenerator {
b.append(tail(child.getPath(), suffix));
if (suffix)
b.append(Utilities.capitalize(typeDetails.getWorkingCode()));
if (!child.getMax().equals("1")) {
b.append(": [ElementBase");
b.append(suffixS);
b.append("]\r\n");
} else {
b.append(": ElementBase");
b.append(suffixS);
b.append("\r\n");
}
if (!child.getMax().equals("1")) {
b.append(": [ElementBase");
b.append(suffixS);
b.append("]\r\n");
} else {
b.append(": ElementBase");
b.append(suffixS);
b.append("\r\n");
}
} else
b.append("\r\n");
} else {
@ -355,11 +375,11 @@ public class GraphQLSchemaGenerator {
b.append("[");
String type = typeDetails.getWorkingCode();
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"))
b.append(generateInnerType(list, sd, typeName, child, mode, suffixS));
b.append(generateInnerType(existingTypeNames, list, sd, typeName, child, mode, suffixS));
else
b.append(type+suffixS);
b.append(type).append(suffixS);
if (!child.getMax().equals("1"))
b.append("]");
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 {
if (child.hasUserData(INNER_TYPE_NAME+"."+mode))
return child.getUserString(INNER_TYPE_NAME+"."+mode);
private String generateInnerType(Map<String, String> existingTypeNames, List<StringBuilder> list, StructureDefinition sd, String name, ElementDefinition child, String mode, String suffix) throws IOException {
String key = child.getName() + "." + 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();
list.add(b);
b.append(mode);
@ -381,16 +404,16 @@ public class GraphQLSchemaGenerator {
b.append(typeName);
b.append(suffix);
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("\r\n");
b.append("\r\n");
return typeName+suffix;
return typeName + suffix;
}
private String tail(String path, boolean suffix) {
if (suffix)
path = path.substring(0, path.length()-3);
path = path.substring(0, path.length() - 3);
int i = path.lastIndexOf(".");
return i < 0 ? path : path.substring(i + 1);
}
@ -404,20 +427,19 @@ public class GraphQLSchemaGenerator {
}
private List<String> sorted(Set<String> keys) {
List<String> sl = new ArrayList<>();
sl.addAll(keys);
List<String> sl = new ArrayList<>(keys);
Collections.sort(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());
if (gqlName.equals(sd.getName())) {
writer.write("scalar ");
writer.write(sd.getName());
writer.write(" # JSON Format: ");
writer.write(getJsonFormat(sd));
} else {
} else {
writer.write("# Type ");
writer.write(sd.getName());
writer.write(": use GraphQL Scalar type ");
@ -426,7 +448,7 @@ public class GraphQLSchemaGenerator {
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);
if (gqlName.equals("date")) {
writer.write("# Search Param ");
@ -436,7 +458,7 @@ public class GraphQLSchemaGenerator {
writer.write("scalar ");
writer.write(name);
writer.write(" # JSON Format: string");
} else {
} else {
writer.write("# Search Param ");
writer.write(name);
writer.write(": use GraphQL Scalar type ");
@ -447,7 +469,7 @@ public class GraphQLSchemaGenerator {
private String getJsonFormat(StructureDefinition sd) throws FHIRException {
for (ElementDefinition ed : sd.getSnapshot().getElement()) {
if (!ed.getType().isEmpty() && ed.getType().get(0).getCodeElement().hasExtension("http://hl7.org/fhir/StructureDefinition/structuredefinition-json-type"))
if (!ed.getType().isEmpty() && ed.getType().get(0).getCodeElement().hasExtension("http://hl7.org/fhir/StructureDefinition/structuredefinition-json-type"))
return ed.getType().get(0).getCodeElement().getExtensionString("http://hl7.org/fhir/StructureDefinition/structuredefinition-json-type");
}
// all primitives but JSON_NUMBER_TYPES are represented as JSON strings
@ -469,4 +491,6 @@ public class GraphQLSchemaGenerator {
return "ID";
return name;
}
public enum FHIROperationType {READ, SEARCH, CREATE, UPDATE, DELETE}
}