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}