Skip to content

Commit

Permalink
feat: Return errors (#55)
Browse files Browse the repository at this point in the history
* feat: Return errors

* Add two error classes: `ServerError` (which we already use)
  and `TimeoutError` (which we can start using when we support
  timeouts).
* Deprecate `terms` and recommend `result` instead.

* Simplify type names

* Make Result type more specific: TermsResult

To differentiate from a future SourceResult type.
  • Loading branch information
ddeboer authored Nov 6, 2020
1 parent e02864c commit e27695e
Show file tree
Hide file tree
Showing 6 changed files with 143 additions and 70 deletions.
60 changes: 38 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,23 +133,31 @@ query Terms {
alternateName
}
}
terms {
uri
prefLabel
altLabel
hiddenLabel
scopeNote
broader {
uri
prefLabel
}
narrower {
uri
prefLabel
result {
__typename
... on Terms {
terms {
uri
prefLabel
altLabel
hiddenLabel
scopeNote
broader {
uri
prefLabel
}
narrower {
uri
prefLabel
}
related {
uri
prefLabel
}
}
}
related {
uri
prefLabel
... on Error {
message
}
}
}
Expand All @@ -169,12 +177,20 @@ query Terms {
alternateName
}
}
terms {
uri
prefLabel
altLabel
hiddenLabel
scopeNote
result {
__typename
... on Terms {
terms {
uri
prefLabel
altLabel
hiddenLabel
scopeNote
}
}
... on Error {
message
}
}
}
}
Expand Down
10 changes: 7 additions & 3 deletions src/commands/sources/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {cli} from 'cli-ux';
import {Command, flags} from '@oclif/command';
import {DistributionsService} from '../../services/distributions';
import * as Logger from '../../helpers/logger';
import {QueryResult} from '../../services/query';
import {Error, TermsResult} from '../../services/query';
import * as RDF from 'rdf-js';
import {Term} from '../../services/terms';
import {Catalog, IRI} from '@netwerk-digitaal-erfgoed/network-of-terms-catalog';
Expand Down Expand Up @@ -36,8 +36,12 @@ export class QuerySourcesCommand extends Command {
}),
};

