# 讓 HAPI FHIR 作為 Official Validator 的 tx server
## 前言
今天同事說到,hapi fhir 在驗大資料 (json 超過 3000 行, Bundle entry 超過20 筆)時,hapi fhir 會出現驗證不過的情況,但其實資料都對,出現的錯誤也不是 terminology code 的問題,而是資料不見了!並且,發生率還蠻高的,不得不把 HAPI FHIR 驗證給替換掉

過程中,我們拿出官方的 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 的內容

不過 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?mode=terminology,如果看到回傳 TerminologyCapabilities 的 resource 就成功囉!

## 讓 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 發出的 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)