Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<Set>
<Headers>
<Header name="Access-Control-Allow-Origin">{request.header.origin}</Header>
<Header name="Access-Control-Allow-Headers">origin, x-requested-with, x-correlation-id, accept, content-type, nhsd-session-urid, nhsd-end-user-organisation-ods, nhsd-ers-business-function, nhsd-ers-on-behalf-of-user-id, authorization, nhsd-ers-comm-rule-org, nhsd-ers-file-name, nhsd-ers-referral-id, if-match</Header>
<Header name="Access-Control-Allow-Headers">origin, x-requested-with, x-correlation-id, accept, content-type, nhsd-session-urid, nhsd-end-user-organisation-ods, nhsd-ers-business-function, nhsd-ers-on-behalf-of-user-id, authorization, nhsd-ers-comm-rule-org, nhsd-ers-file-name, nhsd-ers-file-size, nhsd-ers-file-mime-type, nhsd-ers-referral-id, if-match</Header>
<Header name="Access-Control-Expose-Headers">x-correlation-id, x-request-id, content-type, Location, ETag, Content-Disposition, Content-Length, Cache-Control</Header>
<Header name="Access-Control-Max-Age">3628800</Header>
<Header name="Access-Control-Allow-Methods">GET, PUT, POST, DELETE, HEAD</Header>
Expand Down
2 changes: 1 addition & 1 deletion sandbox/src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const init = async () => {
routes: {
cors: {
origin: ["*"],
headers: ['origin', 'x-requested-with', 'x-correlation-id', 'accept', 'content-type', 'nhsd-session-urid', 'nhsd-end-user-organisation-ods', 'nhsd-ers-business-function', 'authorization', 'nhsd-ers-comm-rule-org', 'nhsd-ers-file-name', 'nhsd-ers-referral-id', 'if-match', 'nhsd-ers-on-behalf-of-user-id'],
headers: ['origin', 'x-requested-with', 'x-correlation-id', 'accept', 'content-type', 'nhsd-session-urid', 'nhsd-end-user-organisation-ods', 'nhsd-ers-business-function', 'authorization', 'nhsd-ers-comm-rule-org', 'nhsd-ers-file-name', 'nhsd-ers-file-size', 'nhsd-ers-file-mime-type', 'nhsd-ers-referral-id', 'if-match', 'nhsd-ers-on-behalf-of-user-id'],
exposedHeaders: ['x-correlation-id', 'x-request-id', 'content-type', 'Location', 'ETag', 'Content-Disposition', 'Content-Length', 'Cache-Control'],
maxAge: 3628800,
credentials: false
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
A simple test upload file - this is only used by the sandbox integration tests and it's not accessible to the 'try it' UI in Bloomreach

Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"id": "d497bbe3-f88b-45f1-b3d4-9c563e4c0f5f",
"resourceType": "Binary",
"meta": {
"lastUpdated": "2026-09-04T09:40:04.656Z"
},
"identifier": [
{
"system": "https://fhir.nhs.uk/Id/ers-binary-id",
"value": "d497bbe3-f88b-45f1-b3d4-9c563e4c0f5f"
}
],
"contentType": "text/plain"
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"resourceType": "OperationOutcome",
"meta": {
"lastUpdated": "2026-09-04T10:15:30.000Z",
"profile": [
"https://www.hl7.org/fhir/R4/operationoutcome.html"
]
},
"issue": [
{
"severity": "error",
"code": "required",
"details": {
"coding": [
{
"system": "https://fhir.nhs.uk/CodeSystem/ers-error-codes",
"code": "MISSING_VALUE"
}
]
},
"diagnostics": "Missing required header: NHSD-eRS-File-Name"
}
]
}
2 changes: 2 additions & 0 deletions sandbox/src/routes/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ const retrieveHealthcareService = require('./r4/retrieveHealthcareService')
const searchForHealthcareServices = require('./r4/searchForHealthcareServices')
const searchServiceRequest = require('./r4/searchServiceRequest')
const retrieveAttachmentR4 = require('./r4/retrieveAttachment')
const uploadFileToDocumentStoreR4 = require('./r4/uploadFileToDocumentStore')

/**
* Services
Expand Down Expand Up @@ -89,6 +90,7 @@ const routes = [].concat(
searchServiceRequest,
createAdviceAndGuidance,
retrieveAttachmentR4,
uploadFileToDocumentStoreR4,
objectStore
)

Expand Down
47 changes: 46 additions & 1 deletion sandbox/src/routes/r4/services/mockResponseProvider.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,31 @@
const OBJECT_STORE_FILE_ID = 'd497bbe3-f88b-45f1-b3d4-9c563e4c0f5f'
const DEFAULT_UPLOAD_FILENAME = 'upload.bin'

function stripTrailingSlash(url) {
return url.replace(/\/+$/, '')
}

function getObjectStoreLocation(request) {
const explicitBaseUrl = request.headers['x-ers-sandbox-baseurl']
const baseUrl = typeof explicitBaseUrl === 'string' && explicitBaseUrl.trim()
? stripTrailingSlash(explicitBaseUrl.trim())
: stripTrailingSlash(request.server.info.uri)

return `${baseUrl}/ObjectStore/${OBJECT_STORE_FILE_ID}`
}

function createContentDisposition(fileNameHeaderValue) {
const inputFileName = String(fileNameHeaderValue || '').replace(/[\r\n]/g, '').trim()
const utf8Filename = inputFileName || DEFAULT_UPLOAD_FILENAME
const asciiFallback = utf8Filename
.replace(/[^\x20-\x7E]/g, '_')
.replace(/["\\]/g, '_')
const safeFallback = asciiFallback || DEFAULT_UPLOAD_FILENAME
const encodedFilename = encodeURIComponent(utf8Filename)

return `attachment; filename="${safeFallback}"; filename*=UTF-8''${encodedFilename}`
}

module.exports = {

getExampleResponseForRetrieveBusinessFunctions: function () {
Expand Down Expand Up @@ -43,7 +71,7 @@ module.exports = {
getExampleResponseForSearchServiceRequest: function (request) {
let ubrn;
const identifier = request.query.identifier;

if (identifier.includes('|')) {
ubrn = identifier.split('|')[1]
}
Expand All @@ -64,6 +92,23 @@ module.exports = {
return 'r4/searchServiceRequest/responses/ResponseExampleEmpty.json'
}

return null
},

getExampleResponseForUploadFileToDocumentStore: function (request) {
const filename = request.headers['nhsd-ers-file-name']
const fileSize = request.headers['nhsd-ers-file-size']
const fileMimeType = request.headers['nhsd-ers-file-mime-type']

if (filename && fileSize && fileMimeType) {
return {
responsePath: 'r4/uploadFileToDocumentStore/responses/BinaryResource.json',
responseCode: 200,
location: getObjectStoreLocation(request),
contentDisposition: createContentDisposition(filename)
}
}

return null
}
}
33 changes: 33 additions & 0 deletions sandbox/src/routes/r4/uploadFileToDocumentStore.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const mockResponseProvider = require('./services/mockResponseProvider')
const validationUtils = require('../common/validationUtils')

module.exports = [
/**
* Sandbox implementation for uploadFileToDocumentStore A039 (R4) endpoint
*/
{
method: 'POST',
path: '/FHIR/R4/Binary',
handler: (request, h) => {
const allowedBusinessFunctions = ['REFERRING_CLINICIAN', 'REFERRING_CLINICIAN_ADMIN', 'SERVICE_PROVIDER_CLINICIAN', 'SERVICE_PROVIDER_CLINICIAN_ADMIN']

const validationResult = validationUtils.validateBusinessFunction(request, h, allowedBusinessFunctions)
if (validationResult) {
return validationResult
}

const exampleResponse = mockResponseProvider.getExampleResponseForUploadFileToDocumentStore(request)
if (exampleResponse) {
const { responsePath, responseCode, location, contentDisposition } = exampleResponse
return h.file(responsePath, { etagMethod: false })
.code(responseCode)
.type('application/fhir+json')
.header('Location', location)
.header('Content-Disposition', contentDisposition)
}

return h.file('r4/uploadFileToDocumentStore/responses/OperationOutcome-422.json').code(422).type('application/fhir+json')
}
}
]

Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
allOf:
- $ref: './NHSDigital-OperationOutcome.yaml'
- type: object
properties:
meta:
type: object
required:
- lastUpdated
- profile
properties:
profile:
type: array
minItems: 1
items:
type: string
enum:
- 'https://www.hl7.org/fhir/R4/operationoutcome.html'

Loading