001package org.hl7.fhir.r5.openapi;
002
003/*
004  Copyright (c) 2011+, HL7, Inc.
005  All rights reserved.
006  
007  Redistribution and use in source and binary forms, with or without modification, 
008  are permitted provided that the following conditions are met:
009    
010   * Redistributions of source code must retain the above copyright notice, this 
011     list of conditions and the following disclaimer.
012   * Redistributions in binary form must reproduce the above copyright notice, 
013     this list of conditions and the following disclaimer in the documentation 
014     and/or other materials provided with the distribution.
015   * Neither the name of HL7 nor the names of its contributors may be used to 
016     endorse or promote products derived from this software without specific 
017     prior written permission.
018  
019  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
020  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
021  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
022  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
023  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
024  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
025  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
026  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
027  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
028  POSSIBILITY OF SUCH DAMAGE.
029  
030 */
031
032
033import java.util.HashSet;
034import java.util.List;
035import java.util.Set;
036
037import org.hl7.fhir.r5.context.IWorkerContext;
038import org.hl7.fhir.r5.model.CapabilityStatement;
039import org.hl7.fhir.r5.model.CapabilityStatement.CapabilityStatementRestComponent;
040import org.hl7.fhir.r5.model.CapabilityStatement.CapabilityStatementRestResourceComponent;
041import org.hl7.fhir.r5.model.CapabilityStatement.CapabilityStatementRestResourceSearchParamComponent;
042import org.hl7.fhir.r5.model.CapabilityStatement.ResourceInteractionComponent;
043import org.hl7.fhir.r5.model.CapabilityStatement.ResourceVersionPolicy;
044import org.hl7.fhir.r5.model.CapabilityStatement.SystemInteractionComponent;
045import org.hl7.fhir.r5.model.CapabilityStatement.SystemRestfulInteraction;
046import org.hl7.fhir.r5.model.CapabilityStatement.TypeRestfulInteraction;
047import org.hl7.fhir.r5.model.CodeType;
048import org.hl7.fhir.r5.model.ContactDetail;
049import org.hl7.fhir.r5.model.ContactPoint;
050import org.hl7.fhir.r5.model.ContactPoint.ContactPointSystem;
051import org.hl7.fhir.r5.model.CapabilityStatement.RestfulCapabilityMode;
052import org.hl7.fhir.r5.model.Enumerations.SearchParamType;
053import org.hl7.fhir.r5.model.Resource;
054import org.hl7.fhir.r5.model.SearchParameter;
055import org.hl7.fhir.r5.openapi.ParameterWriter.ParameterLocation;
056import org.hl7.fhir.r5.openapi.ParameterWriter.ParameterStyle;
057import org.hl7.fhir.r5.openapi.SchemaWriter.SchemaType;
058import org.hl7.fhir.utilities.Utilities;
059
060
061public class OpenApiGenerator {
062
063  private IWorkerContext context;
064  private CapabilityStatement source;
065  private Writer dest;
066
067  public OpenApiGenerator(IWorkerContext context, CapabilityStatement cs, Writer oa) {
068    this.context = context;
069    this.source = cs;
070    this.dest = oa;
071  }
072
073  public void generate(String license, String url) {
074    dest.info().title(source.present()).description(source.getDescription()).license(license, url).version(source.getVersion());
075    for (ContactDetail cd : source.getContact()) {
076      dest.info().contact(cd.getName(), email(cd.getTelecom()), url(cd.getTelecom()));
077    }
078    if (source.hasPublisher())
079      dest.info().contact(source.getPublisher(), null, null);
080
081    if (source.hasImplementation()) {
082      dest.server(source.getImplementation().getUrl()).description(source.getImplementation().getDescription());
083    }
084    dest.externalDocs().url(source.getUrl()).description("FHIR CapabilityStatement");
085
086    for (CapabilityStatementRestComponent csr : source.getRest()) {
087      if (csr.getMode() == RestfulCapabilityMode.SERVER) {
088        generatePaths(csr, source);
089      }
090    }
091    writeBaseParameters(dest.components());
092  }
093
094  private void writeBaseParameters(ComponentsWriter components) {
095    components.parameter("rid").name("rid").in(ParameterLocation.path).description("id of the resource (=Resource.id)").required(true).allowEmptyValue(false).style(ParameterStyle.simple)
096    .schema().type(SchemaType.string);
097    
098    components.parameter("hid").name("hid").in(ParameterLocation.path).description("id of the history entry (=Resource.meta.versionId)").required(true).allowEmptyValue(false).style(ParameterStyle.simple)
099    .schema().type(SchemaType.string);
100
101    components.parameter("summary").name("_summary").in(ParameterLocation.query).description("Requests the server to return a designated subset of the resource").allowEmptyValue().style(ParameterStyle.form)
102    .schema().type(SchemaType.string).enums("true", "text", "data", "count", "false");
103
104    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.form)
105    .schema().type(SchemaType.string).format("mime-type");
106
107    components.parameter("pretty").name("_pretty").in(ParameterLocation.query).description("Ask for a pretty printed response for human convenience").allowEmptyValue().style(ParameterStyle.form)
108    .schema().type(SchemaType.bool);
109
110    SchemaWriter p = components.parameter("elements").name("_elements").in(ParameterLocation.query).description("Requests the server to return a collection of elements from the resource").allowEmptyValue().style(ParameterStyle.form).explode(false)
111    .schema();
112    p.type(SchemaType.array).format("string");
113    p.items().format("string");
114
115    components.parameter("count").name("_count").in(ParameterLocation.query).description("The maximum number of search results on a page. The server is not bound to return the number requested, but cannot return more")
116    .schema().type(SchemaType.number);
117  }
118
119  private void generatePaths(CapabilityStatementRestComponent csr, Resource cs) {
120    generateMetadata();
121    for (CapabilityStatementRestResourceComponent r : csr.getResource())
122      generateResource(r, cs);
123    if (hasOp(csr, SystemRestfulInteraction.HISTORYSYSTEM))
124      generateHistorySystem(csr);
125    if (hasOp(csr, SystemRestfulInteraction.SEARCHSYSTEM))
126      generateSearchSystem(csr, cs);
127    if (hasOp(csr, SystemRestfulInteraction.BATCH) || hasOp(csr, SystemRestfulInteraction.TRANSACTION) )
128      generateBatchTransaction(csr);
129  }
130
131  private void generateResource(CapabilityStatementRestResourceComponent r, Resource cs) {
132    if (hasOp(r, TypeRestfulInteraction.SEARCHTYPE)) 
133      generateSearch(r, cs);
134    if (hasOp(r, TypeRestfulInteraction.READ))
135      generateRead(r);
136    if (hasOp(r, TypeRestfulInteraction.CREATE)) 
137      generateCreate(r);
138    if (hasOp(r, TypeRestfulInteraction.UPDATE)) 
139      generateUpdate(r);
140    if (hasOp(r, TypeRestfulInteraction.PATCH)) 
141      generatePatch(r);
142    if (hasOp(r, TypeRestfulInteraction.DELETE)) 
143      generateDelete(r);
144    if (hasOp(r, TypeRestfulInteraction.HISTORYINSTANCE)) 
145      generateHistoryInstance(r);
146    if (hasOp(r, TypeRestfulInteraction.VREAD)) 
147      generateVRead(r);
148    if (hasOp(r, TypeRestfulInteraction.HISTORYTYPE)) 
149      generateHistoryType(r);
150  }
151
152  private void generateMetadata() {
153    OperationWriter op = makePathMetadata().operation("get");
154    op.summary("Return the server's capability statement");
155    op.operationId("metadata");
156    opOutcome(op.responses().defaultResponse());
157    ResponseObjectWriter resp = op.responses().httpResponse("200");
158    resp.description("the capbility statement");
159    if (isJson())
160      resp.content("application/fhir+json").schemaRef(specRef()+"/fhir.schema.json#/definitions/CapabilityStatement");
161    if (isXml())
162      resp.content("application/fhir+xml").schemaRef(specRef()+"/CapabilityStatement.xsd");
163
164    // parameters - but do they apply?
165    op.paramRef("#/components/parameters/format");
166    op.paramRef("#/components/parameters/pretty");
167    op.paramRef("#/components/parameters/summary");
168    op.paramRef("#/components/parameters/elements");
169  }
170
171  private void generateRead(CapabilityStatementRestResourceComponent r) {
172    OperationWriter op = makePathResId(r).operation("get");
173    op.summary("Read the current state of the resource");
174    op.operationId("read"+r.getType());
175    opOutcome(op.responses().defaultResponse());
176    ResponseObjectWriter resp = op.responses().httpResponse("200");
177    resp.description("the resource being returned");
178    if (r.getVersioning() != ResourceVersionPolicy.NOVERSION)
179      resp.header("ETag").description("Version from Resource.meta.version as a weak ETag").schema().type(SchemaType.string);
180    if (isJson())
181      resp.content("application/fhir+json").schemaRef(specRef()+"/fhir.schema.json#/definitions/"+r.getType());
182    if (isXml())
183      resp.content("application/fhir+xml").schemaRef(specRef()+"/"+r.getType()+".xsd");
184
185    // parameters:
186    op.paramRef("#/components/parameters/rid");
187    op.paramRef("#/components/parameters/summary");
188    op.paramRef("#/components/parameters/format");
189    op.paramRef("#/components/parameters/pretty");
190    op.paramRef("#/components/parameters/elements");
191  }
192
193  private void generateSearch(CapabilityStatementRestResourceComponent r, Resource cs) {
194    OperationWriter op = makePathResType(r).operation("get");
195    op.summary("Search all resources of type "+r.getType()+" based on a set of criteria");
196    op.operationId("search"+r.getType());
197    opOutcome(op.responses().defaultResponse());
198    ResponseObjectWriter resp = op.responses().httpResponse("200");
199    resp.description("the resource being returned");
200    if (isJson())
201      resp.content("application/fhir+json").schemaRef(specRef()+"/fhir.schema.json#/definitions/Bundle");
202    if (isXml())
203      resp.content("application/fhir+xml").schemaRef(specRef()+"/Bundle.xsd");
204    // todo: how do we know that these apply? 
205    op.paramRef("#/components/parameters/format");
206    op.paramRef("#/components/parameters/pretty");
207    op.paramRef("#/components/parameters/summary");
208    op.paramRef("#/components/parameters/elements");
209    Set<String> set = new HashSet<>();
210    for (CapabilityStatementRestResourceSearchParamComponent spc : r.getSearchParam()) {
211      if (!set.contains(spc.getName())) {
212        set.add(spc.getName());
213        ParameterWriter p = op.parameter(spc.getName());
214        p.in(ParameterLocation.query).description(spc.getDocumentation());
215        p.schema().type(getSchemaType(spc.getType()));
216        if (spc.hasDefinition()) {
217          SearchParameter sp = context.fetchResource(SearchParameter.class, spc.getDefinition(), cs);
218          if (sp != null) {
219            p.description(sp.getDescription());
220          }
221        }
222      }
223    }
224  }
225
226  private void generateSearchSystem(CapabilityStatementRestComponent csr, Resource cs) {
227    OperationWriter op = makePathSystem().operation("get");
228    op.summary("Search all resources of all types based on a set of criteria");
229    op.operationId("searchAll");
230    opOutcome(op.responses().defaultResponse());
231    ResponseObjectWriter resp = op.responses().httpResponse("200");
232    resp.description("the resource being returned");
233    if (isJson())
234      resp.content("application/fhir+json").schemaRef(specRef()+"/fhir.schema.json#/definitions/Bundle");
235    if (isXml())
236      resp.content("application/fhir+xml").schemaRef(specRef()+"/Bundle.xsd");
237    // todo: how do we know that these apply? 
238    op.paramRef("#/components/parameters/format");
239    op.paramRef("#/components/parameters/pretty");
240    op.paramRef("#/components/parameters/summary");
241    op.paramRef("#/components/parameters/elements");
242    Set<String> set = new HashSet<>();
243    set.add("_summary");
244    set.add("_format");
245    set.add("_pretty");
246    set.add("_elements");
247    for (CapabilityStatementRestResourceSearchParamComponent spc : csr.getSearchParam()) {
248      if (!set.contains(spc.getName())) {
249        set.add(spc.getName());
250        ParameterWriter p = op.parameter(spc.getName());
251        p.in(ParameterLocation.query).description(spc.getDocumentation());
252        p.schema().type(getSchemaType(spc.getType()));
253        if (spc.hasDefinition()) {
254          SearchParameter sp = context.fetchResource(SearchParameter.class, spc.getDefinition(), cs);
255          if (sp != null) {
256            p.description(sp.getDescription());
257          }
258        }
259      }
260    }
261  }
262
263  private SchemaType getSchemaType(SearchParamType type) {
264    switch (type) {
265    // case COMPOSITE:
266    case DATE: return SchemaType.dateTime;
267    case NUMBER: return SchemaType.number; 
268    case QUANTITY: return SchemaType.string;
269    case REFERENCE: return SchemaType.string;
270    case STRING: return SchemaType.string;
271    case TOKEN: return SchemaType.string;
272    case URI: return SchemaType.string;
273    }
274    return null;
275  }
276
277  private void generateHistoryType(CapabilityStatementRestResourceComponent r) {
278    OperationWriter op = makePathResHistListType(r).operation("get");
279    op.summary("Read the past states of the resource");
280    op.operationId("histtype"+r.getType());
281    opOutcome(op.responses().defaultResponse());
282    ResponseObjectWriter resp = op.responses().httpResponse("200");
283    resp.description("the resources being returned");
284    if (isJson())
285      resp.content("application/fhir+json").schemaRef(specRef()+"/fhir.schema.json#/definitions/Bundle");
286    if (isXml())
287      resp.content("application/fhir+xml").schemaRef(specRef()+"/Bundle.xsd");
288    op.paramRef("#/components/parameters/summary");
289    op.paramRef("#/components/parameters/format");
290    op.paramRef("#/components/parameters/pretty");
291    op.paramRef("#/components/parameters/elements");
292    op.paramRef("#/components/parameters/count");
293
294    op.parameter("_since").in(ParameterLocation.query).description("Only include resource versions that were created at or after the given instant in time").schema().type(SchemaType.dateTime);
295    op.parameter("_at").in(ParameterLocation.query).description("Only include resource versions that were current at some point during the time period specified in the date time value (see Search notes on date searching)").schema().type(SchemaType.dateTime);
296    op.parameter("_list").in(ParameterLocation.query).description("Only include resource versions that are referenced in the specified list (current list references are allowed)").schema().type(SchemaType.string);
297  }
298
299  private void generateHistoryInstance(CapabilityStatementRestResourceComponent r) {
300    OperationWriter op = makePathResHistListId(r).operation("get");
301    op.summary("Read the past states of the resource");
302    op.operationId("histinst"+r.getType());
303    opOutcome(op.responses().defaultResponse());
304    ResponseObjectWriter resp = op.responses().httpResponse("200");
305    resp.description("the resources being returned");
306    if (isJson())
307      resp.content("application/fhir+json").schemaRef(specRef()+"/fhir.schema.json#/definitions/Bundle");
308    if (isXml())
309      resp.content("application/fhir+xml").schemaRef(specRef()+"/Bundle.xsd");
310    op.paramRef("#/components/parameters/rid");
311    op.paramRef("#/components/parameters/summary");
312    op.paramRef("#/components/parameters/format");
313    op.paramRef("#/components/parameters/pretty");
314    op.paramRef("#/components/parameters/elements");
315    op.paramRef("#/components/parameters/count");
316
317    op.parameter("_since").in(ParameterLocation.query).description("Only include resource versions that were created at or after the given instant in time").schema().type(SchemaType.dateTime);
318    op.parameter("_at").in(ParameterLocation.query).description("Only include resource versions that were current at some point during the time period specified in the date time value (see Search notes on date searching)").schema().type(SchemaType.dateTime);
319    op.parameter("_list").in(ParameterLocation.query).description("Only include resource versions that are referenced in the specified list (current list references are allowed)").schema().type(SchemaType.string);
320  }
321
322  private void generateHistorySystem(CapabilityStatementRestComponent csr) {
323    OperationWriter op = makePathHistListSystem().operation("get");
324    op.summary("Read the past states of all resources");
325    op.operationId("histAll");
326    opOutcome(op.responses().defaultResponse());
327    ResponseObjectWriter resp = op.responses().httpResponse("200");
328    resp.description("the resources being returned");
329    if (isJson())
330      resp.content("application/fhir+json").schemaRef(specRef()+"/fhir.schema.json#/definitions/Bundle");
331    if (isXml())
332      resp.content("application/fhir+xml").schemaRef(specRef()+"/Bundle.xsd");
333    op.paramRef("#/components/parameters/summary");
334    op.paramRef("#/components/parameters/format");
335    op.paramRef("#/components/parameters/pretty");
336    op.paramRef("#/components/parameters/elements");
337    op.paramRef("#/components/parameters/count");
338
339    op.parameter("_since").in(ParameterLocation.query).description("Only include resource versions that were created at or after the given instant in time").schema().type(SchemaType.dateTime);
340    op.parameter("_at").in(ParameterLocation.query).description("Only include resource versions that were current at some point during the time period specified in the date time value (see Search notes on date searching)").schema().type(SchemaType.dateTime);
341    op.parameter("_list").in(ParameterLocation.query).description("Only include resource versions that are referenced in the specified list (current list references are allowed)").schema().type(SchemaType.string);
342  }
343
344  private void generateVRead(CapabilityStatementRestResourceComponent r) {
345    OperationWriter op = makePathResHistId(r).operation("get");
346    op.summary("Read a past state of the resource");
347    op.operationId("vread"+r.getType());
348    opOutcome(op.responses().defaultResponse());
349    ResponseObjectWriter resp = op.responses().httpResponse("200");
350    resp.description("the resource being returned");
351    if (r.getVersioning() != ResourceVersionPolicy.NOVERSION)
352      resp.header("ETag").description("Version from Resource.meta.version as a weak ETag for that version").schema().type(SchemaType.string);
353    if (isJson())
354      resp.content("application/fhir+json").schemaRef(specRef()+"/fhir.schema.json#/definitions/"+r.getType());
355    if (isXml())
356      resp.content("application/fhir+xml").schemaRef(specRef()+"/"+r.getType()+".xsd");
357    op.paramRef("#/components/parameters/rid");
358    op.paramRef("#/components/parameters/hid");
359    op.paramRef("#/components/parameters/summary");
360    op.paramRef("#/components/parameters/format");
361    op.paramRef("#/components/parameters/pretty");
362    op.paramRef("#/components/parameters/elements");
363  }
364
365  // todo: how does prefer header affect return type?
366  private void generateUpdate(CapabilityStatementRestResourceComponent r) {
367    OperationWriter op = makePathResId(r).operation("put");
368    if (r.getUpdateCreate())
369      op.summary("Update the current state of the resource (can create a new resource if it does not exist)");
370    else
371      op.summary("Update the current state of the resource");
372    op.operationId("update"+r.getType());
373    RequestBodyWriter req = op.request();
374    req.description("The new state of the resource").required(true);
375    if (isJson())
376      req.content("application/fhir+json").schemaRef(specRef()+"/fhir.schema.json#/definitions/"+r.getType());
377    if (isXml())
378      req.content("application/fhir+xml").schemaRef(specRef()+"/"+r.getType()+".xsd");
379
380    opOutcome(op.responses().defaultResponse());
381    ResponseObjectWriter resp = op.responses().httpResponse("200");
382    resp.description("the resource being returned after being updated");
383    if (r.getVersioning() != ResourceVersionPolicy.NOVERSION)
384      resp.header("ETag").description("Version from Resource.meta.version as a weak ETag").schema().type(SchemaType.string);
385    if (isJson())
386      resp.content("application/fhir+json").schemaRef(specRef()+"/fhir.schema.json#/definitions/"+r.getType());
387    if (isXml())
388      resp.content("application/fhir+xml").schemaRef(specRef()+"/"+r.getType()+".xsd");
389    op.paramRef("#/components/parameters/rid");
390    op.paramRef("#/components/parameters/summary");
391    op.paramRef("#/components/parameters/format");
392    op.paramRef("#/components/parameters/pretty");
393    op.paramRef("#/components/parameters/elements");
394  }
395
396  private void generatePatch(CapabilityStatementRestResourceComponent r) {
397    OperationWriter op = makePathResId(r).operation("patch");
398    op.summary("Change the current state of the resource by providing a patch - a series of change commands");
399    op.operationId("patch"+r.getType());
400    RequestBodyWriter req = op.request();
401    req.description("The new state of the resource").required(true);
402    if (isJson()) {
403      req.content("application/json-patch+json").schemaRef(specRef()+"/fhir.schema.json#/definitions/"+r.getType());
404      req.content("application/fhir+json").schemaRef(specRef()+"/fhir.schema.json#/definitions/Parameters");
405    }
406    if (isXml()) {
407      req.content("application/xml-patch+xml").schemaRef(specRef()+"/"+r.getType()+".xsd");
408      req.content("application/fhir+xml").schemaRef(specRef()+"/Parameters.xsd");
409    }
410
411    opOutcome(op.responses().defaultResponse());
412    ResponseObjectWriter resp = op.responses().httpResponse("200");
413    resp.description("the resource being returned after being patched");
414    if (r.getVersioning() != ResourceVersionPolicy.NOVERSION)
415      resp.header("ETag").description("Version from Resource.meta.version as a weak ETag").schema().type(SchemaType.string);
416    if (isJson())
417      resp.content("application/fhir+json").schemaRef(specRef()+"/fhir.schema.json#/definitions/"+r.getType());
418    if (isXml())
419      resp.content("application/fhir+xml").schemaRef(specRef()+"/"+r.getType()+".xsd");
420    op.paramRef("#/components/parameters/rid");
421    op.paramRef("#/components/parameters/summary");
422    op.paramRef("#/components/parameters/format");
423    op.paramRef("#/components/parameters/pretty");
424    op.paramRef("#/components/parameters/elements");
425  }
426
427  private void generateDelete(CapabilityStatementRestResourceComponent r) {
428    OperationWriter op = makePathResId(r).operation("delete");
429    op.summary("Delete the resource so that it no exists (no read, search etc)");
430    op.operationId("delete"+r.getType());
431    opOutcome(op.responses().defaultResponse());
432    ResponseObjectWriter resp = op.responses().httpResponse("204");
433    resp.description("If the resource is deleted - no content is returned");
434    if (r.getVersioning() != ResourceVersionPolicy.NOVERSION)
435      resp.header("ETag").description("Version from Resource.meta.version as a weak ETag").schema().type(SchemaType.string);
436    op.paramRef("#/components/parameters/rid");
437  }
438
439  private void generateCreate(CapabilityStatementRestResourceComponent r) {
440    OperationWriter op = makePathRes(r).operation("post");
441    op.summary("Create a new resource");
442    op.operationId("create"+r.getType());
443    RequestBodyWriter req = op.request();
444    req.description("The new state of the resource").required(true);
445    if (isJson())
446      req.content("application/fhir+json").schemaRef(specRef()+"/fhir.schema.json#/definitions/"+r.getType());
447    if (isXml())
448      req.content("application/fhir+xml").schemaRef(specRef()+"/"+r.getType()+".xsd");
449
450    opOutcome(op.responses().defaultResponse());
451    ResponseObjectWriter resp = op.responses().httpResponse("200");
452    resp.description("the resource being returned after being updated");
453    if (r.getVersioning() != ResourceVersionPolicy.NOVERSION)
454      resp.header("ETag").description("Version from Resource.meta.version as a weak ETag").schema().type(SchemaType.string);
455    if (isJson())
456      resp.content("application/fhir+json").schemaRef(specRef()+"/fhir.schema.json#/definitions/"+r.getType());
457    if (isXml())
458      resp.content("application/fhir+xml").schemaRef(specRef()+"/"+r.getType()+".xsd");
459    op.paramRef("#/components/parameters/summary");
460    op.paramRef("#/components/parameters/format");
461    op.paramRef("#/components/parameters/pretty");
462    op.paramRef("#/components/parameters/elements");
463  }
464
465  private void generateBatchTransaction(CapabilityStatementRestComponent csr) {
466    OperationWriter op = makePathSystem().operation("put");
467    op.summary("Batch or Transaction");
468    op.operationId("transaction");
469    RequestBodyWriter req = op.request();
470    req.description("The batch or transaction").required(true);
471    if (isJson())
472      req.content("application/fhir+json").schemaRef(specRef()+"/fhir.schema.json#/definitions/Bundle");
473    if (isXml())
474      req.content("application/fhir+xml").schemaRef(specRef()+"/Bundle.xsd");
475
476    opOutcome(op.responses().defaultResponse());
477    ResponseObjectWriter resp = op.responses().httpResponse("200");
478    resp.description("Batch or Transaction response");
479    if (isJson())
480      resp.content("application/fhir+json").schemaRef(specRef()+"/fhir.schema.json#/definitions/Bundle");
481    if (isXml())
482      resp.content("application/fhir+xml").schemaRef(specRef()+"/Bundle.xsd");
483    op.paramRef("#/components/parameters/format");
484    op.paramRef("#/components/parameters/pretty");
485  }
486
487  private void opOutcome(ResponseObjectWriter resp) {
488    resp.description("Error, with details");
489    if (isJson())
490      resp.content("application/fhir+json").schemaRef(specRef()+"/fhir.schema.json#/definitions/OperationOutcome");
491    if (isXml())
492      resp.content("application/fhir+xml").schemaRef(specRef()+"/OperationOutcome.xsd");    
493  }
494
495  private String specRef() {
496    String ver = context.getVersion();
497    if (Utilities.noString(ver))
498      return "https://hl7.org/fhir/STU3";
499    if (ver.startsWith("4.0"))
500      return "https://hl7.org/fhir/R4";
501    if (ver.startsWith("3.0"))
502      return "https://hl7.org/fhir/STU3";
503    if (ver.startsWith("1.0"))
504      return "https://hl7.org/fhir/DSTU2";
505    if (ver.startsWith("1.4"))
506      return "https://hl7.org/fhir/2016May";
507    return "https://build.fhir.org";    
508  }
509
510  private boolean isJson() {
511    for (CodeType f : source.getFormat()) {
512      if (f.getCode().contains("json"))
513        return true;
514    }
515    return false;
516  }
517
518  private boolean isXml() {
519    for (CodeType f : source.getFormat()) {
520      if (f.getCode().contains("xml"))
521        return true;
522    }
523    return false;
524  }
525
526  public PathItemWriter makePathSystem() {
527    PathItemWriter p = dest.path("/");
528    p.summary("System level operations");
529    p.description("System level operations");
530    return p;
531  }
532
533  public PathItemWriter makePathMetadata() {
534    PathItemWriter p = dest.path("/metadata");
535    p.summary("Access to the Server's Capability Statement");
536    p.description("All FHIR Servers return a CapabilityStatement that describes what services they perform");
537    return p;
538  }
539
540  public PathItemWriter makePathRes(CapabilityStatementRestResourceComponent r) {
541    PathItemWriter p = dest.path("/"+r.getType());
542    p.summary("Manager for resources of type "+r.getType());
543    p.description("The Manager for resources of type "+r.getType()+": provides services to manage the collection of all the "+r.getType()+" instances");
544    return p;
545  }
546
547  public PathItemWriter makePathResId(CapabilityStatementRestResourceComponent r) {
548    PathItemWriter p = dest.path("/"+r.getType()+"/{rid}");
549    p.summary("Read/Write/etc resource instance of type "+r.getType());
550    p.description("Access to services to manage the state of a single resource of type "+r.getType());
551    return p;
552  }
553
554  public PathItemWriter makePathResType(CapabilityStatementRestResourceComponent r) {
555    PathItemWriter p = dest.path("/"+r.getType());
556    p.summary("manage the collection of resources of type "+r.getType());
557    p.description("Access to services to manage the collection of all resources of type "+r.getType());
558    return p;
559  }
560
561  public PathItemWriter makePathResHistListType(CapabilityStatementRestResourceComponent r) {
562    PathItemWriter p = dest.path("/"+r.getType()+"/_history");
563    p.summary("Read past versions of resources of type "+r.getType());
564    p.description("Access to previous versions of resourcez of type "+r.getType());
565    return p;
566  }
567
568  public PathItemWriter makePathResHistListId(CapabilityStatementRestResourceComponent r) {
569    PathItemWriter p = dest.path("/"+r.getType()+"/{rid}/_history");
570    p.summary("Read past versions of resource instance of type "+r.getType());
571    p.description("Access to previous versions of a single resource of type "+r.getType());
572    return p;
573  }
574
575  public PathItemWriter makePathResHistId(CapabilityStatementRestResourceComponent r) {
576    PathItemWriter p = dest.path("/"+r.getType()+"/{rid}/_history/{hid}");
577    p.summary("Read a past version of resource instance of type "+r.getType());
578    p.description("Access a to specified previous version of a single resource of type "+r.getType());
579    return p;
580  }
581
582  public PathItemWriter makePathHistListSystem() {
583    PathItemWriter p = dest.path("/_history");
584    p.summary("Read a past version of resource instance of all types");
585    p.description("Access a previous versions of all types");
586    return p;
587  }
588
589  private boolean hasOp(CapabilityStatementRestComponent r, SystemRestfulInteraction opCode) {
590    for (SystemInteractionComponent op : r.getInteraction()) {
591      if (op.getCode() == opCode) 
592        return true;
593    }
594    return false;
595  }
596
597  private boolean hasOp(CapabilityStatementRestResourceComponent r, TypeRestfulInteraction opCode) {
598    for (ResourceInteractionComponent op : r.getInteraction()) {
599      if (op.getCode() == opCode) 
600        return true;
601    }
602    return false;
603  }
604
605  private String url(List<ContactPoint> telecom) {
606    for (ContactPoint cp : telecom) {
607      if (cp.getSystem() == ContactPointSystem.URL)
608        return cp.getValue();
609    }
610    return null;
611  }
612
613
614  private String email(List<ContactPoint> telecom) {
615    for (ContactPoint cp : telecom) {
616      if (cp.getSystem() == ContactPointSystem.EMAIL)
617        return cp.getValue();
618    }
619    return null;
620  }
621
622
623
624}