const QueryPayload = require('../query/query-payload');
const QueryType = require('../query/query-type');
const QueryLanguage = require('../query/query-language');
const RDFMimeType = require('../http/rdf-mime-type');
const QueryContentType = require('../http/query-content-type');
const SELECT_QUERY_RESULT_TYPES = [
RDFMimeType.SPARQL_RESULTS_XML,
RDFMimeType.SPARQL_RESULTS_JSON,
RDFMimeType.BINARY_RDF_RESULTS_TABLE,
RDFMimeType.BOOLEAN_RESULT,
RDFMimeType.SPARQL_STAR_RESULTS_JSON,
RDFMimeType.SPARQL_STAR_RESULTS_TSV
];
const ASK_QUERY_RESULT_TYPES = [
RDFMimeType.SPARQL_RESULTS_XML,
RDFMimeType.SPARQL_RESULTS_JSON,
RDFMimeType.BOOLEAN_RESULT
];
const RDF_FORMATS = [
RDFMimeType.RDF_XML,
RDFMimeType.N_TRIPLES,
RDFMimeType.TURTLE,
RDFMimeType.N3,
RDFMimeType.N_QUADS,
RDFMimeType.JSON_LD,
RDFMimeType.RDF_JSON,
RDFMimeType.TRIX,
RDFMimeType.TRIG,
RDFMimeType.BINARY_RDF,
RDFMimeType.TURTLE_STAR,
RDFMimeType.TRIG_STAR
];
const QUERY_OPERATION_TYPES = [
QueryContentType.X_WWW_FORM_URLENCODED,
QueryContentType.SPARQL_QUERY
];
const QUERY_TO_RESPONSE_TYPE_FORMATS_MAPPING = {
SELECT: SELECT_QUERY_RESULT_TYPES,
DESCRIBE: RDF_FORMATS,
CONSTRUCT: RDF_FORMATS,
ASK: ASK_QUERY_RESULT_TYPES
};
/**
* Payload object holding common request parameters applicable for the query
* endpoint.
*
* Mandatory parameters are: query, queryType and responseType. Validation on
* parameters is executed when <code>QueryPayload.getParams()</code> is invoked.
*
* Content type parameter which is used for setting the Content-Type http header
* is optional and by default
* <code>application/x-www-form-urlencoded</code> type is set.
*
* @class
* @author Mihail Radkov
* @author Svilen Velikov
*/
class GetQueryPayload extends QueryPayload {
/**
* Does basic initialization.
*/
constructor() {
super();
this.contentType = QueryContentType.X_WWW_FORM_URLENCODED;
}
/**
* @param {string} query The query as string to be evaluated.
* @return {UpdateQueryPayload}
* @throws {Error} if the query is not a string
*/
setQuery(query) {
if (typeof query !== 'string') {
throw new Error('Query must be a string!');
}
this.payload.query = query;
return this;
}
/**
* @return {string} a query which was populated in the payload.
*/
getQuery() {
return this.payload.query;
}
/**
* @param {string} [queryLn] the query language that is used for the query.
* @return {GetQueryPayload}
* @throws {Error} if the query language is not one of {@link QueryLanguage}
*/
setQueryLn(queryLn) {
const supportedLanguages = Object.values(QueryLanguage);
if (typeof queryLn !== 'string' ||
supportedLanguages.indexOf(queryLn) === -1) {
throw new Error(`Query language must be one of ${supportedLanguages}!`);
}
this.payload.queryLn = queryLn;
return this;
}
/**
* Populates an optional $key:value binding in the payload. Existing bindings
* will be overridden.
*
* @param {string} [binding] A variable binding name which may appear in the
* query and can be bound to a specific value provided outside
* of the actual query.
* @param {string} [value] A variable's binding value. See the binding comment
* @return {GetQueryPayload}
* @throws {Error} if the binding or the value is not a string
*/
addBinding(binding, value) {
if (typeof binding !== 'string' || typeof value !== 'string') {
throw new Error('Binding and value must be strings!');
}
this.payload[binding] = value;
return this;
}
/**
* @param {boolean} [distinct] Specifies if only distinct query solutions
* should be returned.
* @return {GetQueryPayload}
* @throws {Error} if the parameter is not a boolean
*/
setDistinct(distinct) {
if (typeof distinct !== 'boolean') {
throw new Error('Distinct must be a boolean!');
}
this.payload.distinct = distinct;
return this;
}
/**
* @param {number} limit specifies the maximum number of query solutions to
* return.
* @return {GetQueryPayload}
* @throws {Error} if the limit is not a non negative number
*/
setLimit(limit) {
if (typeof limit !== 'number' || limit < 0) {
throw new Error('Limit must be a non negative number!');
}
this.payload.limit = limit;
return this;
}
/**
* @param {number} [offset] Specifies the number of query solutions to skip.
* @return {GetQueryPayload}
* @throws {Error} if the offset is not a non negative number
*/
setOffset(offset) {
if (typeof offset !== 'number' || offset < 0) {
throw new Error('Offset must be a non negative number!');
}
this.payload.offset = offset;
return this;
}
/**
* @inheritDoc
* @throws {Error} if the validation does not pass
*/
validateParams() {
if (!this.payload.query) {
throw new Error('Parameter query is mandatory!');
}
if (!this.getQueryType()) {
throw new Error('Parameter queryType is mandatory!');
}
if (!this.getResponseType()) {
throw new Error('Parameter responseType is mandatory!');
}
const responseType = this.getResponseType();
const allowedFormats =
QUERY_TO_RESPONSE_TYPE_FORMATS_MAPPING[this.getQueryType()];
if (!this.isResponseTypeSupported(responseType, allowedFormats)) {
throw new Error(`Invalid responseType=${responseType}
for ${this.getQueryType()} query! Must be one of ${allowedFormats}`);
}
return true;
}
/**
* Verifies that responseType is one of the expected types.
*
* @private
* @param {string} responseType
* @param {Array<string>} formats
* @return {boolean} true if responseType is one of the expected types and
* false otherwise.
*/
isResponseTypeSupported(responseType, formats) {
return formats.indexOf(responseType) !== -1;
}
// -----------------------------------------------------
// Configuration properties get/set methods follow below
// -----------------------------------------------------
/**
* A mandatory parameter which is used for resolving the Accept http header
* required by the RDF store.
*
* @param {string} responseType
* @return {GetQueryPayload}
* @throws {Error} if the response type is not one of {@link RDFMimeType}
*/
setResponseType(responseType) {
const supportedTypes = Object.values(RDFMimeType);
if (typeof responseType !== 'string' ||
supportedTypes.indexOf(responseType) === -1) {
throw new Error(`Response type must be one of ${supportedTypes}!`);
}
this.responseType = responseType;
return this;
}
/**
* @return {string} response type which was populated in this payload.
*/
getResponseType() {
return this.responseType;
}
/**
* A mandatory parameter used for resolving request headers and resolving
* the response parsers.
*
* @param {QueryType} queryType
* @return {GetQueryPayload}
* @throws {Error} if the query type is not one of {@link QueryType}
*/
setQueryType(queryType) {
const supportedTypes = Object.values(QueryType);
if (typeof queryType !== 'string' ||
supportedTypes.indexOf(queryType) === -1) {
throw new Error(`Query type must be one of ${supportedTypes}!`);
}
this.queryType = queryType;
return this;
}
/**
* @return {string} query type which was populated in this payload. The value
* is one of the {@link QueryType} enum values.
*/
getQueryType() {
return this.queryType;
}
/**
* @inheritDoc
*/
getSupportedContentTypes() {
return QUERY_OPERATION_TYPES;
}
}
module.exports = GetQueryPayload;