|
@@ -1,10 +1,10 @@ |
|
|
import { constants } from 'http2'; |
|
|
import { constants } from 'http2'; |
|
|
import Negotiator from 'negotiator'; |
|
|
|
|
|
import * as v from 'valibot'; |
|
|
import * as v from 'valibot'; |
|
|
import {Middleware} from './core'; |
|
|
import {Middleware} from './core'; |
|
|
import { getBody, getMethod, getUrl } from './utils'; |
|
|
|
|
|
|
|
|
import {getBody, getDeserializerObjects, getMethod, getUrl} from './utils'; |
|
|
|
|
|
import {IncomingMessage, ServerResponse} from 'http'; |
|
|
|
|
|
|
|
|
export const handleHasMethodAndUrl: Middleware = ({}) => (req, res) => { |
|
|
|
|
|
|
|
|
export const handleHasMethodAndUrl: Middleware = ({}) => (req: IncomingMessage, res: ServerResponse) => { |
|
|
if (!req.method) { |
|
|
if (!req.method) { |
|
|
res.writeHead(constants.HTTP_STATUS_METHOD_NOT_ALLOWED, { |
|
|
res.writeHead(constants.HTTP_STATUS_METHOD_NOT_ALLOWED, { |
|
|
'Allow': 'HEAD,GET,POST,PUT,PATCH,DELETE' // TODO check with resources on allowed methods |
|
|
'Allow': 'HEAD,GET,POST,PUT,PATCH,DELETE' // TODO check with resources on allowed methods |
|
@@ -31,56 +31,36 @@ export const handleHasMethodAndUrl: Middleware = ({}) => (req, res) => { |
|
|
export const handleGetRoot: Middleware = ({ |
|
|
export const handleGetRoot: Middleware = ({ |
|
|
appState, |
|
|
appState, |
|
|
appParams, |
|
|
appParams, |
|
|
serverParams |
|
|
|
|
|
}) => (req, res) => { |
|
|
|
|
|
const method = getMethod(req); |
|
|
|
|
|
|
|
|
responseBodySerializerPair, |
|
|
|
|
|
serverParams, |
|
|
|
|
|
responseMediaType, |
|
|
|
|
|
method, |
|
|
|
|
|
url, |
|
|
|
|
|
}) => (_req: IncomingMessage, res: ServerResponse) => { |
|
|
if (method !== 'GET') { |
|
|
if (method !== 'GET') { |
|
|
return { |
|
|
return { |
|
|
handled: false |
|
|
handled: false |
|
|
}; |
|
|
}; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const baseUrl = serverParams.baseUrl ?? ''; |
|
|
|
|
|
const { url } = getUrl(req, baseUrl); |
|
|
|
|
|
if (url !== '/') { |
|
|
if (url !== '/') { |
|
|
return { |
|
|
return { |
|
|
handled: false |
|
|
handled: false |
|
|
}; |
|
|
}; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const negotiator = new Negotiator(req); |
|
|
|
|
|
const availableMediaTypes = Array.from(appState.serializers.keys()); |
|
|
|
|
|
const theMediaType = negotiator.mediaType(availableMediaTypes); |
|
|
|
|
|
if (typeof theMediaType === 'undefined') { |
|
|
|
|
|
res.statusCode = constants.HTTP_STATUS_NOT_ACCEPTABLE; |
|
|
|
|
|
res.end(); |
|
|
|
|
|
return { |
|
|
|
|
|
handled: true |
|
|
|
|
|
}; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const theSerializerPair = appState.serializers.get(theMediaType); |
|
|
|
|
|
if (typeof theSerializerPair === 'undefined') { |
|
|
|
|
|
res.statusCode = constants.HTTP_STATUS_NOT_ACCEPTABLE; |
|
|
|
|
|
res.end(); |
|
|
|
|
|
return { |
|
|
|
|
|
handled: true |
|
|
|
|
|
}; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const singleResDatum = { |
|
|
const singleResDatum = { |
|
|
name: appParams.name |
|
|
name: appParams.name |
|
|
}; |
|
|
}; |
|
|
const theFormatted = theSerializerPair.serialize(singleResDatum); |
|
|
|
|
|
|
|
|
const theFormatted = responseBodySerializerPair.serialize(singleResDatum); |
|
|
res.writeHead(constants.HTTP_STATUS_OK, { |
|
|
res.writeHead(constants.HTTP_STATUS_OK, { |
|
|
'Content-Type': theMediaType, |
|
|
|
|
|
|
|
|
'Content-Type': responseMediaType, |
|
|
// we are using custom headers for links because the standard Link header |
|
|
// we are using custom headers for links because the standard Link header |
|
|
// is referring to the document metadata (e.g. author, next page, etc) |
|
|
// is referring to the document metadata (e.g. author, next page, etc) |
|
|
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Link |
|
|
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Link |
|
|
'X-Resource-Link': Array.from(appState.resources) |
|
|
'X-Resource-Link': Array.from(appState.resources) |
|
|
.map((r) => |
|
|
.map((r) => |
|
|
`<${baseUrl}/${r.routeName}>; name="${r.collectionName}"`, |
|
|
|
|
|
// TODO add host? |
|
|
|
|
|
|
|
|
`<${serverParams.baseUrl}/${r.routeName}>; name="${r.collectionName}"`, |
|
|
) |
|
|
) |
|
|
.join(', ') |
|
|
.join(', ') |
|
|
}); |
|
|
}); |
|
@@ -92,8 +72,10 @@ export const handleGetRoot: Middleware = ({ |
|
|
|
|
|
|
|
|
export const handleGetCollection: Middleware = ({ |
|
|
export const handleGetCollection: Middleware = ({ |
|
|
appState, |
|
|
appState, |
|
|
serverParams |
|
|
|
|
|
}) => async (req, res) => { |
|
|
|
|
|
|
|
|
serverParams, |
|
|
|
|
|
responseBodySerializerPair, |
|
|
|
|
|
responseMediaType, |
|
|
|
|
|
}) => async (req: IncomingMessage, res: ServerResponse) => { |
|
|
const method = getMethod(req); |
|
|
const method = getMethod(req); |
|
|
if (method !== 'GET') { |
|
|
if (method !== 'GET') { |
|
|
return { |
|
|
return { |
|
@@ -118,33 +100,13 @@ export const handleGetCollection: Middleware = ({ |
|
|
}; |
|
|
}; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const negotiator = new Negotiator(req); |
|
|
|
|
|
const availableMediaTypes = Array.from(appState.serializers.keys()); |
|
|
|
|
|
const theMediaType = negotiator.mediaType(availableMediaTypes); |
|
|
|
|
|
if (typeof theMediaType === 'undefined') { |
|
|
|
|
|
res.statusCode = constants.HTTP_STATUS_NOT_ACCEPTABLE; |
|
|
|
|
|
res.end(); |
|
|
|
|
|
return { |
|
|
|
|
|
handled: true |
|
|
|
|
|
}; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const theSerializerPair = appState.serializers.get(theMediaType); |
|
|
|
|
|
if (typeof theSerializerPair === 'undefined') { |
|
|
|
|
|
res.statusCode = constants.HTTP_STATUS_NOT_ACCEPTABLE; |
|
|
|
|
|
res.end(); |
|
|
|
|
|
return { |
|
|
|
|
|
handled: true |
|
|
|
|
|
}; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
try { |
|
|
await theResource.dataSource.initialize(); |
|
|
await theResource.dataSource.initialize(); |
|
|
const resData = await theResource.dataSource.getMultiple(); // TODO paginated responses per resource |
|
|
const resData = await theResource.dataSource.getMultiple(); // TODO paginated responses per resource |
|
|
const theFormatted = theSerializerPair.serialize(resData); |
|
|
|
|
|
|
|
|
const theFormatted = responseBodySerializerPair.serialize(resData); |
|
|
|
|
|
|
|
|
res.writeHead(constants.HTTP_STATUS_OK, { |
|
|
res.writeHead(constants.HTTP_STATUS_OK, { |
|
|
'Content-Type': theMediaType, |
|
|
|
|
|
|
|
|
'Content-Type': responseMediaType, |
|
|
'X-Resource-Total-Item-Count': resData.length |
|
|
'X-Resource-Total-Item-Count': resData.length |
|
|
}); |
|
|
}); |
|
|
res.end(theFormatted); |
|
|
res.end(theFormatted); |
|
@@ -160,8 +122,10 @@ export const handleGetCollection: Middleware = ({ |
|
|
|
|
|
|
|
|
export const handleGetItem: Middleware = ({ |
|
|
export const handleGetItem: Middleware = ({ |
|
|
appState, |
|
|
appState, |
|
|
serverParams |
|
|
|
|
|
}) => async (req, res) => { |
|
|
|
|
|
|
|
|
serverParams, |
|
|
|
|
|
responseBodySerializerPair, |
|
|
|
|
|
responseMediaType, |
|
|
|
|
|
}) => async (req: IncomingMessage, res: ServerResponse) => { |
|
|
const method = getMethod(req); |
|
|
const method = getMethod(req); |
|
|
if (method !== 'GET') { |
|
|
if (method !== 'GET') { |
|
|
return { |
|
|
return { |
|
@@ -186,32 +150,12 @@ export const handleGetItem: Middleware = ({ |
|
|
}; |
|
|
}; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const negotiator = new Negotiator(req); |
|
|
|
|
|
const availableMediaTypes = Array.from(appState.serializers.keys()); |
|
|
|
|
|
const theMediaType = negotiator.mediaType(availableMediaTypes); |
|
|
|
|
|
if (typeof theMediaType === 'undefined') { |
|
|
|
|
|
res.statusCode = constants.HTTP_STATUS_NOT_ACCEPTABLE; |
|
|
|
|
|
res.end(); |
|
|
|
|
|
return { |
|
|
|
|
|
handled: true |
|
|
|
|
|
}; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const theSerializerPair = appState.serializers.get(theMediaType); |
|
|
|
|
|
if (typeof theSerializerPair === 'undefined') { |
|
|
|
|
|
res.statusCode = constants.HTTP_STATUS_NOT_ACCEPTABLE; |
|
|
|
|
|
res.end(); |
|
|
|
|
|
return { |
|
|
|
|
|
handled: true |
|
|
|
|
|
}; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
try { |
|
|
await theResource.dataSource.initialize(); |
|
|
await theResource.dataSource.initialize(); |
|
|
const singleResDatum = await theResource.dataSource.getSingle(mainResourceId); |
|
|
const singleResDatum = await theResource.dataSource.getSingle(mainResourceId); |
|
|
if (singleResDatum) { |
|
|
if (singleResDatum) { |
|
|
const theFormatted = theSerializerPair.serialize(singleResDatum); |
|
|
|
|
|
res.writeHead(constants.HTTP_STATUS_OK, {'Content-Type': theMediaType}); |
|
|
|
|
|
|
|
|
const theFormatted = responseBodySerializerPair.serialize(singleResDatum); |
|
|
|
|
|
res.writeHead(constants.HTTP_STATUS_OK, {'Content-Type': responseMediaType}); |
|
|
res.end(theFormatted); |
|
|
res.end(theFormatted); |
|
|
return { |
|
|
return { |
|
|
handled: true |
|
|
handled: true |
|
@@ -236,18 +180,15 @@ export const handleGetItem: Middleware = ({ |
|
|
|
|
|
|
|
|
export const handleDeleteItem: Middleware = ({ |
|
|
export const handleDeleteItem: Middleware = ({ |
|
|
appState, |
|
|
appState, |
|
|
serverParams |
|
|
|
|
|
}) => async (req, res) => { |
|
|
|
|
|
const method = getMethod(req); |
|
|
|
|
|
|
|
|
method, |
|
|
|
|
|
url, |
|
|
|
|
|
}) => async (_req: IncomingMessage, res: ServerResponse) => { |
|
|
if (method !== 'DELETE') { |
|
|
if (method !== 'DELETE') { |
|
|
return { |
|
|
return { |
|
|
handled: false |
|
|
handled: false |
|
|
}; |
|
|
}; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const baseUrl = serverParams.baseUrl ?? ''; |
|
|
|
|
|
const { url } = getUrl(req, baseUrl); |
|
|
|
|
|
|
|
|
|
|
|
const [, mainResourceRouteName, mainResourceId = ''] = url.split('/'); |
|
|
const [, mainResourceRouteName, mainResourceId = ''] = url.split('/'); |
|
|
if (mainResourceId === '') { |
|
|
if (mainResourceId === '') { |
|
|
return { |
|
|
return { |
|
@@ -262,28 +203,6 @@ export const handleDeleteItem: Middleware = ({ |
|
|
}; |
|
|
}; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (theResource.checksSerializersOnDelete) { |
|
|
|
|
|
const negotiator = new Negotiator(req); |
|
|
|
|
|
const availableMediaTypes = Array.from(appState.serializers.keys()); |
|
|
|
|
|
const theMediaType = negotiator.mediaType(availableMediaTypes); |
|
|
|
|
|
if (typeof theMediaType === 'undefined') { |
|
|
|
|
|
res.statusCode = constants.HTTP_STATUS_NOT_ACCEPTABLE; |
|
|
|
|
|
res.end(); |
|
|
|
|
|
return { |
|
|
|
|
|
handled: true |
|
|
|
|
|
}; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const theSerializerPair = appState.serializers.get(theMediaType); |
|
|
|
|
|
if (typeof theSerializerPair === 'undefined') { |
|
|
|
|
|
res.statusCode = constants.HTTP_STATUS_NOT_ACCEPTABLE; |
|
|
|
|
|
res.end(); |
|
|
|
|
|
return { |
|
|
|
|
|
handled: true |
|
|
|
|
|
}; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
try { |
|
|
await theResource.dataSource.initialize(); |
|
|
await theResource.dataSource.initialize(); |
|
|
const response = await theResource.dataSource.delete(mainResourceId); |
|
|
const response = await theResource.dataSource.delete(mainResourceId); |
|
@@ -311,18 +230,17 @@ export const handleDeleteItem: Middleware = ({ |
|
|
|
|
|
|
|
|
export const handlePatchItem: Middleware = ({ |
|
|
export const handlePatchItem: Middleware = ({ |
|
|
appState, |
|
|
appState, |
|
|
serverParams, |
|
|
|
|
|
}) => async (req, res) => { |
|
|
|
|
|
const method = getMethod(req); |
|
|
|
|
|
|
|
|
method, |
|
|
|
|
|
url, |
|
|
|
|
|
responseBodySerializerPair, |
|
|
|
|
|
responseMediaType, |
|
|
|
|
|
}) => async (req: IncomingMessage, res: ServerResponse) => { |
|
|
if (method !== 'PATCH') { |
|
|
if (method !== 'PATCH') { |
|
|
return { |
|
|
return { |
|
|
handled: false |
|
|
handled: false |
|
|
}; |
|
|
}; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const baseUrl = serverParams.baseUrl ?? ''; |
|
|
|
|
|
const { url } = getUrl(req, baseUrl); |
|
|
|
|
|
|
|
|
|
|
|
const [, mainResourceRouteName, mainResourceId = ''] = url.split('/'); |
|
|
const [, mainResourceRouteName, mainResourceId = ''] = url.split('/'); |
|
|
if (mainResourceId === '') { |
|
|
if (mainResourceId === '') { |
|
|
return { |
|
|
return { |
|
@@ -337,8 +255,8 @@ export const handlePatchItem: Middleware = ({ |
|
|
}; |
|
|
}; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const theDeserializer = appState.serializers.get(req.headers['content-type'] ?? 'application/octet-stream'); |
|
|
|
|
|
if (typeof theDeserializer === 'undefined') { |
|
|
|
|
|
|
|
|
const { deserializerPair: requestBodyDeserializerPair, encodingPair: requestBodyEncodingPair } = getDeserializerObjects(appState, req); |
|
|
|
|
|
if (typeof requestBodyDeserializerPair === 'undefined' || typeof requestBodyEncodingPair === 'undefined') { |
|
|
res.statusCode = constants.HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE; |
|
|
res.statusCode = constants.HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE; |
|
|
res.end(); |
|
|
res.end(); |
|
|
return { |
|
|
return { |
|
@@ -346,26 +264,6 @@ export const handlePatchItem: Middleware = ({ |
|
|
}; |
|
|
}; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const negotiator = new Negotiator(req); |
|
|
|
|
|
const availableMediaTypes = Array.from(appState.serializers.keys()); |
|
|
|
|
|
const theMediaType = negotiator.mediaType(availableMediaTypes); |
|
|
|
|
|
if (typeof theMediaType === 'undefined') { |
|
|
|
|
|
res.statusCode = constants.HTTP_STATUS_NOT_ACCEPTABLE; |
|
|
|
|
|
res.end(); |
|
|
|
|
|
return { |
|
|
|
|
|
handled: true |
|
|
|
|
|
}; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const theSerializerPair = appState.serializers.get(theMediaType); |
|
|
|
|
|
if (typeof theSerializerPair === 'undefined') { |
|
|
|
|
|
res.statusCode = constants.HTTP_STATUS_NOT_ACCEPTABLE; |
|
|
|
|
|
res.end(); |
|
|
|
|
|
return { |
|
|
|
|
|
handled: true |
|
|
|
|
|
}; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
await theResource.dataSource.initialize(); |
|
|
await theResource.dataSource.initialize(); |
|
|
const existing = await theResource.dataSource.getSingle(mainResourceId); |
|
|
const existing = await theResource.dataSource.getSingle(mainResourceId); |
|
|
if (!existing) { |
|
|
if (!existing) { |
|
@@ -382,7 +280,8 @@ export const handlePatchItem: Middleware = ({ |
|
|
const schema = theResource.schema.type === 'object' ? theResource.schema as v.ObjectSchema<any> : theResource.schema |
|
|
const schema = theResource.schema.type === 'object' ? theResource.schema as v.ObjectSchema<any> : theResource.schema |
|
|
bodyDeserialized = await getBody( |
|
|
bodyDeserialized = await getBody( |
|
|
req, |
|
|
req, |
|
|
theDeserializer, |
|
|
|
|
|
|
|
|
requestBodyDeserializerPair, |
|
|
|
|
|
requestBodyEncodingPair, |
|
|
schema.type === 'object' |
|
|
schema.type === 'object' |
|
|
? v.partial( |
|
|
? v.partial( |
|
|
schema as v.ObjectSchema<any>, |
|
|
schema as v.ObjectSchema<any>, |
|
@@ -398,7 +297,7 @@ export const handlePatchItem: Middleware = ({ |
|
|
|
|
|
|
|
|
if (Array.isArray(err.issues)) { |
|
|
if (Array.isArray(err.issues)) { |
|
|
// TODO better error reporting, localizable messages |
|
|
// TODO better error reporting, localizable messages |
|
|
const theFormatted = theSerializerPair.serialize( |
|
|
|
|
|
|
|
|
const theFormatted = responseBodySerializerPair.serialize( |
|
|
err.issues.map((i) => ( |
|
|
err.issues.map((i) => ( |
|
|
`${i.path?.map((p) => p.key)?.join('.') ?? i.reason}: ${i.message}` |
|
|
`${i.path?.map((p) => p.key)?.join('.') ?? i.reason}: ${i.message}` |
|
|
)) |
|
|
)) |
|
@@ -416,9 +315,9 @@ export const handlePatchItem: Middleware = ({ |
|
|
const params = bodyDeserialized as Record<string, unknown>; |
|
|
const params = bodyDeserialized as Record<string, unknown>; |
|
|
await theResource.dataSource.initialize(); |
|
|
await theResource.dataSource.initialize(); |
|
|
const newObject = await theResource.dataSource.patch(mainResourceId, params); |
|
|
const newObject = await theResource.dataSource.patch(mainResourceId, params); |
|
|
const theFormatted = theSerializerPair.serialize(newObject); |
|
|
|
|
|
|
|
|
const theFormatted = responseBodySerializerPair.serialize(newObject); |
|
|
res.writeHead(constants.HTTP_STATUS_OK, { |
|
|
res.writeHead(constants.HTTP_STATUS_OK, { |
|
|
'Content-Type': theMediaType, |
|
|
|
|
|
|
|
|
'Content-Type': responseMediaType, |
|
|
}); |
|
|
}); |
|
|
res.end(theFormatted); |
|
|
res.end(theFormatted); |
|
|
} catch { |
|
|
} catch { |
|
@@ -433,18 +332,18 @@ export const handlePatchItem: Middleware = ({ |
|
|
|
|
|
|
|
|
export const handleCreateItem: Middleware = ({ |
|
|
export const handleCreateItem: Middleware = ({ |
|
|
appState, |
|
|
appState, |
|
|
serverParams |
|
|
|
|
|
}) => async (req, res) => { |
|
|
|
|
|
const method = getMethod(req); |
|
|
|
|
|
|
|
|
serverParams, |
|
|
|
|
|
method, |
|
|
|
|
|
url, |
|
|
|
|
|
responseMediaType, |
|
|
|
|
|
responseBodySerializerPair, |
|
|
|
|
|
}) => async (req: IncomingMessage, res: ServerResponse) => { |
|
|
if (method !== 'POST') { |
|
|
if (method !== 'POST') { |
|
|
return { |
|
|
return { |
|
|
handled: false |
|
|
handled: false |
|
|
}; |
|
|
}; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const baseUrl = serverParams.baseUrl ?? ''; |
|
|
|
|
|
const { url } = getUrl(req, baseUrl); |
|
|
|
|
|
|
|
|
|
|
|
const [, mainResourceRouteName, mainResourceId = ''] = url.split('/'); |
|
|
const [, mainResourceRouteName, mainResourceId = ''] = url.split('/'); |
|
|
if (mainResourceId !== '') { |
|
|
if (mainResourceId !== '') { |
|
|
return { |
|
|
return { |
|
@@ -459,8 +358,8 @@ export const handleCreateItem: Middleware = ({ |
|
|
}; |
|
|
}; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const theDeserializer = appState.serializers.get(req.headers['content-type'] ?? 'application/octet-stream'); |
|
|
|
|
|
if (typeof theDeserializer === 'undefined') { |
|
|
|
|
|
|
|
|
const { deserializerPair: requestBodyDeserializerPair, encodingPair: requestBodyEncodingPair } = getDeserializerObjects(appState, req); |
|
|
|
|
|
if (typeof requestBodyDeserializerPair === 'undefined' || typeof requestBodyEncodingPair === 'undefined') { |
|
|
res.statusCode = constants.HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE; |
|
|
res.statusCode = constants.HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE; |
|
|
res.end(); |
|
|
res.end(); |
|
|
return { |
|
|
return { |
|
@@ -468,30 +367,9 @@ export const handleCreateItem: Middleware = ({ |
|
|
}; |
|
|
}; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const negotiator = new Negotiator(req); |
|
|
|
|
|
const availableMediaTypes = Array.from(appState.serializers.keys()); |
|
|
|
|
|
const theMediaType = negotiator.mediaType(availableMediaTypes); |
|
|
|
|
|
if (typeof theMediaType === 'undefined') { |
|
|
|
|
|
res.statusCode = constants.HTTP_STATUS_NOT_ACCEPTABLE; |
|
|
|
|
|
res.end(); |
|
|
|
|
|
return { |
|
|
|
|
|
handled: true |
|
|
|
|
|
}; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const theSerializerPair = appState.serializers.get(theMediaType); |
|
|
|
|
|
if (typeof theSerializerPair === 'undefined') { |
|
|
|
|
|
res.statusCode = constants.HTTP_STATUS_NOT_ACCEPTABLE; |
|
|
|
|
|
res.end(); |
|
|
|
|
|
return { |
|
|
|
|
|
handled: true |
|
|
|
|
|
}; |
|
|
|
|
|
} |
|
|
|
|
|
// TODO determine serializer pair before running the middlewares |
|
|
|
|
|
|
|
|
|
|
|
let bodyDeserialized: unknown; |
|
|
let bodyDeserialized: unknown; |
|
|
try { |
|
|
try { |
|
|
bodyDeserialized = await getBody(req, theDeserializer, theResource.schema); |
|
|
|
|
|
|
|
|
bodyDeserialized = await getBody(req, requestBodyDeserializerPair, requestBodyEncodingPair, theResource.schema); |
|
|
} catch (errRaw) { |
|
|
} catch (errRaw) { |
|
|
const err = errRaw as v.ValiError; |
|
|
const err = errRaw as v.ValiError; |
|
|
res.statusCode = constants.HTTP_STATUS_BAD_REQUEST; |
|
|
res.statusCode = constants.HTTP_STATUS_BAD_REQUEST; |
|
@@ -499,7 +377,7 @@ export const handleCreateItem: Middleware = ({ |
|
|
|
|
|
|
|
|
if (Array.isArray(err.issues)) { |
|
|
if (Array.isArray(err.issues)) { |
|
|
// TODO better error reporting, localizable messages |
|
|
// TODO better error reporting, localizable messages |
|
|
const theFormatted = theSerializerPair.serialize( |
|
|
|
|
|
|
|
|
const theFormatted = responseBodySerializerPair.serialize( |
|
|
err.issues.map((i) => ( |
|
|
err.issues.map((i) => ( |
|
|
`${i.path?.map((p) => p.key)?.join('.') ?? i.reason}: ${i.message}` |
|
|
`${i.path?.map((p) => p.key)?.join('.') ?? i.reason}: ${i.message}` |
|
|
)) |
|
|
)) |
|
@@ -519,9 +397,9 @@ export const handleCreateItem: Middleware = ({ |
|
|
const params = bodyDeserialized as Record<string, unknown>; |
|
|
const params = bodyDeserialized as Record<string, unknown>; |
|
|
params[theResource.idAttr] = newId; |
|
|
params[theResource.idAttr] = newId; |
|
|
const newObject = await theResource.dataSource.create(params); |
|
|
const newObject = await theResource.dataSource.create(params); |
|
|
const theFormatted = theSerializerPair.serialize(newObject); |
|
|
|
|
|
|
|
|
const theFormatted = responseBodySerializerPair.serialize(newObject); |
|
|
res.writeHead(constants.HTTP_STATUS_OK, { |
|
|
res.writeHead(constants.HTTP_STATUS_OK, { |
|
|
'Content-Type': theMediaType, |
|
|
|
|
|
|
|
|
'Content-Type': responseMediaType, |
|
|
'Location': `${serverParams.baseUrl}/${theResource.routeName}/${newId}` |
|
|
'Location': `${serverParams.baseUrl}/${theResource.routeName}/${newId}` |
|
|
}); |
|
|
}); |
|
|
res.end(theFormatted); |
|
|
res.end(theFormatted); |
|
@@ -537,18 +415,18 @@ export const handleCreateItem: Middleware = ({ |
|
|
|
|
|
|
|
|
export const handleEmplaceItem: Middleware = ({ |
|
|
export const handleEmplaceItem: Middleware = ({ |
|
|
appState, |
|
|
appState, |
|
|
serverParams |
|
|
|
|
|
}) => async (req, res) => { |
|
|
|
|
|
const method = getMethod(req); |
|
|
|
|
|
|
|
|
serverParams, |
|
|
|
|
|
method, |
|
|
|
|
|
url, |
|
|
|
|
|
responseBodySerializerPair, |
|
|
|
|
|
responseMediaType, |
|
|
|
|
|
}) => async (req: IncomingMessage, res: ServerResponse) => { |
|
|
if (method !== 'PUT') { |
|
|
if (method !== 'PUT') { |
|
|
return { |
|
|
return { |
|
|
handled: false |
|
|
handled: false |
|
|
}; |
|
|
}; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const baseUrl = serverParams.baseUrl ?? ''; |
|
|
|
|
|
const { url } = getUrl(req, baseUrl); |
|
|
|
|
|
|
|
|
|
|
|
const [, mainResourceRouteName, mainResourceId = ''] = url.split('/'); |
|
|
const [, mainResourceRouteName, mainResourceId = ''] = url.split('/'); |
|
|
if (mainResourceId === '') { |
|
|
if (mainResourceId === '') { |
|
|
return { |
|
|
return { |
|
@@ -563,8 +441,8 @@ export const handleEmplaceItem: Middleware = ({ |
|
|
}; |
|
|
}; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const theDeserializer = appState.serializers.get(req.headers['content-type'] ?? 'application/octet-stream'); |
|
|
|
|
|
if (typeof theDeserializer === 'undefined') { |
|
|
|
|
|
|
|
|
const { deserializerPair: requestBodyDeserializerPair, encodingPair: requestBodyEncodingPair } = getDeserializerObjects(appState, req); |
|
|
|
|
|
if (typeof requestBodyDeserializerPair === 'undefined' || typeof requestBodyEncodingPair === 'undefined') { |
|
|
res.statusCode = constants.HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE; |
|
|
res.statusCode = constants.HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE; |
|
|
res.end(); |
|
|
res.end(); |
|
|
return { |
|
|
return { |
|
@@ -572,41 +450,21 @@ export const handleEmplaceItem: Middleware = ({ |
|
|
}; |
|
|
}; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const negotiator = new Negotiator(req); |
|
|
|
|
|
const availableMediaTypes = Array.from(appState.serializers.keys()); |
|
|
|
|
|
const theMediaType = negotiator.mediaType(availableMediaTypes); |
|
|
|
|
|
if (typeof theMediaType === 'undefined') { |
|
|
|
|
|
res.statusCode = constants.HTTP_STATUS_NOT_ACCEPTABLE; |
|
|
|
|
|
res.end(); |
|
|
|
|
|
return { |
|
|
|
|
|
handled: true |
|
|
|
|
|
}; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const theSerializerPair = appState.serializers.get(theMediaType); |
|
|
|
|
|
if (typeof theSerializerPair === 'undefined') { |
|
|
|
|
|
res.statusCode = constants.HTTP_STATUS_NOT_ACCEPTABLE; |
|
|
|
|
|
res.end(); |
|
|
|
|
|
return { |
|
|
|
|
|
handled: true |
|
|
|
|
|
}; |
|
|
|
|
|
} |
|
|
|
|
|
// TODO determine serializer pair before running the middlewares |
|
|
|
|
|
|
|
|
|
|
|
let bodyDeserialized: unknown; |
|
|
let bodyDeserialized: unknown; |
|
|
try { |
|
|
try { |
|
|
const schema = theResource.schema.type === 'object' ? theResource.schema as v.ObjectSchema<any> : theResource.schema |
|
|
const schema = theResource.schema.type === 'object' ? theResource.schema as v.ObjectSchema<any> : theResource.schema |
|
|
//console.log(schema); |
|
|
//console.log(schema); |
|
|
bodyDeserialized = await getBody( |
|
|
bodyDeserialized = await getBody( |
|
|
req, |
|
|
req, |
|
|
theDeserializer, |
|
|
|
|
|
|
|
|
requestBodyDeserializerPair, |
|
|
|
|
|
requestBodyEncodingPair, |
|
|
schema.type === 'object' |
|
|
schema.type === 'object' |
|
|
? v.merge([ |
|
|
? v.merge([ |
|
|
schema as v.ObjectSchema<any>, |
|
|
schema as v.ObjectSchema<any>, |
|
|
v.object({ |
|
|
v.object({ |
|
|
[theResource.idAttr]: v.transform( |
|
|
[theResource.idAttr]: v.transform( |
|
|
v.any(), |
|
|
v.any(), |
|
|
input => input.toString(), // TODO serialize/deserialize ID values |
|
|
|
|
|
|
|
|
input => theResource.idSerializer(input), |
|
|
v.literal(mainResourceId) |
|
|
v.literal(mainResourceId) |
|
|
) |
|
|
) |
|
|
}) |
|
|
}) |
|
@@ -620,7 +478,7 @@ export const handleEmplaceItem: Middleware = ({ |
|
|
|
|
|
|
|
|
if (Array.isArray(err.issues)) { |
|
|
if (Array.isArray(err.issues)) { |
|
|
// TODO better error reporting, localizable messages |
|
|
// TODO better error reporting, localizable messages |
|
|
const theFormatted = theSerializerPair.serialize( |
|
|
|
|
|
|
|
|
const theFormatted = responseBodySerializerPair.serialize( |
|
|
err.issues.map((i) => ( |
|
|
err.issues.map((i) => ( |
|
|
`${i.path?.map((p) => p.key)?.join('.') ?? i.reason}: ${i.message}` |
|
|
`${i.path?.map((p) => p.key)?.join('.') ?? i.reason}: ${i.message}` |
|
|
)) |
|
|
)) |
|
@@ -638,15 +496,15 @@ export const handleEmplaceItem: Middleware = ({ |
|
|
await theResource.dataSource.initialize(); |
|
|
await theResource.dataSource.initialize(); |
|
|
const params = bodyDeserialized as Record<string, unknown>; |
|
|
const params = bodyDeserialized as Record<string, unknown>; |
|
|
const [newObject, isCreated] = await theResource.dataSource.emplace(mainResourceId, params); |
|
|
const [newObject, isCreated] = await theResource.dataSource.emplace(mainResourceId, params); |
|
|
const theFormatted = theSerializerPair.serialize(newObject); |
|
|
|
|
|
|
|
|
const theFormatted = responseBodySerializerPair.serialize(newObject); |
|
|
if (isCreated) { |
|
|
if (isCreated) { |
|
|
res.writeHead(constants.HTTP_STATUS_CREATED, { |
|
|
res.writeHead(constants.HTTP_STATUS_CREATED, { |
|
|
'Content-Type': theMediaType, |
|
|
|
|
|
|
|
|
'Content-Type': responseMediaType, |
|
|
'Location': `${serverParams.baseUrl}/${theResource.routeName}/${mainResourceId}` |
|
|
'Location': `${serverParams.baseUrl}/${theResource.routeName}/${mainResourceId}` |
|
|
}); |
|
|
}); |
|
|
} else { |
|
|
} else { |
|
|
res.writeHead(constants.HTTP_STATUS_OK, { |
|
|
res.writeHead(constants.HTTP_STATUS_OK, { |
|
|
'Content-Type': theMediaType, |
|
|
|
|
|
|
|
|
'Content-Type': responseMediaType, |
|
|
}); |
|
|
}); |
|
|
} |
|
|
} |
|
|
res.end(theFormatted); |
|
|
res.end(theFormatted); |
|
|