protected render(results: QueryResult[], catalog: Catalog): void {
const rowsPerDistribution = results.map((result: QueryResult): Row[] => {
protected render(results: TermsResult[], catalog: Catalog): void {
const rowsPerDistribution = results.map((result: TermsResult): Row[] => {
if (result instanceof Error) {
return [];
}

return result.terms.map(
(term: Term): Row => {
return {
Expand Down
87 changes: 58 additions & 29 deletions src/server/resolvers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {DistributionsService} from '../services/distributions';
import {QueryResult} from '../services/query';
import {Error, TermsResult, ServerError, TimeoutError} from '../services/query';
import * as RDF from 'rdf-js';
import {Term} from '../services/terms';
import {
Expand Down Expand Up @@ -28,39 +28,55 @@ async function queryTerms(object: any, args: any, context: any): Promise<any> {
),
query: args.query,
});
return results.map((result: QueryResult) => {
return results.map((result: TermsResult) => {
if (result instanceof Error) {
return {
source: source(
result.distribution,
context.catalog.getDatasetByDistributionIri(result.distribution.iri)
),
result,
terms: [], // For BC.
};
}

const terms = result.terms.map((term: Term) => {
return {
uri: term.id!.value,
prefLabel: term.prefLabels.map(
(prefLabel: RDF.Term) => prefLabel.value
),
altLabel: term.altLabels.map((altLabel: RDF.Term) => altLabel.value),
hiddenLabel: term.hiddenLabels.map(
(hiddenLabel: RDF.Term) => hiddenLabel.value
),
scopeNote: term.scopeNotes.map(
(scopeNote: RDF.Term) => scopeNote.value
),
broader: term.broaderTerms.map(related => ({
uri: related.id.value,
prefLabel: related.prefLabels.map(prefLabel => prefLabel.value),
})),
narrower: term.narrowerTerms.map(related => ({
uri: related.id.value,
prefLabel: related.prefLabels.map(prefLabel => prefLabel.value),
})),
related: term.relatedTerms.map(related => ({
uri: related.id.value,
prefLabel: related.prefLabels.map(prefLabel => prefLabel.value),
})),
};
});

return {
source: source(
result.distribution,
context.catalog.getDatasetByDistributionIri(result.distribution.iri)
),
terms: result.terms.map((term: Term) => {
return {
uri: term.id!.value,
prefLabel: term.prefLabels.map(
(prefLabel: RDF.Term) => prefLabel.value
),
altLabel: term.altLabels.map((altLabel: RDF.Term) => altLabel.value),
hiddenLabel: term.hiddenLabels.map(
(hiddenLabel: RDF.Term) => hiddenLabel.value
),
scopeNote: term.scopeNotes.map(
(scopeNote: RDF.Term) => scopeNote.value
),
broader: term.broaderTerms.map(related => ({
uri: related.id.value,
prefLabel: related.prefLabels.map(prefLabel => prefLabel.value),
})),
narrower: term.narrowerTerms.map(related => ({
uri: related.id.value,
prefLabel: related.prefLabels.map(prefLabel => prefLabel.value),
})),
related: term.relatedTerms.map(related => ({
uri: related.id.value,
prefLabel: related.prefLabels.map(prefLabel => prefLabel.value),
})),
};
}),
terms, // For BC.
result: {
terms: terms,
},
};
});
}
Expand All @@ -83,4 +99,17 @@ export const resolvers = {
sources: listSources,
terms: queryTerms,
},
TermsResult: {
resolveType(result: TermsResult) {
if (result instanceof TimeoutError) {
return 'TimeoutError';
}

if (result instanceof ServerError) {
return 'ServerError';
}

return 'Terms';
},
},
};
25 changes: 22 additions & 3 deletions src/server/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,32 @@ export const schema = `
prefLabel: [String]!
}
type Terms {
type TermsQueryResult {
source: Source!
terms: [Term]!
terms: [Term]! @deprecated(reason: "Use 'result' instead")
result: TermsResult!
}
type Query {
terms(sources: [ID]!, query: String!): [Terms]
terms(sources: [ID]!, query: String!): [TermsQueryResult]
sources: [Source]
}
union TermsResult = Terms | TimeoutError | ServerError
type Terms {
terms: [Term]
}
type TimeoutError implements Error {
message: String!
}
type ServerError implements Error {
message: String!
}
interface Error {
message: String!
}
`;
6 changes: 3 additions & 3 deletions src/services/distributions.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as Joi from '@hapi/joi';
import Pino from 'pino';
import {QueryResult, QueryTermsService} from './query';
import {TermsResult, QueryTermsService} from './query';
import {Catalog, IRI} from '@netwerk-digitaal-erfgoed/network-of-terms-catalog';
import {IActorInitSparqlArgs} from '@comunica/actor-init-sparql/lib/ActorInitSparql-browser';

Expand Down Expand Up @@ -48,7 +48,7 @@ export class DistributionsService {
this.comunica = args.comunica;
}

async query(options: QueryOptions): Promise<QueryResult> {
async query(options: QueryOptions): Promise<TermsResult> {
const args = Joi.attempt(options, schemaQuery);
this.logger.info(`Preparing to query source "${args.source}"...`);
const dataset = await this.catalog.getDatasetByDistributionIri(args.source);
Expand All @@ -65,7 +65,7 @@ export class DistributionsService {
return queryService.run();
}

async queryAll(options: QueryAllOptions): Promise<QueryResult[]> {
async queryAll(options: QueryAllOptions): Promise<TermsResult[]> {
const args = Joi.attempt(options, schemaQueryAll);
const requests = args.sources.map((source: IRI) =>
this.query({source, query: args.query})
Expand Down
25 changes: 15 additions & 10 deletions src/services/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,17 @@ const schemaConstructor = Joi.object({
comunica: Joi.object().required(),
});

export interface QueryResult {
distribution: Distribution;
terms: Term[];
export type TermsResult = Terms | TimeoutError | ServerError;

export class Terms {
constructor(readonly distribution: Distribution, readonly terms: Term[]) {}
}

export class Error {
constructor(readonly distribution: Distribution, readonly message: string) {}
}
export class TimeoutError extends Error {}
export class ServerError extends Error {}

export class QueryTermsService {
protected logger: Pino.Logger;
Expand All @@ -52,7 +59,7 @@ export class QueryTermsService {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
protected getConfig(): any {
const logger = new LoggerPino({logger: this.logger});
const config = {
return {
log: logger,
sources: [
{
Expand All @@ -64,18 +71,16 @@ export class QueryTermsService {
'?query': literal(this.query),
}),
};
return config;
}

async run(): Promise<QueryResult> {
async run(): Promise<TermsResult> {
this.logger.info(
`Querying "${this.distribution.endpoint}" with "${this.query}"...`
);
const config = this.getConfig();
const timer = new Hoek.Bench();
const result = (await this.engine.query(
this.distribution.query,
config
this.getConfig()
)) as IActorQueryOperationOutputQuads;

return new Promise(resolve => {
Expand All @@ -84,7 +89,7 @@ export class QueryTermsService {
this.logger.error(
`An error occurred when querying "${this.distribution.endpoint}": ${error}`
);
resolve({distribution: this.distribution, terms: []});
resolve(new ServerError(this.distribution, error.message));
});
result.quadStream.on('data', (quad: RDF.Quad) =>
termsTransformer.fromQuad(quad)
Expand All @@ -96,7 +101,7 @@ export class QueryTermsService {
this.distribution.endpoint
}" in ${PrettyMilliseconds(timer.elapsed())}`
);
resolve({distribution: this.distribution, terms});
resolve(new Terms(this.distribution, terms));
});
});
}
Expand Down

0 comments on commit e27695e

Please sign in to comment.