openAPI compliance work + fix for validator

This commit is contained in:
Grahame Grieve 2019-04-05 17:19:25 +11:00
parent 3e9262afe8
commit a40d30f5cb
9 changed files with 209 additions and 22 deletions

View File

@ -741,8 +741,7 @@ public abstract class BaseWorkerContext implements IWorkerContext {
b.append("\r\n");
}
}
if (res != null)
return (T) res;
return (T) res;
}
}
if (class_ == CodeSystem.class && codeSystems.containsKey(uri))

View File

@ -44,6 +44,17 @@ public class BaseWriter {
return arr;
}
protected JsonObject forceArrayObject(String arrayName) {
JsonArray arr = object.getAsJsonArray(arrayName);
if (arr == null) {
arr = new JsonArray();
object.add(arrayName, arr);
}
JsonObject obj = new JsonObject();
arr.add(obj);
return obj;
}
protected JsonObject ensureMapObject(String mapName, String value) {
JsonObject map = object.getAsJsonObject(mapName);

View File

@ -17,4 +17,10 @@ public class ComponentsWriter extends BaseWriter {
ensureMapObject("schemas", name).addProperty("$ref", uri);
return this;
}
public ParameterWriter parameter(String name) {
JsonObject po = new JsonObject();
ensureMapObject("parameters", name).add(name, po);
return new ParameterWriter(po);
}
}

View File

@ -37,13 +37,15 @@ public class InfoWriter extends BaseWriter {
}
public InfoWriter contact(String name, String email, String url) {
JsonObject person = new JsonObject();
person.addProperty("name", name);
if (!Utilities.noString(email))
person.addProperty("email", email);
if (!Utilities.noString(url))
person.addProperty("url", url);
object.add("contact", person);
if (name != null && !object.has("contact")) {
JsonObject person = new JsonObject();
person.addProperty("name", name);
if (!Utilities.noString(email))
person.addProperty("email", email);
if (!Utilities.noString(url))
person.addProperty("url", url);
object.add("contact", person);
}
return this;
}

View File

