# 讓 HAPI FHIR 作為 Official Validator 的 tx server ## 前言 今天同事說到,hapi fhir 在驗大資料 (json 超過 3000 行, Bundle entry 超過20 筆)時,hapi fhir 會出現驗證不過的情況,但其實資料都對,出現的錯誤也不是 terminology code 的問題,而是資料不見了!並且,發生率還蠻高的,不得不把 HAPI FHIR 驗證給替換掉 ![資料正確卻找不到的範例](https://hackmd.io/_uploads/SkdtBFCH1g.png) 過程中,我們拿出官方的 Validator 去驗,竟然卻成功了!只不過會有 code 驗不到的問題,於是我們打算用官方 Validator 接 HAPI FHIR 當作 TX server 作為臨時解決方案,過程依然碰壁 這篇將會針對「讓 HAPI FHIR 作為 Official Validator 的 tx server」 :::warning 後續的 HAPI FHIR 都是使用 Docker 建立 ::: ## 讓 HAPI FHIR 成為 TX Server 在我們的觀念內,HAPI FHIR 應該可以直接成為 TX Server,但其實不然,我們發現 Validator 驗證是否為 TX Server 是透過 metadata API 的資料作為根據,好死不死,hapi fhir 沒有寫。 Validator 驗證是否為 TX Server 的步驟推測如下: 1. 傳送 GET metadata?_summary=true 2. 檢查回傳資料的 instantiates 是否有 http://hl7.org/fhir/CapabilityStatement/terminology-server 3. 傳送 GET metadata?_summary=true&mode=terminology 4. 回傳資料的 resourceType 必須為 TerminologyCapabilities ### 傳送 GET metadata?mode=terminology&_summary=true 的內容 以下是官方 tx server 收到 GET metadata?mode=terminology&_summary=true 的內容 ![官方 tx server 的 metadata?mode=terminology](https://hackmd.io/_uploads/S1XAIY0Hyg.png) 不過 HAPI FHIR 從第 2 步開始就失效,因為從官方的文件 「Response Customizing Static CapabilityStatement」 可以發現,HAPI FHIR 的 CapabilityStatement 是自動產生的,可能沒有做額外的處理,而官方也提供了簡單的修改 CapabilityStatment 的文件: 「Customizing the CapabilityStatement」,接下來,我們就來寫更改 CapabilityStatement 的 intercpetor 吧 ### 撰寫更改 CapabilityStatement 的 Intercpetor - 首先,我們先建立 maven 專案 - 引入相關套件 ```xml!= <dependencies> <dependency> <groupId>ca.uhn.hapi.fhir</groupId> <artifactId>hapi-fhir-server</artifactId> <version>7.4.0</version> </dependency> <dependency> <groupId>ca.uhn.hapi.fhir</groupId> <artifactId>hapi-fhir-structures-r4</artifactId> <version>7.4.0</version> </dependency> <dependency> <groupId>ca.uhn.hapi.fhir</groupId> <artifactId>hapi-fhir-validation</artifactId> <version>7.4.0</version> </dependency> <dependency> <groupId>ca.uhn.hapi.fhir</groupId> <artifactId>hapi-fhir-validation-resources-r4</artifactId> <version>7.4.0</version> </dependency> <dependency> <groupId>jakarta.servlet</groupId> <artifactId>jakarta.servlet-api</artifactId> <version>6.0.0</version> <scope>compile</scope> </dependency> </dependencies> ``` - 新增 `CapabilityStatementCustomizer`,程式碼如下 ```java!= package org.piyan; import ca.uhn.fhir.interceptor.api.Hook; import ca.uhn.fhir.interceptor.api.Interceptor; import ca.uhn.fhir.interceptor.api.Pointcut; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.api.server.ResponseDetails; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import org.hl7.fhir.instance.model.api.IBaseConformance; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4.model.CanonicalType; import org.hl7.fhir.r4.model.CapabilityStatement; import org.hl7.fhir.r4.model.TerminologyCapabilities; @Interceptor public class CapabilityStatementCustomizer { @Hook(Pointcut.SERVER_CAPABILITY_STATEMENT_GENERATED) public void customizeCapabilityStatement(IBaseConformance theCapabilityStatement, RequestDetails theRequestDetails) { CapabilityStatement capabilityStatement = (CapabilityStatement) theCapabilityStatement; capabilityStatement.addInstantiates("http://hl7.org/fhir/CapabilityStatement/terminology-server"); } @Hook(Pointcut.SERVER_OUTGOING_RESPONSE) public void onOutgoingResponse( RequestDetails theRequestDetails, ServletRequestDetails theServletRequestDetails, IBaseResource theResource, ResponseDetails theResponseDetails ) { if (theResource instanceof CapabilityStatement) { var params = theRequestDetails.getParameters(); var mode = params.get("mode"); if (mode != null) { var modeValue = mode[0]; if (modeValue.equals("terminology")) { TerminologyCapabilities tc = new TerminologyCapabilities(); CapabilityStatement cs = ((CapabilityStatement) theResource).copy(); tc.setId(cs.getId()); tc.setMeta(cs.getMeta()); tc.setUrl(cs.getUrl()); tc.setVersion(cs.getVersion()); tc.setName(cs.getName()); tc.setStatus(cs.getStatus()); tc.setDate(cs.getDate()); theResponseDetails.setResponseResource(tc); } } } } } ``` - 進行編譯 ### 映射編譯完的 classes 至 HAPI FHIR Docker 在編譯程序完成後,為了讓 HAPI FHIR 可以使用我們的 Interceptor,我們需要把檔案映射至 Docker 內,並修改 HAPI FHIR 的設定,告訴 HAPI FHIR 要額外用什麼 Class。 #### 修改 docker-compose - 在 volumes 加入映射 classes 至 /app/extra-classes 資料夾的設定 ```yaml!= services: hapi-fhir-jpaserver-start: image: hapiproject/hapi:v7.4.0 container_name: hapi-fhir-jpaserver-start restart: on-failure volumes: - ./classes:/app/extra-classes ports: - "8080:8080" ``` #### 修改 application.yaml - 找到 `custom-bean-packages` 設定,把他註解掉,並填入你的 pacakge name,在此為 org.pyian ```yaml!= # comma-separated package names, will be @ComponentScan'ed by Spring to allow for creating custom Spring beans custom-bean-packages: org.pyian ``` - 找到 `custom-interceptor-classes` 設定,把他註解掉,並填入 classes path,在此為 `org.pyian.CapabilityStatementCustomizer` ```yaml!= # comma-separated list of fully qualified interceptor classes. # classes listed here will be fetched from the Spring context when combined with 'custom-bean-packages', # or will be instantiated via reflection using an no-arg contructor; then registered with the server custom-interceptor-classes: org.pyian.CapabilityStatementCustomizer ``` ## 小測試 - 透過 postman 呼叫 metadata,看到 instantiates 就代表成功囉 ![透過 postman 呼叫 metadata](https://hackmd.io/_uploads/r1XCvK0Bke.png) - 透過 postman 呼叫 metadata?mode=terminology,如果看到回傳 TerminologyCapabilities 的 resource 就成功囉! ![透過 postman 呼叫 metadata?mode=terminology](https://hackmd.io/_uploads/r1Xk_FCHyg.png) ## 讓 HAPI FHIR 可以支援 Validator 傳送的 $validate-code API 讓 Validator 成功將 HAPI FHIR 認證為 TX Server 之後,又是另一段的噩夢的開始。 ### HAPI FHIR 不支援 Validator 傳送 $validate-code 的資料 以下是 Validator,傳送 $validate-code 至 HAPI FHIR 的資料: ```json!= { "resourceType": "Parameters", "parameter": [{ "name": "coding", "valueCoding": { "system": "http://snomed.info/sct", "code": "26643006" } }, { "name": "displayLanguage", "valueString": "zh-TW" }, { "name": "default-to-latest-version", "valueBoolean": true }, { "name": "valueSet", "resource": { "resourceType": "ValueSet", "url": "https://twcore.mohw.gov.tw/ig/pas/ValueSet/medication-path-sct-tw--0", "version": "1.0.0", "status": "active", "compose": { "include": [{ "system": "http://snomed.info/sct", "filter": [{ "property": "concept", "op": "is-a", "value": "284009009" }] }] } } }, { "name": "cache-id", "valueId": "bf5478ef-df0a-4eb9-9580-4c1f6b680610" }, { "name": "profile-url", "valueString": "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891" }, { "name": "diagnostics", "valueBoolean": true }] } ``` 傳送到 HAPI FHIR 所收到的錯誤: ```json!= { "resourceType": "OperationOutcome", "issue": [{ "severity": "error", "code": "processing", "diagnostics": "HAPI-0901: Either ValueSet ID or ValueSet identifier or system and code must be provided. Unable to validate." }] } ``` ### 為何不支援? - 在進行分析過後,我們發現 HAPI FHIR 的 <font color="red">$validate-code 必須要有 url 這個參數</font>才可以進行驗證 (有興趣可以看 [JpaResourceDaoValueSet.java:233](https://github.com/hapifhir/hapi-fhir/blob/cdb0a9c480e51e6990dc016e4da19428b2da1c7b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/JpaResourceDaoValueSet.java#L233) 的 code 來推斷) - 而另一個問題則是如果是 [CommonCode](https://hapifhir.io/hapi-fhir/docs/validation/validation_support_modules.html#CommonCodeSystemsTerminologyService) 則會遇到給予 url 一樣無法驗證的問題 ### 撰寫 $validate-code pre-process 和 post-process 在分析問題後,我們得出了可能的解法 1. 遇到一般的 Value Set,給予 url 理論上可以正常驗證 (利用 pre-process 更改 request body) 2. 遇到可能為 Common Code,就使用 CommonCodeSystemsTerminologyService - 首先,我們要建立 HttpServletRequestWrapper,讓 request body 可以被覆用 ```java!= package org.pyian; import jakarta.servlet.ReadListener; import org.apache.commons.io.IOUtils; import java.io.*; import java.nio.charset.StandardCharsets; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequestWrapper; import jakarta.servlet.ServletInputStream; public class ModifiableHttpServletRequest extends HttpServletRequestWrapper { private byte[] body; public ModifiableHttpServletRequest(HttpServletRequest request) throws IOException { super(request); // 讀取原始的 request body body = IOUtils.toString(request.getInputStream(), StandardCharsets.UTF_8).getBytes(); } @Override public ServletInputStream getInputStream() throws IOException { final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body); return new ServletInputStream() { @Override public int read() throws IOException { return byteArrayInputStream.read(); } @Override public boolean isFinished() { return byteArrayInputStream.available() == 0; } @Override public boolean isReady() { return true; } @Override public void setReadListener(ReadListener readListener) { throw new UnsupportedOperationException(); } }; } @Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(getInputStream())); } public void setBody(byte[] newBody) { this.body = newBody; } public byte[] getBody() { return this.body; } } ``` - 再來建立我們的核心 intercpetor ```java!= package org.pyian; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.support.ConceptValidationOptions; import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.context.support.ValidationSupportContext; import ca.uhn.fhir.fhirpath.IFhirPath; import ca.uhn.fhir.interceptor.api.Hook; import ca.uhn.fhir.interceptor.api.Pointcut; import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.rest.api.RequestTypeEnum; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.api.server.ResponseDetails; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import jakarta.servlet.http.HttpServletRequest; import org.hl7.fhir.common.hapi.validation.support.CommonCodeSystemsTerminologyService; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4.model.*; import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.Optional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ValidateCodeOpCustomizer { private static final Logger logger = LoggerFactory.getLogger(ValidateCodeOpCustomizer.class); @Hook(Pointcut.SERVER_INCOMING_REQUEST_PRE_HANDLER_SELECTED) public void onIncomingRequest(ServletRequestDetails theRequestDetails, HttpServletRequest theServletRequest) throws IOException { if (theRequestDetails == null) return; String incomingResourceName = theRequestDetails.getResourceName(); String incomingOperation = theRequestDetails.getOperation(); if (incomingResourceName == null || incomingOperation == null) return; if (incomingResourceName.equals("ValueSet") && incomingOperation.equals("$validate-code") && theRequestDetails.getRequestType() == RequestTypeEnum.POST) { valueSetValidateCodePreProcess(theRequestDetails, theServletRequest); } else if (incomingResourceName.equals("CodeSystem") && incomingOperation.equals("$validate-code") && theRequestDetails.getRequestType() == RequestTypeEnum.POST) { codeSystemValidateCodePreProcess(theRequestDetails, theServletRequest); } } private static void valueSetValidateCodePreProcess(ServletRequestDetails theRequestDetails, HttpServletRequest theServletRequest) throws IOException { String contentType = theServletRequest.getContentType(); if (contentType.contains("application/json") || contentType.contains("application/fhir+json")) { logger.info("Doing custom value set $validate-code pre-process"); // 創建可修改的請求包裝器 ModifiableHttpServletRequest modifiableRequest = new ModifiableHttpServletRequest(theServletRequest); byte[] requestBody = modifiableRequest.getBody(); if (requestBody == null || requestBody.length == 0) { logger.info("Request body is empty, do nothing"); return; } // 解析原始請求 IParser parser = theRequestDetails.getFhirContext().newJsonParser(); Parameters requestParams = (Parameters) parser.parseResource(new ByteArrayInputStream(requestBody)); IFhirPath fhirPath = theRequestDetails.getFhirContext().newFhirPath(); Optional<ValueSet> userValueSet = fhirPath.evaluateFirst(requestParams, "Parameters.parameter.where(name='valueSet').resource", ValueSet.class); Optional<UriType> userUrl = fhirPath.evaluateFirst(requestParams, "Parameters.parameter.where(name='url').value", UriType.class); Optional<Coding> userCoding = fhirPath.evaluateFirst(requestParams, "Parameters.parameter.where(name='coding').value", Coding.class); Optional<CodeType> userCode = fhirPath.evaluateFirst(requestParams, "Parameters.parameter.where(name='code').value", CodeType.class); if (userValueSet.isPresent() && userUrl.isEmpty() && (userCoding.isPresent() || userCode.isPresent())) { logger.info("Missing url in request, try to append it automatically"); Parameters newParams = new Parameters(); userCoding.ifPresent(coding -> newParams.addParameter(new Parameters.ParametersParameterComponent().setName("coding").setValue(coding))); userCode.ifPresent(code -> newParams.addParameter(new Parameters.ParametersParameterComponent().setName("code").setValue(code))); String url = userValueSet.get().getUrl(); if (url == null) { url = "nope"; } url = url.replaceAll("--\\d+$", ""); newParams.addParameter("url", new UriType(url)); if (!url.startsWith("urn:uuid") && !url.startsWith("urn:oid") && !url.equals(CommonCodeSystemsTerminologyService.LANGUAGES_VALUESET_URL) && !url.equals(CommonCodeSystemsTerminologyService.MIMETYPES_VALUESET_URL) && !url.equals(CommonCodeSystemsTerminologyService.CURRENCIES_VALUESET_URL) && !url.equals(CommonCodeSystemsTerminologyService.UCUM_VALUESET_URL) && !url.equals(CommonCodeSystemsTerminologyService.ALL_LANGUAGES_VALUESET_URL) && !url.equals(CommonCodeSystemsTerminologyService.USPS_VALUESET_URL) && !isCommonCodeSystemInCompose(theRequestDetails.getFhirContext(), requestParams) ) { // do nothing } else { newParams.addParameter(new Parameters.ParametersParameterComponent().setName("valueSet").setResource(userValueSet.get())); } // 將修改後的參數轉換回 JSON String modifiedJson = parser.encodeResourceToString(newParams); modifiableRequest.setBody(modifiedJson.getBytes()); theRequestDetails.setServletRequest(modifiableRequest); } else { theRequestDetails.setServletRequest(modifiableRequest); } } } private static boolean isCommonCodeSystemInCompose(FhirContext ctx, Parameters theRequestParams) { String whereCommonCode = String.format("Parameters.parameter.where(name='valueSet').resource.compose.include.where(system='%s' or system='%s' or system='%s' or system='%s' or system='%s')", CommonCodeSystemsTerminologyService.LANGUAGES_CODESYSTEM_URL, CommonCodeSystemsTerminologyService.MIMETYPES_CODESYSTEM_URL, CommonCodeSystemsTerminologyService.CURRENCIES_CODESYSTEM_URL, CommonCodeSystemsTerminologyService.UCUM_CODESYSTEM_URL, CommonCodeSystemsTerminologyService.USPS_CODESYSTEM_URL ); Optional<ValueSet.ConceptSetComponent> systemUrl = ctx.newFhirPath().evaluateFirst(theRequestParams, whereCommonCode, ValueSet.ConceptSetComponent.class); return systemUrl.isPresent(); } private static void codeSystemValidateCodePreProcess(ServletRequestDetails theRequestDetails, HttpServletRequest theServletRequest) throws IOException { String contentType = theServletRequest.getContentType(); if (contentType.contains("application/json") || contentType.contains("application/fhir+json")) { logger.info("Doing custom code system $validate-code pre-process"); // 創建可修改的請求包裝器 ModifiableHttpServletRequest modifiableRequest = new ModifiableHttpServletRequest(theServletRequest); byte[] requestBody = modifiableRequest.getBody(); if (requestBody == null || requestBody.length == 0) { logger.info("Request body is empty, do nothing"); return; } // 解析原始請求 IParser parser = theRequestDetails.getFhirContext().newJsonParser(); Parameters requestParams = (Parameters) parser.parseResource(new ByteArrayInputStream(requestBody)); IFhirPath fhirPath = theRequestDetails.getFhirContext().newFhirPath(); Optional<Coding> userCoding = fhirPath.evaluateFirst(requestParams, "Parameters.parameter.where(name='coding').value", Coding.class); Optional<UriType> userUrl = fhirPath.evaluateFirst(requestParams, "Parameters.parameter.where(name='url').value", UriType.class); if (userCoding.isPresent() && userUrl.isEmpty()) { logger.info("Missing url in request, try to append it automatically"); String url = userCoding.get().getSystem(); url = url.replaceAll("--\\d+$", ""); requestParams.addParameter("url", new UriType(url)); // 將修改後的參數轉換回 JSON String modifiedJson = parser.encodeResourceToString(requestParams); modifiableRequest.setBody(modifiedJson.getBytes()); theRequestDetails.setServletRequest(modifiableRequest); } else { theRequestDetails.setServletRequest(modifiableRequest); } } } @Hook(Pointcut.SERVER_OUTGOING_RESPONSE) public void onOutgoingResponse( RequestDetails theRequestDetails, ServletRequestDetails theServletRequestDetails, IBaseResource theResource, ResponseDetails theResponseDetails ) { String incomingResourceName = theRequestDetails.getResourceName(); String incomingOperation = theRequestDetails.getOperation(); if (incomingResourceName == null || incomingOperation == null) return; if (incomingResourceName.equals("ValueSet") && incomingOperation.equals("$validate-code") && theRequestDetails.getRequestType() == RequestTypeEnum.POST) { FhirContext ctx = FhirContext.forR4(); IParser parser = ctx.newJsonParser(); parser.setPrettyPrint(true); IFhirPath fhirPath = ctx.newFhirPath(); Optional<Parameters.ParametersParameterComponent> result = fhirPath.evaluateFirst(theResource, "Parameters.parameter.where(name='result')", Parameters.ParametersParameterComponent.class); if (result.isPresent()) { Parameters.ParametersParameterComponent resultParam = result.get(); boolean resultValue = Boolean.parseBoolean(String.valueOf(resultParam.getValue())); if (!resultValue) { Parameters requestParams = (Parameters) theRequestDetails.getResource(); Optional<Parameters.ParametersParameterComponent> userValueSetParam = fhirPath.evaluateFirst(requestParams, "Parameters.parameter.where(name='valueSet')", Parameters.ParametersParameterComponent.class); if (userValueSetParam.isPresent()) { Optional<CodeType> theCode = fhirPath.evaluateFirst(requestParams, "Parameters.parameter.where(name='code').value", CodeType.class); boolean validateSingleCodeResult = doValidateSingleCode(theRequestDetails.getFhirContext(), theCode, (ValueSet) userValueSetParam.get().getResource()); if (validateSingleCodeResult) { Parameters goodParams = new Parameters(); goodParams.addParameter().setName("result").setValue(new BooleanType(true)); goodParams.addParameter().setName("message").setValue(new StringType("Code validated by common code system terminology service")); theResponseDetails.setResponseResource(goodParams); } } } } } } private boolean doValidateSingleCode(FhirContext ctx, Optional<CodeType> code, ValueSet valueSet) { IFhirPath fhirPath = ctx.newFhirPath(); // 正常進到這階段的 code 幾乎都是 common code String whereCommonCode = String.format("ValueSet.compose.include.where(system='%s' or system='%s' or system='%s' or system='%s' or system='%s').system", CommonCodeSystemsTerminologyService.LANGUAGES_CODESYSTEM_URL, CommonCodeSystemsTerminologyService.MIMETYPES_CODESYSTEM_URL, CommonCodeSystemsTerminologyService.CURRENCIES_CODESYSTEM_URL, CommonCodeSystemsTerminologyService.UCUM_CODESYSTEM_URL, CommonCodeSystemsTerminologyService.USPS_CODESYSTEM_URL ); Optional<UriType> theSystem = fhirPath.evaluateFirst(valueSet, whereCommonCode, UriType.class); if (!code.isPresent() || !theSystem.isPresent()) { return false; } CommonCodeSystemsTerminologyService service = new CommonCodeSystemsTerminologyService(ctx); IValidationSupport.CodeValidationResult r = service.validateCode( new ValidationSupportContext(ctx.getValidationSupport()), new ConceptValidationOptions().setInferSystem(true), theSystem.get().getValue(), code.get().getCode(), null, null ); if (r!=null) { return r.isOk(); } return false; } } ``` - 進行編譯 - 編譯後的步驟與 [映射編譯完的 classes 至 HAPI FHIR Docker 相同](#映射編譯完的-classes-至-HAPI-FHIR-Docker),`custom-interceptor-classes` 請記得加入 org.pyian.ValidateCodeOpCustomizer ## 測試 - 使用官方 validate_cli,並把 tx server 指向自己的 hapi fhir ```bash!= java -jar ./validator_cli.jar -version 4.0.1 PATH\Bubdle_1105.json -tx http://127.0.0.1:8080/fhir ``` - 正常會看到連接 tx server 的 log ![validator cli connect to hapi tx server log](https://hackmd.io/_uploads/SyWLtKCrJl.png) ## 額外內容 ### 官方 validator 發出的 request body #### 一般的 value set ```json!= { "resourceType": "Parameters", "parameter": [{ "name": "coding", "valueCoding": { "system": "http://snomed.info/sct", "code": "26643006" } }, { "name": "displayLanguage", "valueString": "zh-TW" }, { "name": "default-to-latest-version", "valueBoolean": true }, { "name": "valueSet", "resource": { "resourceType": "ValueSet", "url": "https://twcore.mohw.gov.tw/ig/pas/ValueSet/medication-path-sct-tw--0", "version": "1.0.0", "status": "active", "compose": { "include": [{ "system": "http://snomed.info/sct", "filter": [{ "property": "concept", "op": "is-a", "value": "284009009" }] }] } } }, { "name": "cache-id", "valueId": "bf5478ef-df0a-4eb9-9580-4c1f6b680610" }, { "name": "profile-url", "valueString": "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891" }, { "name": "diagnostics", "valueBoolean": true }] } ``` #### Common Code ```json!= { "resourceType": "Parameters", "parameter": [{ "name": "inferSystem", "valueBoolean": true }, { "name": "code", "valueCode": "application/pdf" }, { "name": "displayLanguage", "valueString": "zh-TW" }, { "name": "default-to-latest-version", "valueBoolean": true }, { "name": "valueSet", "resource": { "resourceType": "ValueSet", "url": "urn:uuid:d65a17b5-388d-4da8-b383-12fcc08ec902", "status": "active", "compose": { "include": [{ "system": "urn:ietf:bcp:13" }] } } }, { "name": "cache-id", "valueId": "bf5478ef-df0a-4eb9-9580-4c1f6b680610" }, { "name": "profile-url", "valueString": "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891" }, { "name": "diagnostics", "valueBoolean": true }] } ``` #### Common Code (附帶 value set) ```json!= { "resourceType": "Parameters", "parameter": [ { "name": "inferSystem", "valueBoolean": true }, { "name": "code", "valueCode": "application/pdf" }, { "name": "displayLanguage", "valueString": "zh-TW" }, { "name": "default-to-latest-version", "valueBoolean": true }, { "name": "valueSet", "resource": { "resourceType": "ValueSet", "id": "mimetypes", "meta": { "lastUpdated": "2019-11-01T09:29:23.356+11:00", "profile": [ "http://hl7.org/fhir/StructureDefinition/shareablevalueset" ] }, "extension": [ { "url": "http://hl7.org/fhir/StructureDefinition/structuredefinition-wg", "valueCode": "fhir" }, { "url": "http://hl7.org/fhir/StructureDefinition/structuredefinition-standards-status", "valueCode": "normative" }, { "url": "http://hl7.org/fhir/StructureDefinition/structuredefinition-normative-version", "valueCode": "4.0.0" }, { "url": "http://hl7.org/fhir/StructureDefinition/structuredefinition-fmm", "valueInteger": 5 } ], "url": "http://hl7.org/fhir/ValueSet/mimetypes", "identifier": [ { "system": "urn:ietf:rfc:3986", "value": "urn:oid:2.16.840.1.113883.4.642.3.1024" } ], "version": "4.0.1", "name": "Mime Types", "title": "MimeType", "status": "active", "experimental": false, "date": "2019-11-01T09:29:23+11:00", "publisher": "HL7 International - FHIR-Infrastructure", "contact": [ { "telecom": [ { "system": "url", "value": "http://hl7.org/fhir" } ] } ], "description": "This value set includes all possible codes from BCP-13 (http://tools.ietf.org/html/bcp13)", "compose": { "include": [ { "system": "urn:ietf:bcp:13" } ] } } }, { "name": "cache-id", "valueId": "e38fc2a2-5b0d-4724-962c-3b30b2484cfc" }, { "name": "profile-url", "valueString": "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891" }, { "name": "diagnostics", "valueBoolean": true } ] } ``` ## Support Me 文件創作花費了很多心血製作,如果你覺得很有幫助 不妨贊助我一下喝杯咖啡唄,[Support Me](https://portaly.cc/Li070/support)