@platformatic/sql-openapi
Advanced tools
Comparing version 0.3.0 to 0.4.0
@@ -1,1 +0,1 @@ | ||
{"processes":{"37c0cdcb-b739-46c8-8447-b68739045984":{"parent":"d2b4c28f-108b-4e08-b1f9-d15c96266f9d","externalId":"test/ignore.test.js","children":[]},"403bc0e6-1fbc-4c86-8bb1-d884cc4b3fae":{"parent":"d2b4c28f-108b-4e08-b1f9-d15c96266f9d","externalId":"test/hooks.test.js","children":[]},"6c3390ed-7e3e-4b9a-8d8d-b449f3310cd2":{"parent":"d2b4c28f-108b-4e08-b1f9-d15c96266f9d","externalId":"test/where.test.js","children":[]},"9855f880-a4e8-4d3f-804c-21a19b5ac89f":{"parent":"d2b4c28f-108b-4e08-b1f9-d15c96266f9d","externalId":"test/simple.test.js","children":[]},"d2b4c28f-108b-4e08-b1f9-d15c96266f9d":{"parent":null,"children":["37c0cdcb-b739-46c8-8447-b68739045984","403bc0e6-1fbc-4c86-8bb1-d884cc4b3fae","6c3390ed-7e3e-4b9a-8d8d-b449f3310cd2","9855f880-a4e8-4d3f-804c-21a19b5ac89f","e3781dc6-7ce2-435f-862d-d1ab33e8f35c"]},"e3781dc6-7ce2-435f-862d-d1ab33e8f35c":{"parent":"d2b4c28f-108b-4e08-b1f9-d15c96266f9d","externalId":"test/order_by.test.js","children":[]}},"files":{"/Users/matteo/Repositories/platformatic/packages/sql-openapi/index.js":["37c0cdcb-b739-46c8-8447-b68739045984","403bc0e6-1fbc-4c86-8bb1-d884cc4b3fae","6c3390ed-7e3e-4b9a-8d8d-b449f3310cd2","9855f880-a4e8-4d3f-804c-21a19b5ac89f","e3781dc6-7ce2-435f-862d-d1ab33e8f35c"],"/Users/matteo/Repositories/platformatic/packages/sql-openapi/lib/entity-to-routes.js":["37c0cdcb-b739-46c8-8447-b68739045984","403bc0e6-1fbc-4c86-8bb1-d884cc4b3fae","6c3390ed-7e3e-4b9a-8d8d-b449f3310cd2","9855f880-a4e8-4d3f-804c-21a19b5ac89f","e3781dc6-7ce2-435f-862d-d1ab33e8f35c"]},"externalIds":{"test/ignore.test.js":{"root":"37c0cdcb-b739-46c8-8447-b68739045984","children":[]},"test/hooks.test.js":{"root":"403bc0e6-1fbc-4c86-8bb1-d884cc4b3fae","children":[]},"test/where.test.js":{"root":"6c3390ed-7e3e-4b9a-8d8d-b449f3310cd2","children":[]},"test/simple.test.js":{"root":"9855f880-a4e8-4d3f-804c-21a19b5ac89f","children":[]},"test/order_by.test.js":{"root":"e3781dc6-7ce2-435f-862d-d1ab33e8f35c","children":[]}}} | ||
{"processes":{"42d15934-e8f2-45a1-954f-31efa1455bf6":{"parent":"cf984c92-532d-4ae1-9559-9b4b588f05d5","externalId":"test/ignore.test.js","children":[]},"48993cf3-72fe-4040-91b5-28d8be85c30b":{"parent":"cf984c92-532d-4ae1-9559-9b4b588f05d5","externalId":"test/where.test.js","children":[]},"7f01475e-4ecf-4934-94ef-47d32f22e4cd":{"parent":"cf984c92-532d-4ae1-9559-9b4b588f05d5","externalId":"test/simple.test.js","children":[]},"c2b5c996-0b3a-4aca-b0ee-517845bda2e2":{"parent":"cf984c92-532d-4ae1-9559-9b4b588f05d5","externalId":"test/order_by.test.js","children":[]},"cdfcaa24-c93d-46da-a6b5-3e787b3a39be":{"parent":"cf984c92-532d-4ae1-9559-9b4b588f05d5","externalId":"test/hooks.test.js","children":[]},"cf984c92-532d-4ae1-9559-9b4b588f05d5":{"parent":null,"children":["42d15934-e8f2-45a1-954f-31efa1455bf6","48993cf3-72fe-4040-91b5-28d8be85c30b","7f01475e-4ecf-4934-94ef-47d32f22e4cd","c2b5c996-0b3a-4aca-b0ee-517845bda2e2","cdfcaa24-c93d-46da-a6b5-3e787b3a39be","d0d9eae7-77b6-42ef-a376-cd79dab441cb"]},"d0d9eae7-77b6-42ef-a376-cd79dab441cb":{"parent":"cf984c92-532d-4ae1-9559-9b4b588f05d5","externalId":"test/nested.test.js","children":[]}},"files":{"/Users/matteo/Repositories/platformatic/packages/sql-openapi/index.js":["42d15934-e8f2-45a1-954f-31efa1455bf6","48993cf3-72fe-4040-91b5-28d8be85c30b","7f01475e-4ecf-4934-94ef-47d32f22e4cd","c2b5c996-0b3a-4aca-b0ee-517845bda2e2","cdfcaa24-c93d-46da-a6b5-3e787b3a39be","d0d9eae7-77b6-42ef-a376-cd79dab441cb"],"/Users/matteo/Repositories/platformatic/packages/sql-openapi/lib/entity-to-routes.js":["42d15934-e8f2-45a1-954f-31efa1455bf6","48993cf3-72fe-4040-91b5-28d8be85c30b","7f01475e-4ecf-4934-94ef-47d32f22e4cd","c2b5c996-0b3a-4aca-b0ee-517845bda2e2","cdfcaa24-c93d-46da-a6b5-3e787b3a39be","d0d9eae7-77b6-42ef-a376-cd79dab441cb"]},"externalIds":{"test/ignore.test.js":{"root":"42d15934-e8f2-45a1-954f-31efa1455bf6","children":[]},"test/where.test.js":{"root":"48993cf3-72fe-4040-91b5-28d8be85c30b","children":[]},"test/simple.test.js":{"root":"7f01475e-4ecf-4934-94ef-47d32f22e4cd","children":[]},"test/order_by.test.js":{"root":"c2b5c996-0b3a-4aca-b0ee-517845bda2e2","children":[]},"test/hooks.test.js":{"root":"cdfcaa24-c93d-46da-a6b5-3e787b3a39be","children":[]},"test/nested.test.js":{"root":"d0d9eae7-77b6-42ef-a376-cd79dab441cb","children":[]}}} |
'use strict' | ||
const Swagger = require('@fastify/swagger') | ||
const SwaggerUI = require('@fastify/swagger-ui') | ||
const deepmerge = require('@fastify/deepmerge')({ all: true }) | ||
@@ -12,2 +13,3 @@ const camelcase = require('camelcase') | ||
async function setupOpenAPI (app, opts) { | ||
const prefix = opts.prefix || '' | ||
const openapiConfig = deepmerge({ | ||
@@ -17,3 +19,4 @@ exposeRoute: true, | ||
title: 'Platformatic DB', | ||
description: 'Exposing a SQL database as REST' | ||
description: 'Exposing a SQL database as REST', | ||
version: '1.0.0' | ||
} | ||
@@ -36,2 +39,4 @@ }, opts) | ||
app.register(SwaggerUI, opts) | ||
for (const entity of Object.values(app.platformatic.entities)) { | ||
@@ -62,3 +67,3 @@ const entitySchema = mapSQLEntityToJSONSchema(entity) | ||
entity, | ||
prefix: '/' + entity.pluralName | ||
prefix: `${prefix}/${entity.pluralName}` | ||
}) | ||
@@ -65,0 +70,0 @@ } |
@@ -7,21 +7,14 @@ 'use strict' | ||
async function entityPlugin (app, opts) { | ||
const entity = opts.entity | ||
const entitySchema = { | ||
$ref: entity.name + '#' | ||
} | ||
const primaryKeyParams = getPrimaryKeyParams(entity) | ||
const getEntityLinksForEntity = (app, entity) => { | ||
const entityLinks = {} | ||
const primaryKeyCamelcase = camelcase(entity.primaryKey) | ||
for (const relation of entity.relations) { | ||
const ownField = camelcase(relation.column_name) | ||
const relatedEntity = app.platformatic.entities[camelcase(singularize(relation.foreign_table_name))] | ||
const relatedEntityPrimaryKeyCamelcase = capitalize(camelcase(relatedEntity.primaryKey)) | ||
const getEntityById = `Get${relatedEntity.name}With${relatedEntityPrimaryKeyCamelcase}` | ||
const relatedEntityPrimaryKeyCamelcase = camelcase(relatedEntity.primaryKey) | ||
const relatedEntityPrimaryKeyCamelcaseCapitalized = capitalize(relatedEntityPrimaryKeyCamelcase) | ||
const getEntityById = `Get${relatedEntity.name}By${relatedEntityPrimaryKeyCamelcaseCapitalized}` | ||
entityLinks[getEntityById] = { | ||
operationId: `get${relatedEntity.name}By${relatedEntityPrimaryKeyCamelcase}`, | ||
operationId: `get${relatedEntity.name}By${relatedEntityPrimaryKeyCamelcaseCapitalized}`, | ||
parameters: { | ||
[primaryKeyCamelcase]: `$response.body#/${ownField}` | ||
[relatedEntityPrimaryKeyCamelcase]: `$response.body#/${ownField}` | ||
} | ||
@@ -36,5 +29,5 @@ } | ||
const relatedEntity = app.platformatic.entities[camelcase(singularize(relation.table_name))] | ||
const getAllEntities = `GetAll${capitalize(relatedEntity.pluralName)}` | ||
entityLinks[getAllEntities] = { | ||
operationId: `getAll${capitalize(relatedEntity.pluralName)}`, | ||
const getEntities = `Get${capitalize(relatedEntity.pluralName)}` | ||
entityLinks[getEntities] = { | ||
operationId: `get${capitalize(relatedEntity.pluralName)}`, | ||
parameters: { | ||
@@ -45,6 +38,27 @@ [`where.${theirField}.eq`]: `$response.body#/${ownField}` | ||
} | ||
return entityLinks | ||
} | ||
const whereArgs = Object.keys(entity.fields).sort().map((name) => { | ||
return entity.fields[name] | ||
}).reduce((acc, field) => { | ||
const getFieldsForEntity = (entity) => ({ | ||
type: 'array', | ||
items: { | ||
type: 'string', | ||
enum: Object.keys(entity.fields).map((field) => entity.fields[field].camelcase).sort() | ||
} | ||
}) | ||
async function entityPlugin (app, opts) { | ||
const entity = opts.entity | ||
const entitySchema = { | ||
$ref: entity.name + '#' | ||
} | ||
const primaryKeyParams = getPrimaryKeyParams(entity) | ||
const primaryKeyCamelcase = camelcase(entity.primaryKey) | ||
const entityLinks = getEntityLinksForEntity(app, entity) | ||
const sortedEntityFields = Object.keys(entity.fields).sort() | ||
const whereArgs = sortedEntityFields.reduce((acc, name) => { | ||
const field = entity.fields[name] | ||
const baseKey = `where.${field.camelcase}.` | ||
@@ -64,5 +78,4 @@ for (const modifier of ['eq', 'neq', 'gt', 'gte', 'lt', 'lte']) { | ||
const orderByArgs = Object.keys(entity.fields).sort().map((name) => { | ||
return entity.fields[name] | ||
}).reduce((acc, field) => { | ||
const orderByArgs = sortedEntityFields.reduce((acc, name) => { | ||
const field = entity.fields[name] | ||
const key = `orderby.${field.camelcase}` | ||
@@ -79,13 +92,7 @@ acc[key] = { type: 'string', enum: ['asc', 'desc'] } | ||
const fields = { | ||
type: 'array', | ||
items: { | ||
type: 'string', | ||
enum: Object.keys(entity.fields).map((field) => entity.fields[field].camelcase).sort() | ||
} | ||
} | ||
const fields = getFieldsForEntity(entity) | ||
app.get('/', { | ||
schema: { | ||
operationId: 'getAll' + entity.name, | ||
operationId: 'get' + capitalize(entity.pluralName), | ||
querystring: { | ||
@@ -113,8 +120,12 @@ type: 'object', | ||
const { limit, offset, fields } = query | ||
// TODO computing this where clause will be slow | ||
// refactor to use a barebone for(;;) loop | ||
const where = Object.keys(query).reduce((acc, key) => { | ||
if (key.indexOf('where.') === 0) { | ||
const queryKeys = Object.keys(query) | ||
const where = {} | ||
const orderBy = [] | ||
for (let i = 0; i < queryKeys.length; i++) { | ||
const key = queryKeys[i] | ||
if (key.startsWith('where.')) { | ||
const [, field, modifier] = key.split('.') | ||
acc[field] = acc[field] || {} | ||
where[field] ||= {} | ||
let value = query[key] | ||
@@ -128,21 +139,24 @@ if (modifier === 'in' || modifier === 'nin') { | ||
} | ||
acc[field][modifier] = value | ||
return acc | ||
} | ||
return acc | ||
}, {}) | ||
const orderBy = Object.keys(query).reduce((acc, key) => { | ||
if (key.indexOf('orderby.') === 0) { | ||
where[field][modifier] = value | ||
} else if (key.startsWith('orderby.')) { | ||
const [, field] = key.split('.') | ||
acc[field] = acc[field] || {} | ||
acc.push({ field, direction: query[key] }) | ||
orderBy[field] ||= {} | ||
orderBy.push({ field, direction: query[key] }) | ||
} | ||
return acc | ||
}, []) | ||
} | ||
const ctx = { app: this, reply } | ||
const res = await entity.find({ limit, offset, fields, orderBy, where, ctx }) | ||
// X-Total-Count header | ||
if (query.totalCount) { | ||
const totalCount = await entity.count({ where, ctx }) | ||
let totalCount | ||
if ((((offset ?? 0) === 0) || (res.length > 0)) && ((limit === undefined) || (res.length < limit))) { | ||
totalCount = (offset ?? 0) + res.length | ||
} else { | ||
totalCount = await entity.count({ where, ctx }) | ||
} | ||
reply.header('X-Total-Count', totalCount) | ||
} | ||
return res | ||
@@ -202,2 +216,133 @@ }) | ||
// For every reverse relationship we create: entity/:entity_Id/target_entity | ||
for (const reverseRelationship of entity.reverseRelationships) { | ||
const targetEntityName = singularize(camelcase(reverseRelationship.relation.table_name)) | ||
const targetEntity = app.platformatic.entities[targetEntityName] | ||
const targetForeignKeyCamelcase = camelcase(reverseRelationship.relation.column_name) | ||
const targetEntitySchema = { | ||
$ref: targetEntity.name + '#' | ||
} | ||
const entityLinks = getEntityLinksForEntity(app, targetEntity) | ||
// e.g. getQuotesForMovie | ||
const operationId = `get${capitalize(targetEntity.pluralName)}For${capitalize(entity.singularName)}` | ||
app.get(`/:${camelcase(entity.primaryKey)}/${targetEntity.pluralName}`, { | ||
schema: { | ||
operationId, | ||
params: getPrimaryKeyParams(entity), | ||
querystring: { | ||
type: 'object', | ||
properties: { | ||
fields: getFieldsForEntity(targetEntity) | ||
} | ||
}, | ||
response: { | ||
200: { | ||
type: 'array', | ||
items: targetEntitySchema | ||
} | ||
} | ||
}, | ||
links: { | ||
200: entityLinks | ||
} | ||
}, async function (request, reply) { | ||
const ctx = { app: this, reply } | ||
// IF we want to have HTTP/404 in case the entity does not exist | ||
// we need to do 2 queries. One to check if the entity exists. the other to get the related entities | ||
// Improvement: this could be also done with a single query with a join, | ||
// check that the entity exists | ||
const resEntity = await entity.count({ | ||
ctx, | ||
where: { | ||
[primaryKeyCamelcase]: { | ||
eq: request.params[primaryKeyCamelcase] | ||
} | ||
} | ||
}) | ||
if (resEntity === 0) { | ||
return reply.callNotFound() | ||
} | ||
// get the related entities | ||
const res = await targetEntity.find({ | ||
ctx, | ||
where: { | ||
[targetForeignKeyCamelcase]: { | ||
eq: request.params[primaryKeyCamelcase] | ||
} | ||
}, | ||
fields: request.query.fields | ||
}) | ||
if (res.length === 0) { | ||
// This is a query on a FK, so | ||
return [] | ||
} | ||
return res | ||
}) | ||
} | ||
// For every relationship we create: entity/:entity_Id/target_entity | ||
for (const relation of entity.relations) { | ||
const targetEntityName = singularize(camelcase(relation.foreign_table_name)) | ||
const targetEntity = app.platformatic.entities[targetEntityName] | ||
const targetForeignKeyCamelcase = camelcase(relation.foreign_column_name) | ||
const targetEntitySchema = { | ||
$ref: targetEntity.name + '#' | ||
} | ||
const entityLinks = getEntityLinksForEntity(app, targetEntity) | ||
// e.g. getMovieForQuote | ||
const operationId = `get${capitalize(targetEntity.singularName)}For${capitalize(entity.singularName)}` | ||
app.get(`/:${camelcase(entity.primaryKey)}/${targetEntity.singularName}`, { | ||
schema: { | ||
operationId, | ||
params: getPrimaryKeyParams(entity), | ||
querystring: { | ||
type: 'object', | ||
properties: { | ||
fields: getFieldsForEntity(targetEntity) | ||
} | ||
}, | ||
response: { | ||
200: targetEntitySchema | ||
} | ||
}, | ||
links: { | ||
200: entityLinks | ||
} | ||
}, async function (request, reply) { | ||
const ctx = { app: this, reply } | ||
// check that the entity exists | ||
const resEntity = await entity.count({ | ||
ctx, | ||
where: { | ||
[primaryKeyCamelcase]: { | ||
eq: request.params[primaryKeyCamelcase] | ||
} | ||
} | ||
}) | ||
if (resEntity === 0) { | ||
return reply.callNotFound() | ||
} | ||
// get the related entity | ||
const res = await targetEntity.find({ | ||
ctx, | ||
where: { | ||
[targetForeignKeyCamelcase]: { | ||
eq: request.params[primaryKeyCamelcase] | ||
} | ||
}, | ||
fields: request.query.fields | ||
}) | ||
if (res.length === 0) { | ||
return reply.callNotFound() | ||
} | ||
return res[0] | ||
}) | ||
} | ||
for (const method of ['POST', 'PUT']) { | ||
@@ -204,0 +349,0 @@ app.route({ |
{ | ||
"name": "@platformatic/sql-openapi", | ||
"version": "0.3.0", | ||
"version": "0.4.0", | ||
"description": "Map a SQL database to OpenAPI, for Fastify", | ||
@@ -17,3 +17,3 @@ "main": "index.js", | ||
"devDependencies": { | ||
"@platformatic/sql-mapper": "0.3.0", | ||
"@platformatic/sql-mapper": "0.4.0", | ||
"fastify": "^4.6.0", | ||
@@ -28,5 +28,6 @@ "mercurius": "^11.0.0", | ||
"dependencies": { | ||
"@platformatic/sql-json-schema-mapper": "0.3.0", | ||
"@platformatic/sql-json-schema-mapper": "0.4.0", | ||
"@fastify/deepmerge": "^1.1.0", | ||
"@fastify/swagger": "^7.4.1", | ||
"@fastify/swagger": "^8.0.0", | ||
"@fastify/swagger-ui": "^1.0.0", | ||
"camelcase": "^6.0.0", | ||
@@ -33,0 +34,0 @@ "fastify-plugin": "^4.1.0", |
@@ -50,2 +50,10 @@ 'use strict' | ||
const res = await app.inject({ | ||
method: 'GET', | ||
url: '/documentation/json' | ||
}) | ||
equal(res.json().info.version, '1.0.0', 'GET /documentation/json info version default') | ||
} | ||
{ | ||
const res = await app.inject({ | ||
method: 'POST', | ||
@@ -288,67 +296,105 @@ url: '/pages', | ||
{ | ||
const res = await app.inject({ | ||
method: 'GET', | ||
url: '/posts' | ||
}) | ||
equal(res.statusCode, 200, '/posts status code') | ||
equal(res.headers['x-total-count'], undefined, '/posts without x-total-count') | ||
const url = '/posts' | ||
const res = await app.inject({ method: 'GET', url }) | ||
equal(res.statusCode, 200, `${url} status code`) | ||
equal(res.headers['x-total-count'], undefined, `${url} without x-total-count`) | ||
same(res.json(), posts.map((p, i) => { | ||
return { ...p, id: i + 1 + '' } | ||
}), '/posts response') | ||
}), `${url} response`) | ||
} | ||
{ | ||
const res = await app.inject({ | ||
method: 'GET', | ||
url: '/posts?limit=3' | ||
}) | ||
equal(res.statusCode, 200, '/posts?limit=3 status code') | ||
const url = '/posts?limit=3' | ||
const res = await app.inject({ method: 'GET', url }) | ||
equal(res.statusCode, 200, `${url} status code`) | ||
equal(res.headers['x-total-count'], undefined, `${url} without x-total-count`) | ||
same(res.json(), posts.map((p, i) => { | ||
return { ...p, id: i + 1 + '' } | ||
}).slice(0, 3), '/posts?limit=3 response') | ||
}).slice(0, 3), `${url} response`) | ||
} | ||
{ | ||
const res = await app.inject({ | ||
method: 'GET', | ||
url: '/posts?offset=2' | ||
}) | ||
equal(res.statusCode, 200, '/posts?offset=2 status code') | ||
const url = '/posts?offset=2' | ||
const res = await app.inject({ method: 'GET', url }) | ||
equal(res.statusCode, 200, `${url} status code`) | ||
equal(res.headers['x-total-count'], undefined, `${url} without x-total-count`) | ||
same(res.json(), posts.map((p, i) => { | ||
return { ...p, id: i + 1 + '' } | ||
}).slice(2), '/posts?offset=2 response') | ||
}).slice(2), `${url} response`) | ||
} | ||
{ | ||
const res = await app.inject({ | ||
method: 'GET', | ||
url: '/posts?totalCount=true' | ||
}) | ||
equal(res.headers['x-total-count'], posts.length, '/posts?totalCount=true with x-total-count') | ||
equal(res.statusCode, 200, '/posts?totalCount=true status code') | ||
const url = '/posts?limit=2&offset=1' | ||
const res = await app.inject({ method: 'GET', url }) | ||
equal(res.statusCode, 200, `${url} status code`) | ||
equal(res.headers['x-total-count'], undefined, `${url} without x-total-count`) | ||
same(res.json(), posts.map((p, i) => { | ||
return { ...p, id: i + 1 + '' } | ||
}).slice(1, 3), `${url} response`) | ||
} | ||
{ | ||
const res = await app.inject({ | ||
method: 'GET', | ||
url: '/posts?limit=2&offset=1' | ||
}) | ||
equal(res.statusCode, 200, '/posts?limit=2&offset=1 status code') | ||
same(res.headers['x-total-count'], undefined, '/posts?limit=2&offset=1 without x-total-count') | ||
const url = '/posts?totalCount=true' | ||
const res = await app.inject({ method: 'GET', url }) | ||
equal(res.statusCode, 200, `${url} status code`) | ||
equal(res.headers['x-total-count'], posts.length, `${url} with x-total-count`) | ||
} | ||
{ | ||
const url = '/posts?totalCount=true&limit=3' | ||
const res = await app.inject({ method: 'GET', url }) | ||
equal(res.statusCode, 200, `${url} status code`) | ||
equal(res.headers['x-total-count'], posts.length, `${url} with x-total-count`) | ||
same(res.json(), posts.map((p, i) => { | ||
return { ...p, id: i + 1 + '' } | ||
}).slice(1, 3), '/posts?limit=2&offset=1 response') | ||
}).slice(0, 3), `${url} response`) | ||
} | ||
{ | ||
const res = await app.inject({ | ||
method: 'GET', | ||
url: '/posts?limit=2&offset=1&totalCount=true' | ||
}) | ||
equal(res.headers['x-total-count'], posts.length, '/posts?limit=2&offset=1&totalCount=true without x-total-count') | ||
equal(res.statusCode, 200, 'posts status code') | ||
const url = '/posts?totalCount=true&offset=2' | ||
const res = await app.inject({ method: 'GET', url }) | ||
equal(res.statusCode, 200, `${url} status code`) | ||
equal(res.headers['x-total-count'], posts.length, `${url} with x-total-count`) | ||
same(res.json(), posts.map((p, i) => { | ||
return { ...p, id: i + 1 + '' } | ||
}).slice(1, 3), '/posts?limit=2&offset=1&totalCount=true response') | ||
}).slice(2), `${url} response`) | ||
} | ||
{ | ||
const url = '/posts?totalCount=true&limit=2&offset=1' | ||
const res = await app.inject({ method: 'GET', url }) | ||
equal(res.statusCode, 200, `${url} status code`) | ||
equal(res.headers['x-total-count'], posts.length, `${url} with x-total-count`) | ||
same(res.json(), posts.map((p, i) => { | ||
return { ...p, id: i + 1 + '' } | ||
}).slice(1, 3), `${url} response`) | ||
} | ||
{ | ||
const url = '/posts?totalCount=true&limit=2&offset=99' | ||
const res = await app.inject({ method: 'GET', url }) | ||
equal(res.statusCode, 200, `${url} status code`) | ||
equal(res.headers['x-total-count'], posts.length, `${url} with x-total-count`) | ||
same(res.json(), [], `${url} response`) | ||
} | ||
{ | ||
const url = '/posts?totalCount=true&limit=99&offset=0' | ||
const res = await app.inject({ method: 'GET', url }) | ||
equal(res.statusCode, 200, `${url} status code`) | ||
equal(res.headers['x-total-count'], posts.length, `${url} with x-total-count`) | ||
same(res.json(), posts.map((p, i) => { | ||
return { ...p, id: i + 1 + '' } | ||
}).slice(0, 4), `${url} response`) | ||
} | ||
{ | ||
const url = '/posts?totalCount=true&limit=99&offset=2' | ||
const res = await app.inject({ method: 'GET', url }) | ||
equal(res.statusCode, 200, `${url} status code`) | ||
equal(res.headers['x-total-count'], posts.length, `${url} with x-total-count`) | ||
same(res.json(), posts.map((p, i) => { | ||
return { ...p, id: i + 1 + '' } | ||
}).slice(2, 4), `${url} response`) | ||
} | ||
}) | ||
@@ -483,3 +529,3 @@ | ||
test('simple db, simple rest API', async (t) => { | ||
const { pass, teardown, matchSnapshot } = t | ||
const { pass, teardown, matchSnapshot, equal } = t | ||
t.snapshotFile = resolve(__dirname, 'tap-snapshots', 'simple-openapi-3.cjs') | ||
@@ -514,2 +560,3 @@ | ||
matchSnapshot(json, 'GET /documentation/json response') | ||
equal(json.info.version, '42.42.42', 'GET /documentation/json info version override by opts') | ||
}) | ||
@@ -560,1 +607,61 @@ | ||
}) | ||
test('expose the api with a prefix, if defined', async (t) => { | ||
const { pass, teardown, same, equal, matchSnapshot } = t | ||
const app = fastify() | ||
app.register(sqlMapper, { | ||
...connInfo, | ||
async onDatabaseLoad (db, sql) { | ||
pass('onDatabaseLoad called') | ||
await clear(db, sql) | ||
await createBasicPages(db, sql) | ||
} | ||
}) | ||
app.register(sqlOpenAPI, { prefix: '/api' }) | ||
teardown(app.close.bind(app)) | ||
await app.ready() | ||
{ | ||
const res = await app.inject({ | ||
method: 'POST', | ||
url: '/pages', | ||
body: { | ||
title: 'Hello' | ||
} | ||
}) | ||
equal(res.statusCode, 404, 'POST /pages status code') | ||
same(res.json(), { | ||
message: 'Route POST:/pages not found', | ||
error: 'Not Found', | ||
statusCode: 404 | ||
}, 'POST /pages response') | ||
} | ||
{ | ||
const res = await app.inject({ | ||
method: 'POST', | ||
url: '/api/pages', | ||
body: { | ||
title: 'Hello' | ||
} | ||
}) | ||
equal(res.statusCode, 200, 'POST /pages status code') | ||
equal(res.headers.location, '/api/pages/1', 'POST /api/pages location') | ||
same(res.json(), { | ||
id: 1, | ||
title: 'Hello' | ||
}, 'POST /pages response') | ||
} | ||
// Check that the documentation is not prefixed | ||
{ | ||
t.snapshotFile = resolve(__dirname, 'tap-snapshots', 'simple-openapi-4.cjs') | ||
const res = await app.inject({ | ||
method: 'GET', | ||
url: '/documentation/json' | ||
}) | ||
const json = res.json() | ||
matchSnapshot(json, 'GET /documentation/json response') | ||
} | ||
}) |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
361681
39
7814
7
+ Added@fastify/swagger-ui@^1.0.0
+ Added@fastify/swagger@8.15.0(transitive)
+ Added@fastify/swagger-ui@1.10.2(transitive)
+ Added@platformatic/sql-json-schema-mapper@0.4.0(transitive)
- Removed@fastify/swagger@7.6.1(transitive)
- Removed@platformatic/sql-json-schema-mapper@0.3.0(transitive)
Updated@fastify/swagger@^8.0.0