@ -2,9 +2,11 @@ package org.hl7.fhir.r5.openapi;
import java.util.List;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.model.CapabilityStatement;
import org.hl7.fhir.r5.model.CapabilityStatement.CapabilityStatementRestComponent;
import org.hl7.fhir.r5.model.CapabilityStatement.CapabilityStatementRestResourceComponent;
import org.hl7.fhir.r5.model.CapabilityStatement.CapabilityStatementRestResourceSearchParamComponent;
import org.hl7.fhir.r5.model.CapabilityStatement.ResourceInteractionComponent;
import org.hl7.fhir.r5.model.CapabilityStatement.ResourceVersionPolicy;
import org.hl7.fhir.r5.model.CapabilityStatement.RestfulCapabilityMode;
@ -13,24 +15,32 @@ import org.hl7.fhir.r5.model.CodeType;
import org.hl7.fhir.r5.model.ContactDetail;
import org.hl7.fhir.r5.model.ContactPoint;
import org.hl7.fhir.r5.model.ContactPoint.ContactPointSystem;
import org.hl7.fhir.r5.model.Enumerations.SearchParamType;
import org.hl7.fhir.r5.model.SearchParameter;
import org.hl7.fhir.r5.openapi.ParameterWriter.ParameterLocation;
import org.hl7.fhir.r5.openapi.ParameterWriter.ParameterStyle;
import org.hl7.fhir.r5.openapi.SchemaWriter.SchemaType;
public class OpenApiGenerator {
private IWorkerContext context;
private CapabilityStatement source;
private Writer dest;
public OpenApiGenerator(CapabilityStatement cs, Writer oa) {
public OpenApiGenerator(IWorkerContext context, CapabilityStatement cs, Writer oa) {
this.context = context;
this.source = cs;
this.dest = oa;
}
public void generate(String license) {
dest.info().title(source.getTitle()).description(source.getDescription()).license(license, null).version(source.getVersion());
if (source.hasPublisher())
dest.info().contact(source.getPublisher(), null, null);
public void generate(String license, String url) {
dest.info().title(source.getTitle()).description(source.getDescription()).license(license, url).version(source.getVersion());
for (ContactDetail cd : source.getContact()) {
dest.info().contact(cd.getName(), email(cd.getTelecom()), url(cd.getTelecom()));
}
if (source.hasPublisher())
dest.info().contact(source.getPublisher(), null, null);
if (source.hasImplementation()) {
dest.server(source.getImplementation().getUrl()).description(source.getImplementation().getDescription());
@ -42,8 +52,23 @@ public class OpenApiGenerator {
generatePaths(csr);
}
}
writeBaseParameters(dest.components());
}
private void writeBaseParameters(ComponentsWriter components) {
components.parameter("summary").name("_summary").in(ParameterLocation.query).description("Requests the server to return a designated subset of the resource").allowEmptyValue().style(ParameterStyle.matrix)
.schema().type(SchemaType.string).enums("true", "text", "data", "count", "false");
components.parameter("format").name("_format").in(ParameterLocation.query).description("Specify alternative response formats by their MIME-types (when a client is unable acccess accept: header)").allowEmptyValue().style(ParameterStyle.matrix)
.schema().type(SchemaType.string).format("mime-type");
components.parameter("pretty").name("_pretty").in(ParameterLocation.query).description("Ask for a pretty printed response for human convenience").allowEmptyValue().style(ParameterStyle.matrix)
.schema().type(SchemaType.bool);
components.parameter("elements").name("_elements").in(ParameterLocation.query).description("Requests the server to return a collection of elements from the resource").allowEmptyValue().style(ParameterStyle.matrix).explode(false)
.schema().type(SchemaType.array).format("string");
}
private void generatePaths(CapabilityStatementRestComponent csr) {
for (CapabilityStatementRestResourceComponent r : csr.getResource())
generateResource(r);
@ -58,6 +83,8 @@ public class OpenApiGenerator {
generateVRead(r);
if (hasOp(r, TypeRestfulInteraction.UPDATE))
generateUpdate(r);
if (hasOp(r, TypeRestfulInteraction.CREATE))
generateCreate(r);
}
private void generateRead(CapabilityStatementRestResourceComponent r) {
@ -73,6 +100,12 @@ public class OpenApiGenerator {
resp.content("application/fhir+json").schemaRef(specRef()+"/fhir.json.schema#/definitions/"+r.getType());
if (isXml())
resp.content("application/fhir+xml").schemaRef(specRef()+"/"+r.getType()+".xsd");
// parameters:
op.paramRef("#/Components/parameters/summary");
op.paramRef("#/Components/parameters/format");
op.paramRef("#/Components/parameters/pretty");
op.paramRef("#/Components/parameters/elements");
}
private void generateSearch(CapabilityStatementRestResourceComponent r) {
@ -88,6 +121,35 @@ public class OpenApiGenerator {
resp.content("application/fhir+json").schemaRef(specRef()+"/fhir.json.schema#/definitions/Bundle");
if (isXml())
resp.content("application/fhir+xml").schemaRef(specRef()+"/Bundle.xsd");
op.paramRef("#/Components/parameters/summary");
op.paramRef("#/Components/parameters/format");
op.paramRef("#/Components/parameters/pretty");
op.paramRef("#/Components/parameters/elements");
for (CapabilityStatementRestResourceSearchParamComponent spc : r.getSearchParam()) {
ParameterWriter p = op.parameter(spc.getName());
p.in(ParameterLocation.query).description(spc.getDocumentation());
p.schema().type(getSchemaType(spc.getType()));
if (spc.hasDefinition()) {
SearchParameter sp = context.fetchResource(SearchParameter.class, spc.getDefinition());
if (sp != null) {
p.description(sp.getDescription());
}
}
}
}
private SchemaType getSchemaType(SearchParamType type) {
switch (type) {
// case COMPOSITE:
case DATE: return SchemaType.dateTime;
case NUMBER: return SchemaType.number;
case QUANTITY: return SchemaType.string;
case REFERENCE: return SchemaType.string;
case STRING: return SchemaType.string;
case TOKEN: return SchemaType.string;
case URI: return SchemaType.string;
}
return null;
}
private void generateVRead(CapabilityStatementRestResourceComponent r) {
@ -103,12 +165,19 @@ public class OpenApiGenerator {
resp.content("application/fhir+json").schemaRef(specRef()+"/fhir.json.schema#/definitions/"+r.getType());
if (isXml())
resp.content("application/fhir+xml").schemaRef(specRef()+"/"+r.getType()+".xsd");
op.paramRef("#/Components/parameters/summary");
op.paramRef("#/Components/parameters/format");
op.paramRef("#/Components/parameters/pretty");
op.paramRef("#/Components/parameters/elements");
}
// todo: how does prefer header affect return type?
private void generateUpdate(CapabilityStatementRestResourceComponent r) {
OperationWriter op = makePathResId(r).operation("put");
op.summary("Update the current state of the resource");
if (r.getUpdateCreate())
op.summary("Update the current state of the resource (can create a new resource if it does not exist)");
else
op.summary("Update the current state of the resource");
op.operationId("update"+r.getType());
RequestBodyWriter req = op.request();
req.description("The new state of the resource").required(true);
@ -126,6 +195,36 @@ public class OpenApiGenerator {
resp.content("application/fhir+json").schemaRef(specRef()+"/fhir.json.schema#/definitions/"+r.getType());
if (isXml())
resp.content("application/fhir+xml").schemaRef(specRef()+"/"+r.getType()+".xsd");
op.paramRef("#/Components/parameters/summary");
op.paramRef("#/Components/parameters/format");
op.paramRef("#/Components/parameters/pretty");
op.paramRef("#/Components/parameters/elements");
}
private void generateCreate(CapabilityStatementRestResourceComponent r) {
OperationWriter op = makePathRes(r).operation("put");
op.summary("Create a new resource");
op.operationId("create"+r.getType());
RequestBodyWriter req = op.request();
req.description("The new state of the resource").required(true);
if (isJson())
req.content("application/fhir+json").schemaRef(specRef()+"/fhir.json.schema#/definitions/"+r.getType());
if (isXml())
req.content("application/fhir+xml").schemaRef(specRef()+"/"+r.getType()+".xsd");
opOutcome(op.responses().defaultResponse());
ResponseObjectWriter resp = op.responses().httpResponse("200");
resp.description("the resource being returned after being updated");
if (r.getVersioning() != ResourceVersionPolicy.NOVERSION)
resp.header("ETag").description("Version from Resource.meta.version as a weak ETag");
if (isJson())
resp.content("application/fhir+json").schemaRef(specRef()+"/fhir.json.schema#/definitions/"+r.getType());
if (isXml())
resp.content("application/fhir+xml").schemaRef(specRef()+"/"+r.getType()+".xsd");
op.paramRef("#/Components/parameters/summary");
op.paramRef("#/Components/parameters/format");
op.paramRef("#/Components/parameters/pretty");
op.paramRef("#/Components/parameters/elements");
}
private void opOutcome(ResponseObjectWriter resp) {

View File

@ -39,11 +39,13 @@ public class OperationWriter extends BaseWriter {
}
public ParameterWriter parameter(String name) {
return new ParameterWriter(ensureMapObject("parameters", name));
JsonObject obj = forceArrayObject("parameters");
obj.addProperty("name", name);
return new ParameterWriter(obj);
}
public OperationWriter pathRef(String name, String url) {
ensureMapObject("parameters", name).addProperty("$ref", url);
public OperationWriter paramRef(String url) {
forceArrayObject("parameters").addProperty("$ref", url);
return this;
}

View File

@ -20,9 +20,19 @@ public class ParameterWriter extends BaseWriter {
return this;
}
public ParameterWriter name(String value) {
object.addProperty("name", value);
return this;
}
public ParameterWriter allowEmptyValue() {
object.addProperty("allowEmptyValue", true);
return this;
}
public ParameterWriter description(String value) {
object.addProperty("description", value);
if (value != null)
object.addProperty("description", value);
return this;
}
@ -64,6 +74,12 @@ public class ParameterWriter extends BaseWriter {
return this;
}
public SchemaWriter schema() {
JsonObject so = new JsonObject();
object.add("schema", so);
return new SchemaWriter(so);
}
public ParameterWriter schemaRef(String name, String uri) {
JsonObject schema = new JsonObject();
schema.addProperty("$ref", uri);

View File

@ -0,0 +1,52 @@
package org.hl7.fhir.r5.openapi;
import org.hl7.fhir.r5.openapi.SchemaWriter.SchemaType;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
public class SchemaWriter extends BaseWriter {
public enum SchemaType {
array,
bool,
dateTime,
number,
string;
public String toCode() {
switch (this) {
case array: return "array";
case bool: return "boolean";
case dateTime: return "dateTime";
case number: return "number";
case string: return "string";
}
return "??";
}
}
public SchemaWriter(JsonObject object) {
super(object);
}
public SchemaWriter type(SchemaType value) {
if (value != null)
object.addProperty("type", value.toCode());
return this;
}
public SchemaWriter enums(String... values) {
JsonArray arr = forceArray("enum");
for (String s : values)
arr.add(s);
return this;
}
public SchemaWriter format(String value) {
if (value != null)
object.addProperty("format", value);
return this;
}
}

View File

@ -20,13 +20,13 @@ public class Writer extends BaseWriter {
public Writer(OutputStream stream) {
super( new JsonObject());
this.stream = stream;
object.addProperty("openapi", "3.0.1");
object.addProperty("openapi", "3.0.2");
}
public Writer(OutputStream stream, InputStream template) throws JsonSyntaxException, IOException {
super(parse(template));
this.stream = stream;
object.addProperty("openapi", "3.0.1");
object.addProperty("openapi", "3.0.2");
}
private static JsonObject parse(InputStream template) throws JsonSyntaxException, IOException {