From 50e68dd736ed2164d754ea995accbe51d7241aa2 Mon Sep 17 00:00:00 2001 From: Adler Faulkner Date: Mon, 4 Dec 2023 11:10:22 -0800 Subject: [PATCH] feat(composite-mappings): move composites to mappings, add exists operator --- src/SklEngine.ts | 362 +++++++++--------- src/index.ts | 1 + src/mapping/Mapper.ts | 6 +- src/storage/FindOperator.ts | 5 +- src/storage/operator/Exists.ts | 6 + .../sparql/SparqlQueryBuilder.ts | 23 +- src/util/Types.ts | 78 ++-- test/assets/schemas/core.json | 51 ++- test/assets/schemas/count-in-series-verb.json | 17 +- .../schemas/destroy-in-series-verb.json | 9 +- test/assets/schemas/divide-function.json | 1 - .../assets/schemas/exists-in-series-verb.json | 9 +- .../exists-with-parameter-reference.json | 9 +- test/assets/schemas/find-in-series-verb.json | 9 +- .../schemas/findAll-in-series-verb.json | 9 +- test/assets/schemas/get-dropbox-file.json | 15 +- test/assets/schemas/get-stubhub-events.json | 6 +- .../schemas/get-ticketmaster-events.json | 8 +- test/assets/schemas/parallel-verb.json | 47 ++- test/assets/schemas/save-in-series-verb.json | 8 +- .../assets/schemas/series-verb-no-verbId.json | 22 +- .../series-verb-with-pre-processing.json | 20 +- test/assets/schemas/series-verb.json | 15 +- test/assets/schemas/trigger.json | 1 - .../src/assets/get-ticketmaster-events.json | 8 +- test/unit/SklEngine.test.ts | 81 ++-- .../sparql/SparqlQueryBuilder.test.ts | 362 ++++++++++-------- 27 files changed, 649 insertions(+), 539 deletions(-) create mode 100644 src/storage/operator/Exists.ts diff --git a/src/SklEngine.ts b/src/SklEngine.ts index 82439b7..d4ce75f 100644 --- a/src/SklEngine.ts +++ b/src/SklEngine.ts @@ -16,8 +16,10 @@ import { Mapper } from './mapping/Mapper'; import type { SklEngineOptions } from './SklEngineOptions'; import type { FindOperator } from './storage/FindOperator'; import type { FindAllOptions, FindOneOptions, FindOptionsWhere } from './storage/FindOptionsTypes'; +import { Exists } from './storage/operator/Exists'; import { In } from './storage/operator/In'; import { InversePath } from './storage/operator/InversePath'; +import { Not } from './storage/operator/Not'; import { OneOrMorePath } from './storage/operator/OneOrMorePath'; import { SequencePath } from './storage/operator/SequencePath'; import { ZeroOrMorePath } from './storage/operator/ZeroOrMorePath'; @@ -28,20 +30,20 @@ import type { OrArray, Entity, OperationResponse, - VerbMapping, - VerbIntegrationMapping, - VerbNounMapping, - MappingWithVerbMapping, - MappingWithOperationMapping, MappingWithReturnValueMapping, MappingWithParameterMapping, SeriesVerbArgs, Verb, - TriggerVerbMapping, + TriggerMapping, MappingWithParameterReference, RdfList, VerbConfig, JSONObject, + VerbMapping, + Mapping, + MappingWithSeries, + MappingWithParallel, + JSONValue, } from './util/Types'; import { convertJsonLdToQuads, @@ -324,7 +326,7 @@ export class SKLEngine { } public async performMapping( - args: JSONObject, + args: JSONValue, mapping: OrArray, frame?: Record, verbConfig?: VerbConfig, @@ -355,14 +357,14 @@ export class SKLEngine { } } - private async findTriggerVerbMapping(integration: string): Promise { + private async findTriggerVerbMapping(integration: string): Promise { return (await this.findBy( { type: SKL.TriggerVerbMapping, [SKL.integration]: integration, }, `Failed to find a Trigger Verb mapping for integration ${integration}`, - )) as TriggerVerbMapping; + )) as TriggerMapping; } private async executeVerbByName( @@ -386,23 +388,14 @@ export class SKLEngine { if (verbConfig?.callbacks?.onVerbStart) { verbConfig.callbacks.onVerbStart(verb['@id'], verbArgs); } - let verbReturnValue: any; - if (SKL.returnValueMapping in verb) { - verbReturnValue = await this.performReturnValueMappingWithFrame( - verbArgs, - verb as MappingWithReturnValueMapping, - verbConfig, - ); - } else if (SKL.series in verb) { - verbReturnValue = await this.executeSeriesVerb(verb, verbArgs, verbConfig); - } else if (SKL.parallel in verb) { - verbReturnValue = await this.executeParallelVerb(verb, verbArgs, verbConfig); - } else if (verbArgs.noun) { - verbReturnValue = await this.executeNounMappingVerb(verb, verbArgs, verbConfig); - } else if (verbArgs.account) { - verbReturnValue = await this.executeIntegrationMappingVerb(verb, verbArgs, verbConfig); - } else { - throw new Error(`Verb must be a composite or its parameters must include either a noun or an account.`); + const { mapping, account } = await this.findMappingForVerbContextually(verb['@id'], verbArgs); + const shouldValidate = this.shouldValidate(verbConfig); + if (shouldValidate) { + await this.assertVerbParamsMatchParameterSchemas(verbArgs, verb); + } + const verbReturnValue = await this.executeMapping(mapping, verbArgs, verbConfig, verb, account); + if (shouldValidate) { + await this.assertVerbReturnValueMatchesReturnTypeSchema(verbReturnValue, verb); } this.globalCallbacks?.onVerbEnd?.(verb['@id'], verbReturnValue); if (verbConfig?.callbacks?.onVerbEnd) { @@ -411,24 +404,132 @@ export class SKLEngine { return verbReturnValue; } + private async findMappingForVerbContextually( + verbId: string, + args: JSONObject, + ): Promise<{ mapping: VerbMapping; account?: Entity }> { + if (args.mapping) { + const mapping = await this.findByIfExists({ id: args.mapping as string }); + if (!mapping) { + throw new Error(`Mapping ${args.mapping as string} not found.`); + } + return { mapping: mapping as VerbMapping }; + } + if (args.noun) { + const mapping = await this.findVerbNounMapping(verbId, args.noun as string); + if (mapping) { + return { mapping }; + } + } + if (args.account) { + const account = await this.findBy({ id: args.account as string }); + const integrationId = (account[SKL.integration] as ReferenceNodeObject)['@id']; + const mapping = await this.findVerbIntegrationMapping(verbId, integrationId); + if (mapping) { + return { mapping, account }; + } + } + + const mappings = await this.findAllBy({ + type: SKL.Mapping, + [SKL.verb]: verbId, + [SKL.integration]: Not(Exists()), + [SKL.noun]: Not(Exists()), + }); + if (mappings.length === 1) { + return { mapping: mappings[0] as VerbMapping }; + } + if (mappings.length > 1) { + throw new Error('Multiple mappings found for verb, please specify one.'); + } + if (args.noun) { + throw new Error(`Mapping between noun ${args.noun as string} and verb ${verbId} not found.`); + } + if (args.account) { + throw new Error(`Mapping between account ${args.account as string} and verb ${verbId} not found.`); + } + throw new Error(`No mapping found.`); + } + + private async executeMapping( + mapping: Mapping, + args: JSONObject, + verbConfig?: VerbConfig, + verb?: Verb, + account?: Entity, + ): Promise> { + args = await this.addPreProcessingMappingToArgs(mapping, args, verbConfig); + + const mappedArgs = await this.performParameterMappingOnArgsIfDefined( + args, + mapping as MappingWithParameterMapping, + verbConfig, + false, + ); + + let returnValue: OrArray = mappedArgs; + if (SKL.operationId in mapping || SKL.operationMapping in mapping) { + returnValue = await this.executeOperationMapping( + mapping, + mappedArgs, + args, + account!, + verbConfig, + ) as NodeObject; + } else if (SKL.series in mapping) { + returnValue = await this.executeSeriesMapping( + mapping as MappingWithSeries, + args, + verbConfig, + ); + } else if (SKL.parallel in mapping) { + returnValue = await this.executeParallelMapping( + mapping as MappingWithParallel, + args, + verbConfig, + ); + } else if (SKL.verbId in mapping || SKL.verbMapping in mapping) { + returnValue = await this.executeVerbMapping(mapping, mappedArgs, verbConfig); + } + + return await this.performReturnValueMappingWithFrameIfDefined( + returnValue as JSONValue, + mapping as MappingWithReturnValueMapping, + verbConfig, + ); + } + private shouldValidate(verbConfig?: VerbConfig): boolean { return verbConfig?.disableValidation === undefined ? this.disableValidation !== true : !verbConfig.disableValidation; } - private async executeSeriesVerb(verb: Verb, args: JSONObject, verbConfig?: VerbConfig): Promise> { - const shouldValidate = this.shouldValidate(verbConfig); - if (shouldValidate) { - await this.assertVerbParamsMatchParameterSchemas(args, verb); - } - const seriesVerbMappingsList = this.rdfListToArray(verb[SKL.series]!); + private async executeOperationMapping( + mapping: Mapping, + mappedArgs: JSONObject, + originalArgs: JSONObject, + account: Entity, + verbConfig?: VerbConfig, + ): Promise { + const operationInfo = await this.performOperationMappingWithArgs(originalArgs, mapping, verbConfig); + return await this.performOperation( + operationInfo, + mappedArgs, + originalArgs, + account, + verbConfig, + ); + } + + private async executeSeriesMapping( + mapping: MappingWithSeries, + args: JSONObject, + verbConfig?: VerbConfig, + ): Promise> { + const seriesVerbMappingsList = this.rdfListToArray(mapping[SKL.series]!); const seriesVerbArgs = { originalVerbParameters: args, previousVerbReturnValue: {}}; - const returnValue = await this.executeSeriesFromList(seriesVerbMappingsList, seriesVerbArgs, verbConfig); - if (shouldValidate) { - await this.assertVerbReturnValueMatchesReturnTypeSchema(returnValue, verb); - } - return returnValue; + return await this.executeSeriesFromList(seriesVerbMappingsList, seriesVerbArgs, verbConfig); } private rdfListToArray(list: { '@list': VerbMapping[] } | RdfList): VerbMapping[] { @@ -444,12 +545,12 @@ export class SKLEngine { } private async executeSeriesFromList( - list: VerbMapping[], + list: Mapping[], args: SeriesVerbArgs, verbConfig?: VerbConfig, ): Promise> { const nextVerbMapping = list[0]; - const returnValue = await this.executeVerbFromVerbMapping(nextVerbMapping, args, verbConfig); + const returnValue = await this.executeMapping(nextVerbMapping, args, verbConfig); if (list.length > 1) { return await this.executeSeriesFromList( list.slice(1), @@ -460,57 +561,42 @@ export class SKLEngine { return returnValue; } - private async executeVerbFromVerbMapping( - verbMapping: VerbMapping, + private async executeVerbMapping( + verbMapping: Mapping, args: JSONObject, verbConfig?: VerbConfig, ): Promise> { - args = await this.addPreProcessingMappingToArgs(verbMapping, args, verbConfig); const verbId = await this.performVerbMappingWithArgs(args, verbMapping, verbConfig); if (verbId) { - const verbArgs = await this.performParameterMappingOnArgsIfDefined( - { ...args, verbId }, - verbMapping, - verbConfig, - false, - ); if (verbId === SKL_ENGINE.update) { - await this.updateEntityFromVerbArgs(verbArgs); + await this.updateEntityFromVerbArgs(args); return {}; } if (verbId === SKL_ENGINE.save) { - return await this.saveEntityOrEntitiesFromVerbArgs(verbArgs); + return await this.saveEntityOrEntitiesFromVerbArgs(args); } if (verbId === SKL_ENGINE.destroy) { - return await this.destroyEntityOrEntitiesFromVerbArgs(verbArgs); + return await this.destroyEntityOrEntitiesFromVerbArgs(args); } if (verbId === SKL_ENGINE.findAll) { - return await this.findAll(verbArgs); + return await this.findAll(args); } if (verbId === SKL_ENGINE.find) { - return await this.find(verbArgs); + return await this.find(args); } if (verbId === SKL_ENGINE.count) { - return await this.countAndWrapValueFromVerbArgs(verbArgs); + return await this.countAndWrapValueFromVerbArgs(args); } if (verbId === SKL_ENGINE.exists) { - return await this.existsAndWrapValueFromVerbArgs(verbArgs); - } - const returnValue = await this.findAndExecuteVerb(verbId, verbArgs, verbConfig); - if (SKL.returnValueMapping in verbMapping) { - return await this.performReturnValueMappingWithFrame( - returnValue as JSONObject, - verbMapping as MappingWithReturnValueMapping, - verbConfig, - ); + return await this.existsAndWrapValueFromVerbArgs(args); } - return returnValue; + return await this.findAndExecuteVerb(verbId, args, verbConfig); } return {}; } private async addPreProcessingMappingToArgs( - verbMapping: VerbMapping, + verbMapping: Mapping, args: JSONObject, verbConfig?: VerbConfig, ): Promise { @@ -567,82 +653,30 @@ export class SKLEngine { return await this.executeVerb(verb, args, verbConfig); } - private async executeParallelVerb(verb: Verb, args: JSONObject, verbConfig?: VerbConfig): Promise { - const shouldValidate = this.shouldValidate(verbConfig); - if (shouldValidate) { - await this.assertVerbParamsMatchParameterSchemas(args, verb); - } - const parallelVerbMappings = ensureArray(verb[SKL.parallel] as unknown as OrArray); + private async executeParallelMapping( + mapping: MappingWithParallel, + args: JSONObject, + verbConfig?: VerbConfig, + ): Promise { + const parallelVerbMappings = ensureArray(mapping[SKL.parallel] as unknown as OrArray); const nestedReturnValues = await Promise.all>>( parallelVerbMappings.map((verbMapping): Promise> => - this.executeVerbFromVerbMapping(verbMapping, args, verbConfig)), + this.executeMapping(verbMapping, args, verbConfig)), ); - const allReturnValues = nestedReturnValues.flat(); - if (shouldValidate) { - await this.assertVerbReturnValueMatchesReturnTypeSchema(allReturnValues, verb); - } - return allReturnValues; + return nestedReturnValues.flat(); } - private async executeIntegrationMappingVerb( - verb: Verb, - args: JSONObject, - verbConfig?: VerbConfig, - ): Promise { - const shouldValidate = this.shouldValidate(verbConfig); - if (shouldValidate) { - await this.assertVerbParamsMatchParameterSchemas(args, verb); - } - const account = await this.findBy({ id: args.account as string }); - const integrationId = (account[SKL.integration] as ReferenceNodeObject)['@id']; - const mapping = await this.findVerbIntegrationMapping(verb['@id'], integrationId); - const operationArgs = await this.performParameterMappingOnArgsIfDefined( - args, - mapping as MappingWithParameterMapping, - verbConfig, - ); - const operationInfo = await this.performOperationMappingWithArgs(args, mapping, verbConfig); - const rawReturnValue = await this.performOperation( - operationInfo, - operationArgs, - args, - account, - undefined, - verbConfig, - ); - if (operationInfo[SKL.schemeName] && rawReturnValue.data.authorizationUrl) { - return { - '@type': '@json', - '@value': rawReturnValue.data, - }; - } - - if (mapping[SKL.returnValueMapping]) { - const mappedReturnValue = await this.performReturnValueMappingWithFrame( - rawReturnValue, - mapping as MappingWithReturnValueMapping, - verbConfig, - verb, - ); - if (shouldValidate) { - await this.assertVerbReturnValueMatchesReturnTypeSchema(mappedReturnValue, verb); - } - return mappedReturnValue; - } - return rawReturnValue as unknown as NodeObject; - } - - private async findVerbIntegrationMapping(verbId: string, integrationId: string): Promise { + private async findVerbIntegrationMapping(verbId: string, integrationId: string): Promise { return (await this.findBy({ type: SKL.VerbIntegrationMapping, [SKL.verb]: verbId, [SKL.integration]: integrationId, - })) as VerbIntegrationMapping; + })) as VerbMapping; } private async performOperationMappingWithArgs( - args: JSONObject, - mapping: MappingWithOperationMapping, + args: JSONValue, + mapping: Mapping, verbConfig?: VerbConfig, ): Promise { if (mapping[SKL.operationId]) { @@ -664,8 +698,8 @@ export class SKLEngine { operationArgs: JSONObject, originalArgs: JSONObject, account: Entity, - securityCredentials?: Entity, verbConfig?: VerbConfig, + securityCredentials?: Entity, ): Promise { if (operationInfo[SKL.schemeName]) { return await this.performOauthSecuritySchemeStageWithCredentials( @@ -713,21 +747,20 @@ export class SKLEngine { }; } - private async performReturnValueMappingWithFrame( - returnValue: JSONObject, + private async performReturnValueMappingWithFrameIfDefined( + returnValue: JSONValue, mapping: MappingWithReturnValueMapping, verbConfig?: VerbConfig, - verb?: Entity, ): Promise { - return await this.performMapping( - returnValue, - mapping[SKL.returnValueMapping], - { - ...getValueIfDefined(verb?.[SKL.returnValueFrame]), - ...getValueIfDefined(mapping[SKL.returnValueFrame]), - }, - verbConfig, - ); + if (SKL.returnValueMapping in mapping) { + return await this.performMapping( + returnValue, + mapping[SKL.returnValueMapping], + getValueIfDefined(mapping[SKL.returnValueFrame]), + verbConfig, + ); + } + return returnValue as NodeObject; } private async performParameterMappingOnArgsIfDefined( @@ -783,48 +816,20 @@ export class SKLEngine { return executor; } - private async executeNounMappingVerb(verb: Entity, args: JSONObject, verbConfig?: VerbConfig): Promise { - const mapping = await this.findVerbNounMapping(verb['@id'], args.noun as string); - if (mapping[SKL.returnValueMapping]) { - return await this.performReturnValueMappingWithFrame( - args, - mapping as MappingWithReturnValueMapping, - verbConfig, - verb, - ); - } - const verbArgs = await this.performParameterMappingOnArgsIfDefined(args, mapping, verbConfig, false); - const verbId = await this.performVerbMappingWithArgs(args, mapping, verbConfig); - const mappedVerb = (await this.findBy({ id: verbId })) as Verb; - - this.globalCallbacks?.onVerbStart?.(verb['@id'], verbArgs); - if (verbConfig?.callbacks?.onVerbStart) { - verbConfig.callbacks.onVerbStart(verb['@id'], verbArgs); - } - - const returnValue = await this.executeIntegrationMappingVerb(mappedVerb, verbArgs, verbConfig); - - this.globalCallbacks?.onVerbEnd?.(verb['@id'], returnValue); - if (verbConfig?.callbacks?.onVerbEnd) { - verbConfig.callbacks.onVerbEnd(verb['@id'], returnValue); - } - return returnValue; - } - - private async findVerbNounMapping(verbId: string, noun: string): Promise { - return (await this.findBy({ + private async findVerbNounMapping(verbId: string, noun: string): Promise { + return (await this.findByIfExists({ type: SKL.VerbNounMapping, [SKL.verb]: verbId, [SKL.noun]: InversePath({ subPath: ZeroOrMorePath({ subPath: RDFS.subClassOf as string }), value: noun, }), - })) as VerbNounMapping; + })) as VerbMapping; } private async performVerbMappingWithArgs( - args: JSONObject, - mapping: MappingWithVerbMapping, + args: JSONValue, + mapping: Mapping, verbConfig?: VerbConfig, ): Promise { if (mapping[SKL.verbId]) { @@ -925,14 +930,13 @@ export class SKLEngine { operationArgs, args, account, - securityCredentials, verbConfig, + securityCredentials, ); - const mappedReturnValue = await this.performReturnValueMappingWithFrame( + const mappedReturnValue = await this.performReturnValueMappingWithFrameIfDefined( rawReturnValue, mapping as MappingWithReturnValueMapping, verbConfig, - getOauthTokenVerb, ); await this.assertVerbReturnValueMatchesReturnTypeSchema(mappedReturnValue, getOauthTokenVerb); const bearerToken = getValueIfDefined(mappedReturnValue[SKL.bearerToken]); @@ -1000,7 +1004,7 @@ export class SKLEngine { } else if (Object.keys(returnValue).length > 0) { if (returnValue['@id']) { returnTypeSchemaObject[SHACL.targetNode] = { '@id': returnValue['@id'] }; - } else { + } else if (returnValue['@type']) { returnTypeSchemaObject[SHACL.targetClass] = { '@id': Array.isArray(returnValue['@type']) ? returnValue['@type'][0] : returnValue['@type']!, }; diff --git a/src/index.ts b/src/index.ts index e1787a9..5c89bbc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,6 +3,7 @@ export * from './mapping/Mapper'; // Storage/Operator export * from './storage/operator/Equal'; +export * from './storage/operator/Exists'; export * from './storage/operator/GreaterThan'; export * from './storage/operator/GreaterThanOrEqual'; export * from './storage/operator/In'; diff --git a/src/mapping/Mapper.ts b/src/mapping/Mapper.ts index 4da039f..2f768d7 100644 --- a/src/mapping/Mapper.ts +++ b/src/mapping/Mapper.ts @@ -2,7 +2,7 @@ import * as RmlParser from '@comake/rmlmapper-js'; import type { NodeObject } from 'jsonld'; import jsonld from 'jsonld'; -import type { OrArray, JSONObject } from '../util/Types'; +import type { OrArray, JSONValue } from '../util/Types'; export interface MapperArgs { functions?: Record any>; @@ -16,7 +16,7 @@ export class Mapper { } public async apply( - data: JSONObject, + data: JSONValue, mapping: OrArray, frame: Record, ): Promise { @@ -24,7 +24,7 @@ export class Mapper { return await this.frame(result, frame); } - private async doMapping(data: JSONObject, mapping: OrArray): Promise { + private async doMapping(data: JSONValue, mapping: OrArray): Promise { const sources = { 'input.json': JSON.stringify(data) }; const options = { functions: this.functions }; const mappingNodeObject = Array.isArray(mapping) diff --git a/src/storage/FindOperator.ts b/src/storage/FindOperator.ts index 8f4b53d..484ce5a 100644 --- a/src/storage/FindOperator.ts +++ b/src/storage/FindOperator.ts @@ -2,6 +2,7 @@ export type FindOperatorType = | 'in' | 'not' | 'equal' +| 'exists' | 'gt' | 'gte' | 'lt' @@ -16,13 +17,13 @@ export type FindOperatorType = export interface FindOperatorArgs { operator: TType; - value: T | FindOperator; + value?: T | FindOperator; } export class FindOperator { public readonly type = 'operator'; public readonly operator: TType; - public readonly value: T | FindOperator; + public readonly value?: T | FindOperator; public constructor(args: FindOperatorArgs) { this.operator = args.operator; diff --git a/src/storage/operator/Exists.ts b/src/storage/operator/Exists.ts new file mode 100644 index 0000000..a93e14e --- /dev/null +++ b/src/storage/operator/Exists.ts @@ -0,0 +1,6 @@ +import { FindOperator } from '../FindOperator'; + +// eslint-disable-next-line @typescript-eslint/naming-convention +export function Exists(): FindOperator { + return new FindOperator({ operator: 'exists' }); +} diff --git a/src/storage/query-adapter/sparql/SparqlQueryBuilder.ts b/src/storage/query-adapter/sparql/SparqlQueryBuilder.ts index 7e4a07f..2fb4ce0 100644 --- a/src/storage/query-adapter/sparql/SparqlQueryBuilder.ts +++ b/src/storage/query-adapter/sparql/SparqlQueryBuilder.ts @@ -371,7 +371,7 @@ export class SparqlQueryBuilder { } const variable = this.createVariable(); const triple = { subject, predicate, object: variable }; - const { filter, valuePattern } = this.resolveFindOperatorAsExpressionWithMultipleValues( + const { filter, valuePattern, tripleInFilter } = this.resolveFindOperatorAsExpressionWithMultipleValues( variable, operator, triple, @@ -379,7 +379,7 @@ export class SparqlQueryBuilder { return { values: valuePattern ? [ valuePattern ] : [], filters: filter ? [ filter ] : [], - triples: [ triple ], + triples: tripleInFilter ? [] : [ triple ], }; } @@ -528,6 +528,14 @@ export class SparqlQueryBuilder { tripleInFilter: true, }; } + if (operator.operator === 'exists') { + return { + filter: createSparqlExistsOperation([ + createSparqlBasicGraphPattern([ triple ]), + ]), + tripleInFilter: true, + }; + } const resolvedExpression = this.resolveValueToExpression(operator.value) as Expression; switch (operator.operator) { case 'equal': @@ -609,8 +617,13 @@ export class SparqlQueryBuilder { triple: Triple, ): OperationExpression { let filterExpression: FilterPattern; - const rightSideIsOperation = typeof rightSide === 'object' && 'operator' in rightSide; - if (rightSideIsOperation) { + const isFindOperator = FindOperator.isFindOperator(rightSide); + if (isFindOperator && (rightSide as FindOperator).operator === 'exists') { + return createSparqlNotExistsOperation([ + createSparqlBasicGraphPattern([ triple ]), + ]); + } + if (isFindOperator) { let expression: OperationExpression | undefined; try { ({ filter: expression } = this.resolveFindOperatorAsExpressionWithMultipleValues( @@ -620,7 +633,7 @@ export class SparqlQueryBuilder { true, )); } catch { - throw new Error(`Unsupported Not sub operator "${rightSide.operator}"`); + throw new Error(`Unsupported Not sub operator "${(rightSide as FindOperator).operator}"`); } filterExpression = createSparqlFilterWithExpression(expression!); } else { diff --git a/src/util/Types.ts b/src/util/Types.ts index 9a47f55..6496c52 100644 --- a/src/util/Types.ts +++ b/src/util/Types.ts @@ -145,10 +145,6 @@ export type Verb = NodeObject & { [SKL.parametersContext]?: ValueObject; [SKL.parameters]?: NodeShape; [SKL.returnValue]?: NodeObject; - [SKL.returnValueFrame]?: ValueObject; - [SKL.series]?: { '@list': VerbMapping[] } | (RdfList & NodeObject); - [SKL.parallel]?: NodeObject; - [SKL.returnValueMapping]?: OrArray; }; export interface SeriesVerbArgs extends JSONObject { @@ -156,60 +152,46 @@ export interface SeriesVerbArgs extends JSONObject { previousVerbReturnValue: JSONObject; } -export type MappingWithParameterMapping = NodeObject & { - [SKL.parameterMapping]: OrArray; - [SKL.parameterMappingFrame]: ValueObject; +export type Mapping = NodeObject & { + [SKL.preProcessingMapping]?: OrArray; + [SKL.preProcessingMappingFrame]?: ValueObject; + [SKL.parameterMapping]?: OrArray; + [SKL.parameterMappingFrame]?: ValueObject; + [SKL.parameterReference]?: string | ValueObject; + [SKL.operationId]?: ValueObject; + [SKL.operationMapping]?: TriplesMap; + [SKL.verbId]?: ValueObject | string; + [SKL.verbMapping]?: TriplesMap; + [SKL.returnValueMapping]?: OrArray; + [SKL.returnValueFrame]?: ValueObject; + [SKL.series]?: { '@list': VerbMapping[] } | (RdfList & NodeObject); + [SKL.parallel]?: OrArray; }; -export type MappingWithParameterReference = NodeObject & { - [SKL.parameterReference]: string | ValueObject; +export type VerbMapping = Mapping & { + [SKL.verb]: ReferenceNodeObject; + [SKL.noun]?: ReferenceNodeObject; + [SKL.integration]?: ReferenceNodeObject; }; -export type MappingWithReturnValueMapping = NodeObject & { - [SKL.returnValueMapping]: OrArray; - [SKL.returnValueFrame]: ValueObject; -}; +export type MappingWithParameterMapping = VerbMapping & +Required>; -export type MappingWithVerbMapping = NodeObject & { - [SKL.verbId]?: ValueObject | string; - [SKL.verbMapping]?: TriplesMap; -}; +export type MappingWithParameterReference = VerbMapping & +Required>; -export type MappingWithOperationMapping = NodeObject & { - [SKL.constantOperationId]: ValueObject; - [SKL.operationMapping]?: TriplesMap; -}; +export type MappingWithReturnValueMapping = VerbMapping & +Required>; -export interface VerbMapping extends - MappingWithVerbMapping, - Partial, - Partial, - Partial {} - -export interface VerbIntegrationMapping extends - Partial, - Partial, - MappingWithOperationMapping, - Partial { - [SKL.verb]: ReferenceNodeObject; - [SKL.integration]: ReferenceNodeObject; -} +export type MappingWithSeries = VerbMapping & +Required>; -export interface VerbNounMapping extends - Partial, - Partial, - MappingWithVerbMapping, - Partial { - [SKL.verb]: ReferenceNodeObject; - [SKL.noun]: ReferenceNodeObject; -} +export type MappingWithParallel = VerbMapping & +Required>; -export interface TriggerVerbMapping extends - MappingWithVerbMapping, - Partial, - Partial { +export type TriggerMapping = Mapping & { [SKL.integration]: ReferenceNodeObject; -} +}; export type PossibleArrayFieldValues = | boolean diff --git a/test/assets/schemas/core.json b/test/assets/schemas/core.json index 38392ce..7ad3ce0 100644 --- a/test/assets/schemas/core.json +++ b/test/assets/schemas/core.json @@ -235,14 +235,26 @@ "shacl:path": "skl:returnValue", "shacl:name": "returnValue", "shacl:description": "A SHACL NodeShape specifying the format and constraints that the return value of a Verb must conform to." - }, - { - "shacl:maxCount": 1, - "shacl:path": "skl:returnValueFrame", - "shacl:name": "returnValueFrame", - "shacl:datatype": "rdf:JSON", - "shacl:description": "A JSON-LD Frame used to transform the JSON-LD returned by a Mapping to this Verb." - }, + } + ] + }, + { + "@id": "https://standardknowledge.com/ontologies/core/NounMappedVerb", + "@type": ["owl:Class", "shacl:NodeShape", "skl:Noun"], + "rdfs:subClassOf": "https://standardknowledge.com/ontologies/core/Verb" + }, + { + "@id": "https://standardknowledge.com/ontologies/core/OpenApiSecuritySchemeVerb", + "@type": ["owl:Class", "shacl:NodeShape", "skl:Noun"], + "rdfs:subClassOf": "https://standardknowledge.com/ontologies/core/Verb" + }, + { + "@id": "https://standardknowledge.com/ontologies/core/CompositeMapping", + "@type": ["owl:Class", "shacl:NodeShape", "skl:Noun"], + "rdfs:label": "Composite Mapping", + "rdfs:subClassOf": "https://standardknowledge.com/ontologies/core/Mapping", + "shacl:closed": false, + "shacl:property": [ { "shacl:maxCount": 1, "shacl:path": "skl:series", @@ -334,7 +346,7 @@ "shacl:maxCount": 1, "shacl:name": "returnValueFrame", "shacl:datatype": "rdf:JSON", - "shacl:description": "A JSON-LD Frame used to transform the JSON-LD returned by the returnValueMapping into a prefered format. This field overrides the returnValueFrame of the Verb. If not supplied, the Verb's returnValueFrame will be used instead." + "shacl:description": "A JSON-LD Frame used to transform the JSON-LD returned by the returnValueMapping into a prefered format." }, { "shacl:path": "skl:parallel", @@ -379,16 +391,6 @@ } ] }, - { - "@id": "https://standardknowledge.com/ontologies/core/NounMappedVerb", - "@type": ["owl:Class", "shacl:NodeShape", "skl:Noun"], - "rdfs:subClassOf": "https://standardknowledge.com/ontologies/core/Verb" - }, - { - "@id": "https://standardknowledge.com/ontologies/core/OpenApiSecuritySchemeVerb", - "@type": ["owl:Class", "shacl:NodeShape", "skl:Noun"], - "rdfs:subClassOf": "https://standardknowledge.com/ontologies/core/Verb" - }, { "@id": "https://standardknowledge.com/ontologies/core/VerbIntegrationMapping", "@type": ["owl:Class", "shacl:NodeShape", "skl:Noun"], @@ -409,8 +411,8 @@ { "shacl:maxCount": 1, "shacl:datatype": "xsd:string", - "shacl:name": "constantOperationId", - "shacl:path": "https://standardknowledge.com/ontologies/core/constantOperationId" + "shacl:name": "operationId", + "shacl:path": "https://standardknowledge.com/ontologies/core/operationId" }, { "shacl:maxCount": 1, @@ -423,6 +425,13 @@ "shacl:nodeKind": "shacl:IRI", "shacl:path": "https://standardknowledge.com/ontologies/core/returnValueMapping" }, + { + "shacl:maxCount": 1, + "shacl:path": "skl:returnValueFrame", + "shacl:name": "returnValueFrame", + "shacl:datatype": "rdf:JSON", + "shacl:description": "A JSON-LD Frame used to transform the JSON-LD returned by a Mapping to this Verb." + }, { "shacl:maxCount": 1, "shacl:minCount": 1, diff --git a/test/assets/schemas/count-in-series-verb.json b/test/assets/schemas/count-in-series-verb.json index 06c72ec..210abfb 100644 --- a/test/assets/schemas/count-in-series-verb.json +++ b/test/assets/schemas/count-in-series-verb.json @@ -28,11 +28,9 @@ "skl:account": { "@type": "@id" }, "skl:openApiDescription": { "@type": "@json" }, "skl:parametersContext": { "@type": "@json" }, - "skl:returnValueFrame": { "@type": "@json" }, "skl:noun": { "@type": "@id" }, "skl:parameterMapping": { "@type": "@id" }, "skl:parameterMappingFrame": { "@type": "@json" }, - "skl:returnValueMapping": { "@type": "@id" }, "skl:operationMapping": { "@type": "@id" }, "skl:verbMapping": { "@type": "@id" }, "integration": { "@id": "skl:integration", "@type": "@id" }, @@ -70,7 +68,12 @@ "shacl:datatype": "rdf:JSON" } ] - }, + } + }, + { + "@id": "https://example.com/countEntitiesMapping", + "@type": "https://standardknowledge.com/ontologies/core/CompositeMapping", + "verb": "https://example.com/countEntities", "skl:series": [ { "skl:verbId": "https://standardknowledge.com/ontologies/skl-engine/count", @@ -82,7 +85,10 @@ "rml:referenceFormulation": "http://semweb.mmlab.be/ns/ql#JSONPath", "rml:source": "input.json" }, - "rr:subject": "https://example.com/mappingSubject", + "rr:subjectMap": { + "@type": "rr:SubjectMap", + "rr:termType": "rr:BlankNode" + }, "rr:predicateObjectMap": [ { "@type": "rr:PredicateObjectMap", @@ -101,8 +107,7 @@ "@id": "https://example.com/where", "@type": "@json" } - }, - "@id": "https://example.com/mappingSubject" + } } } ] diff --git a/test/assets/schemas/destroy-in-series-verb.json b/test/assets/schemas/destroy-in-series-verb.json index 21d2c5d..dc70918 100644 --- a/test/assets/schemas/destroy-in-series-verb.json +++ b/test/assets/schemas/destroy-in-series-verb.json @@ -29,11 +29,9 @@ "skl:account": { "@type": "@id" }, "skl:openApiDescription": { "@type": "@json" }, "skl:parametersContext": { "@type": "@json" }, - "skl:returnValueFrame": { "@type": "@json" }, "skl:noun": { "@type": "@id" }, "skl:parameterMapping": { "@type": "@id" }, "skl:parameterMappingFrame": { "@type": "@json" }, - "skl:returnValueMapping": { "@type": "@id" }, "skl:operationMapping": { "@type": "@id" }, "skl:verbMapping": { "@type": "@id" }, "integration": { "@id": "skl:integration", "@type": "@id" }, @@ -70,7 +68,12 @@ "shacl:nodeKind": "shacl:IRI" } ] - }, + } + }, + { + "@id": "https://example.com/destroyEntitiesMapping", + "@type": "https://standardknowledge.com/ontologies/core/CompositeMapping", + "verb": "https://example.com/destroyEntities", "skl:series": [ { "skl:verbId": "https://standardknowledge.com/ontologies/skl-engine/destroy", diff --git a/test/assets/schemas/divide-function.json b/test/assets/schemas/divide-function.json index 6c698ac..6dda491 100644 --- a/test/assets/schemas/divide-function.json +++ b/test/assets/schemas/divide-function.json @@ -26,7 +26,6 @@ "skl:account": { "@type": "@id" }, "skl:openApiDescription": { "@type": "@json" }, "skl:parametersContext": { "@type": "@json" }, - "skl:returnValueFrame": { "@type": "@json" }, "skl:noun": { "@type": "@id" }, "skl:returnValueMapping": { "@type": "@id" }, "integration": { "@id": "skl:integration", "@type": "@id" }, diff --git a/test/assets/schemas/exists-in-series-verb.json b/test/assets/schemas/exists-in-series-verb.json index 3dbd95d..840778d 100644 --- a/test/assets/schemas/exists-in-series-verb.json +++ b/test/assets/schemas/exists-in-series-verb.json @@ -28,11 +28,9 @@ "skl:account": { "@type": "@id" }, "skl:openApiDescription": { "@type": "@json" }, "skl:parametersContext": { "@type": "@json" }, - "skl:returnValueFrame": { "@type": "@json" }, "skl:noun": { "@type": "@id" }, "skl:parameterMapping": { "@type": "@id" }, "skl:parameterMappingFrame": { "@type": "@json" }, - "skl:returnValueMapping": { "@type": "@id" }, "skl:operationMapping": { "@type": "@id" }, "skl:verbMapping": { "@type": "@id" }, "integration": { "@id": "skl:integration", "@type": "@id" }, @@ -70,7 +68,12 @@ "shacl:datatype": "rdf:JSON" } ] - }, + } + }, + { + "@id": "https://example.com/entitiesExistMapping", + "@type": "https://standardknowledge.com/ontologies/core/CompositeMapping", + "verb": "https://example.com/entitiesExist", "skl:series": [ { "skl:verbId": "https://standardknowledge.com/ontologies/skl-engine/exists", diff --git a/test/assets/schemas/exists-with-parameter-reference.json b/test/assets/schemas/exists-with-parameter-reference.json index 7cc6ee6..649b8d5 100644 --- a/test/assets/schemas/exists-with-parameter-reference.json +++ b/test/assets/schemas/exists-with-parameter-reference.json @@ -28,12 +28,10 @@ "skl:account": { "@type": "@id" }, "skl:openApiDescription": { "@type": "@json" }, "skl:parametersContext": { "@type": "@json" }, - "skl:returnValueFrame": { "@type": "@json" }, "skl:noun": { "@type": "@id" }, "skl:parameterMapping": { "@type": "@id" }, "skl:parameterMappingFrame": { "@type": "@json" }, "skl:parameterReference": { "@type": "xsd:string" }, - "skl:returnValueMapping": { "@type": "@id" }, "skl:operationMapping": { "@type": "@id" }, "skl:verbMapping": { "@type": "@id" }, "integration": { "@id": "skl:integration", "@type": "@id" }, @@ -71,7 +69,12 @@ "shacl:datatype": "rdf:JSON" } ] - }, + } + }, + { + "@id": "https://example.com/entitiesExistMapping", + "@type": "https://standardknowledge.com/ontologies/core/CompositeMapping", + "verb": "https://example.com/entitiesExist", "skl:series": [ { "skl:verbId": "https://standardknowledge.com/ontologies/skl-engine/exists", diff --git a/test/assets/schemas/find-in-series-verb.json b/test/assets/schemas/find-in-series-verb.json index 5dfcd2c..b44eece 100644 --- a/test/assets/schemas/find-in-series-verb.json +++ b/test/assets/schemas/find-in-series-verb.json @@ -28,11 +28,9 @@ "skl:account": { "@type": "@id" }, "skl:openApiDescription": { "@type": "@json" }, "skl:parametersContext": { "@type": "@json" }, - "skl:returnValueFrame": { "@type": "@json" }, "skl:noun": { "@type": "@id" }, "skl:parameterMapping": { "@type": "@id" }, "skl:parameterMappingFrame": { "@type": "@json" }, - "skl:returnValueMapping": { "@type": "@id" }, "skl:operationMapping": { "@type": "@id" }, "skl:verbMapping": { "@type": "@id" }, "integration": { "@id": "skl:integration", "@type": "@id" }, @@ -70,7 +68,12 @@ "shacl:datatype": "rdf:JSON" } ] - }, + } + }, + { + "@id": "https://example.com/findEntityMapping", + "@type": "https://standardknowledge.com/ontologies/core/CompositeMapping", + "verb": "https://example.com/findEntity", "skl:series": [ { "skl:verbId": "https://standardknowledge.com/ontologies/skl-engine/find", diff --git a/test/assets/schemas/findAll-in-series-verb.json b/test/assets/schemas/findAll-in-series-verb.json index 568584c..c16fb74 100644 --- a/test/assets/schemas/findAll-in-series-verb.json +++ b/test/assets/schemas/findAll-in-series-verb.json @@ -28,11 +28,9 @@ "skl:account": { "@type": "@id" }, "skl:openApiDescription": { "@type": "@json" }, "skl:parametersContext": { "@type": "@json" }, - "skl:returnValueFrame": { "@type": "@json" }, "skl:noun": { "@type": "@id" }, "skl:parameterMapping": { "@type": "@id" }, "skl:parameterMappingFrame": { "@type": "@json" }, - "skl:returnValueMapping": { "@type": "@id" }, "skl:operationMapping": { "@type": "@id" }, "skl:verbMapping": { "@type": "@id" }, "integration": { "@id": "skl:integration", "@type": "@id" }, @@ -70,7 +68,12 @@ "shacl:datatype": "rdf:JSON" } ] - }, + } + }, + { + "@id": "https://example.com/findAllEntitiesMapping", + "@type": "https://standardknowledge.com/ontologies/core/CompositeMapping", + "verb": "https://example.com/findAllEntities", "skl:series": [ { "skl:verbId": "https://standardknowledge.com/ontologies/skl-engine/findAll", diff --git a/test/assets/schemas/get-dropbox-file.json b/test/assets/schemas/get-dropbox-file.json index 220bba0..0cbaf30 100644 --- a/test/assets/schemas/get-dropbox-file.json +++ b/test/assets/schemas/get-dropbox-file.json @@ -81,9 +81,6 @@ }, "skl:returnValue": { "@id": "https://standardknowledge.com/ontologies/core/File" - }, - "skl:returnValueFrame": { - "@type": ["https://standardknowledge.com/ontologies/core/File", "https://standardknowledge.com/ontologies/core/Folder"] } }, { @@ -524,6 +521,12 @@ } ], "rr:subject": "https://example.com/data/abc123" + }, + "skl:returnValueFrame": { + "@type": [ + "https://standardknowledge.com/ontologies/core/File", + "https://standardknowledge.com/ontologies/core/Folder" + ] } }, { @@ -780,9 +783,6 @@ "shacl:path": "https://standardknowledge.com/ontologies/core/accessToken" } ] - }, - "skl:returnValueFrame": { - "@id": "https://example.com/getOauthTokensReturnValueSubject" } }, { @@ -962,6 +962,9 @@ } ], "rr:subject": "https://example.com/getOauthTokensReturnValueSubject" + }, + "skl:returnValueFrame": { + "@id": "https://example.com/getOauthTokensReturnValueSubject" } }, { diff --git a/test/assets/schemas/get-stubhub-events.json b/test/assets/schemas/get-stubhub-events.json index 85a9a4b..9b6198a 100644 --- a/test/assets/schemas/get-stubhub-events.json +++ b/test/assets/schemas/get-stubhub-events.json @@ -104,9 +104,6 @@ "shacl:path": "https://standardknowledge.com/ontologies/core/accessToken" } ] - }, - "skl:returnValueFrame": { - "@id": "https://example.com/getOauthTokensReturnValueSubject" } }, { @@ -210,6 +207,9 @@ } ], "rr:subject": "https://example.com/getOauthTokensReturnValueSubject" + }, + "skl:returnValueFrame": { + "@id": "https://example.com/getOauthTokensReturnValueSubject" } }, { diff --git a/test/assets/schemas/get-ticketmaster-events.json b/test/assets/schemas/get-ticketmaster-events.json index 0320338..f0b4391 100644 --- a/test/assets/schemas/get-ticketmaster-events.json +++ b/test/assets/schemas/get-ticketmaster-events.json @@ -150,9 +150,6 @@ }, "https://standardknowledge.com/ontologies/core/returnValue": { "@id": "https://standardknowledge.com/ontologies/core/TokenPaginatedCollection" - }, - "https://standardknowledge.com/ontologies/core/returnValueFrame": { - "@type": "https://standardknowledge.com/ontologies/core/TokenPaginatedCollection" } }, { @@ -721,7 +718,10 @@ "rr:template": "https://example.com/data/venues/{id}" } } - ] + ], + "https://standardknowledge.com/ontologies/core/returnValueFrame": { + "@type": "https://standardknowledge.com/ontologies/core/TokenPaginatedCollection" + } }, { "@id": "https://example.com/data/TicketmasterOpenApiDescription", diff --git a/test/assets/schemas/parallel-verb.json b/test/assets/schemas/parallel-verb.json index 819ad64..9429b94 100644 --- a/test/assets/schemas/parallel-verb.json +++ b/test/assets/schemas/parallel-verb.json @@ -71,6 +71,27 @@ } ] }, + "skl:returnValue": { + "@type": "shacl:NodeShape", + "shacl:closed": false, + "shacl:property": [ + { + "shacl:name": "links", + "shacl:datatype": "xsd:string", + "shacl:path": "example:links" + }, + { + "shacl:name": "length", + "shacl:datatype": "xsd:integer", + "shacl:path": "example:length" + } + ] + } + }, + { + "@id": "https://example.com/parseLinksAndCountCharactersFromEntityMapping", + "@type": "https://standardknowledge.com/ontologies/core/CompositeMapping", + "verb": "https://example.com/parseLinksAndCountCharactersFromEntity", "skl:parallel": [ { "skl:parameterMapping": { @@ -301,23 +322,7 @@ } } } - ], - "skl:returnValue": { - "@type": "shacl:NodeShape", - "shacl:closed": false, - "shacl:property": [ - { - "shacl:name": "links", - "shacl:datatype": "xsd:string", - "shacl:path": "example:links" - }, - { - "shacl:name": "length", - "shacl:datatype": "xsd:integer", - "shacl:path": "example:length" - } - ] - } + ] }, { "@id": "https://example.com/getTextBody", @@ -363,14 +368,6 @@ "shacl:path": "https://example.com/text" } ] - }, - "skl:returnValueFrame": { - "@context": { - "text": { - "@id": "https://example.com/text", - "@type": "http://www.w3.org/2001/XMLSchema#string" - } - } } }, { diff --git a/test/assets/schemas/save-in-series-verb.json b/test/assets/schemas/save-in-series-verb.json index 10af97f..e16d759 100644 --- a/test/assets/schemas/save-in-series-verb.json +++ b/test/assets/schemas/save-in-series-verb.json @@ -29,7 +29,6 @@ "skl:account": { "@type": "@id" }, "skl:openApiDescription": { "@type": "@json" }, "skl:parametersContext": { "@type": "@json" }, - "skl:returnValueFrame": { "@type": "@json" }, "skl:noun": { "@type": "@id" }, "skl:parameterMapping": { "@type": "@id" }, "skl:parameterMappingFrame": { "@type": "@json" }, @@ -70,7 +69,12 @@ "shacl:nodeKind": "shacl:IRI" } ] - }, + } + }, + { + "@id": "https://example.com/saveEntitiesMapping", + "@type": "https://standardknowledge.com/ontologies/core/CompositeMapping", + "verb": "https://example.com/saveEntities", "skl:series": [ { "skl:verbId": "https://standardknowledge.com/ontologies/skl-engine/save", diff --git a/test/assets/schemas/series-verb-no-verbId.json b/test/assets/schemas/series-verb-no-verbId.json index f1a33e5..d0008a7 100644 --- a/test/assets/schemas/series-verb-no-verbId.json +++ b/test/assets/schemas/series-verb-no-verbId.json @@ -71,7 +71,12 @@ "shacl:datatype": "xsd:string" } ] - }, + } + }, + { + "@id": "https://example.com/transformTextMapping", + "@type": "https://standardknowledge.com/ontologies/core/CompositeMapping", + "verb": "https://example.com/transformText", "skl:series": [ { "skl:parameterMapping": { @@ -183,7 +188,10 @@ } } ], - "rr:subject": "https://example.com/mappingSubject" + "rr:subjectMap": { + "@type": "rr:SubjectMap", + "rr:termType": "rr:BlankNode" + } }, "skl:parameterMappingFrame": { "@context": { @@ -191,8 +199,7 @@ "@id": "https://example.com/value", "@type": "http://www.w3.org/2001/XMLSchema#string" } - }, - "@id": "https://example.com/mappingSubject" + } }, "skl:verbId": "https://example.com/getText" } @@ -220,7 +227,12 @@ "shacl:name": "value" } ] - }, + } + }, + { + "@id": "https://example.com/getTextMapping", + "@type": "https://standardknowledge.com/ontologies/core/Mapping", + "verb": "https://example.com/getText", "skl:returnValueMapping": { "@type": "rr:TriplesMap", "rml:logicalSource": { diff --git a/test/assets/schemas/series-verb-with-pre-processing.json b/test/assets/schemas/series-verb-with-pre-processing.json index d4afd1f..93b5175 100644 --- a/test/assets/schemas/series-verb-with-pre-processing.json +++ b/test/assets/schemas/series-verb-with-pre-processing.json @@ -72,7 +72,12 @@ "shacl:nodeKind": { "@id": "shacl:IRI" } } ] - }, + } + }, + { + "@id": "https://example.com/parseAndSaveLinksFromEntityMapping", + "@type": "https://standardknowledge.com/ontologies/core/CompositeMapping", + "verb": "https://example.com/parseAndSaveLinksFromEntity", "skl:series": [ { "skl:preProcessingMapping": { @@ -83,7 +88,10 @@ "rml:referenceFormulation": "http://semweb.mmlab.be/ns/ql#JSONPath", "rml:source": "input.json" }, - "rr:subject": "https://example.com/mappingSubject", + "rr:subjectMap": { + "@type": "rr:SubjectMap", + "rr:termType": "rr:BlankNode" + }, "rr:predicateObjectMap": [ { "@type": "rr:PredicateObjectMap", @@ -353,14 +361,6 @@ "shacl:path": "https://example.com/text" } ] - }, - "skl:returnValueFrame": { - "@context": { - "text": { - "@id": "https://example.com/text", - "@type": "http://www.w3.org/2001/XMLSchema#string" - } - } } }, { diff --git a/test/assets/schemas/series-verb.json b/test/assets/schemas/series-verb.json index 76d6ca9..699769a 100644 --- a/test/assets/schemas/series-verb.json +++ b/test/assets/schemas/series-verb.json @@ -70,7 +70,12 @@ "shacl:nodeKind": { "@id": "shacl:IRI" } } ] - }, + } + }, + { + "@id": "https://example.com/parseAndSaveLinksFromEntityMapping", + "@type": "https://standardknowledge.com/ontologies/core/CompositeMapping", + "verb": "https://example.com/parseAndSaveLinksFromEntity", "skl:series": [ { "skl:parameterMapping": { @@ -322,14 +327,6 @@ "shacl:path": "https://example.com/text" } ] - }, - "skl:returnValueFrame": { - "@context": { - "text": { - "@id": "https://example.com/text", - "@type": "http://www.w3.org/2001/XMLSchema#string" - } - } } }, { diff --git a/test/assets/schemas/trigger.json b/test/assets/schemas/trigger.json index 46e1c1f..08cac2f 100644 --- a/test/assets/schemas/trigger.json +++ b/test/assets/schemas/trigger.json @@ -28,7 +28,6 @@ "skl:account": { "@type": "@id" }, "skl:openApiDescription": { "@type": "@json" }, "skl:parametersContext": { "@type": "@json" }, - "skl:returnValueFrame": { "@type": "@json" }, "skl:noun": { "@type": "@id" }, "skl:parameterMapping": { "@type": "@id" }, "skl:parameterMappingFrame": { "@type": "@json" }, diff --git a/test/deploy/src/assets/get-ticketmaster-events.json b/test/deploy/src/assets/get-ticketmaster-events.json index f04ddfb..487691f 100644 --- a/test/deploy/src/assets/get-ticketmaster-events.json +++ b/test/deploy/src/assets/get-ticketmaster-events.json @@ -153,9 +153,6 @@ }, "https://standardknowledge.com/ontologies/core/returnValue": { "@id": "https://standardknowledge.com/ontologies/core/TokenPaginatedCollection" - }, - "https://standardknowledge.com/ontologies/core/returnValueFrame": { - "@type": "https://standardknowledge.com/ontologies/core/TokenPaginatedCollection" } }, { @@ -724,7 +721,10 @@ "rr:template": "https://example.com/data/venues/{id}" } } - ] + ], + "https://standardknowledge.com/ontologies/core/returnValueFrame": { + "@type": "https://standardknowledge.com/ontologies/core/TokenPaginatedCollection" + } }, { "@id": "https://example.com/data/TicketmasterOpenApiDescription", diff --git a/test/unit/SklEngine.test.ts b/test/unit/SklEngine.test.ts index a66f12a..44e0ef7 100644 --- a/test/unit/SklEngine.test.ts +++ b/test/unit/SklEngine.test.ts @@ -621,7 +621,7 @@ describe('SKLEngine', (): void => { ); }); - it('returns the raw operation response if the openapi operation mapping does not have a return value mapping.', + it('returns the raw operation response if the verb integration mapping does not have a return value mapping.', async(): Promise => { schemas = schemas.map((schemaItem: any): any => { if (schemaItem['@id'] === 'https://example.com/data/4') { @@ -906,8 +906,11 @@ describe('SKLEngine', (): void => { const sklEngine = new SKLEngine({ type: 'memory' }); await sklEngine.save(schemas); await expect(sklEngine.verb.authorizeWithPkceOauth({ account })).resolves.toEqual({ - '@type': '@json', - '@value': response, + data: response, + operationParameters: { + account, + client_id: 'adlerfaulkner', + }, }); expect(executeSecuritySchemeStage).toHaveBeenCalledTimes(1); expect(executeSecuritySchemeStage).toHaveBeenCalledWith( @@ -1001,6 +1004,37 @@ describe('SKLEngine', (): void => { }); }); + describe('calling Verbs with a specific mapping', (): void => { + it('can execute the verb with the mapping.', async(): Promise => { + schemas = await frameAndCombineSchemas([ + './test/assets/schemas/core.json', + './test/assets/schemas/series-verb.json', + ]); + const functions = { + 'https://example.com/functions/parseLinksFromText'(data: any): string[] { + const text = data['https://example.com/functions/text']; + const res = text.match(URI_REGEXP); + return res; + }, + }; + const sklEngine = new SKLEngine({ type: 'memory', functions }); + await sklEngine.save(schemas); + const entity = { + '@id': 'https://example.com/data/1', + '@type': 'https://schema.org/BlogPosting', + 'https://schema.org/articleBody': { + '@value': 'Hello world https://example.com/test', + '@type': XSD.string, + }, + }; + const response = await sklEngine.verb.parseAndSaveLinksFromEntity({ + entity, + mapping: 'https://example.com/parseAndSaveLinksFromEntityMapping', + }); + expect(response).toEqual({}); + }); + }); + describe('calling Verbs which map a Noun to another Verb', (): void => { let executeOperation: any; let setOpenapiSpec: any; @@ -1153,7 +1187,7 @@ describe('SKLEngine', (): void => { }); }); - describe('calling Verbs which specify a series sub Verb execution', (): void => { + describe('calling Verbs which specify series composite mapping', (): void => { it('can execute multiple Verbs in series.', async(): Promise => { schemas = await frameAndCombineSchemas([ './test/assets/schemas/core.json', @@ -1180,7 +1214,7 @@ describe('SKLEngine', (): void => { expect(response).toEqual({}); }); - it('runs a preProcessingMapping and adds preProcessedParameters to the series verb arguments.', + it('runs a preProcessingMapping and adds preProcessedParameters to the series mapping arguments.', async(): Promise => { schemas = await frameAndCombineSchemas([ './test/assets/schemas/core.json', @@ -1207,23 +1241,24 @@ describe('SKLEngine', (): void => { expect(response).toEqual({}); }); - it('does not run a verb in the series if its verbMapping does not return a verbId.', async(): Promise => { - schemas = await frameAndCombineSchemas([ - './test/assets/schemas/core.json', - './test/assets/schemas/series-verb-no-verbId.json', - ]); - const sklEngine = new SKLEngine({ type: 'memory' }); - await sklEngine.save(schemas); - const response = await sklEngine.verb.transformText({ text: 'Hello' }); - expect(response).toEqual( - expect.objectContaining({ - text: 'Hello', - }), - ); - }); + it('does not run a verb from a series mapping if its verbMapping does not return a verbId.', + async(): Promise => { + schemas = await frameAndCombineSchemas([ + './test/assets/schemas/core.json', + './test/assets/schemas/series-verb-no-verbId.json', + ]); + const sklEngine = new SKLEngine({ type: 'memory' }); + await sklEngine.save(schemas); + const response = await sklEngine.verb.transformText({ text: 'Hello' }); + expect(response).toEqual( + expect.objectContaining({ + text: 'Hello', + }), + ); + }); }); - describe('calling Verbs which specify a parallel sub Verb execution', (): void => { + describe('calling Verbs which use a parallel composite mapping', (): void => { beforeEach(async(): Promise => { schemas = await frameAndCombineSchemas([ './test/assets/schemas/core.json', @@ -1286,7 +1321,7 @@ describe('SKLEngine', (): void => { }, }; schemas = schemas.map((schemaItem: any): any => { - if (schemaItem['@id'] === 'https://example.com/parseLinksAndCountCharactersFromEntity') { + if (schemaItem['@id'] === 'https://example.com/parseLinksAndCountCharactersFromEntityMapping') { // eslint-disable-next-line @typescript-eslint/no-dynamic-delete delete schemaItem[SKL.parallel][0][SKL.returnValueMapping][RR.subjectMap][RR.class]; schemaItem[SKL.parallel][0][SKL.returnValueMapping][RR.subjectMap][RR.constant] = 'https://example.com/res/1'; @@ -1591,7 +1626,7 @@ describe('SKLEngine', (): void => { }); }); - it('throws an error when a noun or account is not supplied with the Verb.', async(): Promise => { + it('throws an error when a valid mapping cannot be found.', async(): Promise => { schemas = await frameAndCombineSchemas([ './test/assets/schemas/core.json', './test/assets/schemas/get-dropbox-file.json', @@ -1599,7 +1634,7 @@ describe('SKLEngine', (): void => { const sklEngine = new SKLEngine({ type: 'memory' }); await sklEngine.save(schemas); await expect(sklEngine.verb.getName({ entity: { [RDFS.label]: 'final.jpg' }})) - .rejects.toThrow('Verb must be a composite or its parameters must include either a noun or an account.'); + .rejects.toThrow('No mapping found.'); }); it('throws an error if the operation is not supported.', async(): Promise => { diff --git a/test/unit/storage/query-adapter/sparql/SparqlQueryBuilder.test.ts b/test/unit/storage/query-adapter/sparql/SparqlQueryBuilder.test.ts index 4e1b7e0..0780984 100644 --- a/test/unit/storage/query-adapter/sparql/SparqlQueryBuilder.test.ts +++ b/test/unit/storage/query-adapter/sparql/SparqlQueryBuilder.test.ts @@ -2,6 +2,7 @@ import DataFactory from '@rdfjs/data-model'; import type { FindOperatorType } from '../../../../../src/storage/FindOperator'; import { Equal } from '../../../../../src/storage/operator/Equal'; +import { Exists } from '../../../../../src/storage/operator/Exists'; import { GreaterThan } from '../../../../../src/storage/operator/GreaterThan'; import { GreaterThanOrEqual } from '../../../../../src/storage/operator/GreaterThanOrEqual'; import { In } from '../../../../../src/storage/operator/In'; @@ -1012,66 +1013,46 @@ describe('A SparqlQueryBuilder', (): void => { graphSelectionTriples: [], where: [ { - type: 'bgp', - triples: [ - { + type: 'graph', + name: entityVariable, + patterns: [{ + type: 'bgp', + triples: [{ subject: entityVariable, - predicate, - object: c1, - }, - ], + predicate: c2, + object: c3, + }], + }], }, { type: 'filter', expression: { type: 'operation', - operator: '&&', - args: [ - { - type: 'operation', - operator: 'notexists', - args: [{ - type: 'group', - patterns: [ - { - type: 'bgp', - triples: [{ - subject: entityVariable, - predicate, - object: c1, - }], - }, - { - type: 'filter', - expression: { - type: 'operation', - operator: '=', - args: [ - c1, - DataFactory.literal('1', XSD.integer), - ], - }, - }, - ], - }], - }, - { - type: 'operation', - operator: 'exists', - args: [{ - type: 'graph', - name: entityVariable, - patterns: [{ - type: 'bgp', - triples: [{ - subject: entityVariable, - predicate: c2, - object: c3, - }], + operator: 'notexists', + args: [{ + type: 'group', + patterns: [ + { + type: 'bgp', + triples: [{ + subject: entityVariable, + predicate, + object: c1, }], - }], - }, - ], + }, + { + type: 'filter', + expression: { + type: 'operation', + operator: '=', + args: [ + c1, + DataFactory.literal('1', XSD.integer), + ], + }, + }, + ], + }], }, }, ], @@ -1092,69 +1073,49 @@ describe('A SparqlQueryBuilder', (): void => { graphSelectionTriples: [], where: [ { - type: 'bgp', - triples: [ - { + type: 'graph', + name: entityVariable, + patterns: [{ + type: 'bgp', + triples: [{ subject: entityVariable, - predicate, - object: c1, - }, - ], + predicate: c2, + object: c3, + }], + }], }, { type: 'filter', expression: { type: 'operation', - operator: '&&', - args: [ - { - type: 'operation', - operator: 'notexists', - args: [{ - type: 'group', - patterns: [ - { - type: 'bgp', - triples: [{ - subject: entityVariable, - predicate, - object: c1, - }], - }, - { - type: 'filter', - expression: { - type: 'operation', - operator: 'in', - args: [ - c1, - [ - DataFactory.literal('1', XSD.integer), - DataFactory.literal('2', XSD.integer), - ], - ], - }, - }, - ], - }], - }, - { - type: 'operation', - operator: 'exists', - args: [{ - type: 'graph', - name: entityVariable, - patterns: [{ - type: 'bgp', - triples: [{ - subject: entityVariable, - predicate: c2, - object: c3, - }], + operator: 'notexists', + args: [{ + type: 'group', + patterns: [ + { + type: 'bgp', + triples: [{ + subject: entityVariable, + predicate, + object: c1, }], - }], - }, - ], + }, + { + type: 'filter', + expression: { + type: 'operation', + operator: 'in', + args: [ + c1, + [ + DataFactory.literal('1', XSD.integer), + DataFactory.literal('2', XSD.integer), + ], + ], + }, + }, + ], + }], }, }, ], @@ -1175,66 +1136,46 @@ describe('A SparqlQueryBuilder', (): void => { graphSelectionTriples: [], where: [ { - type: 'bgp', - triples: [ - { + type: 'graph', + name: entityVariable, + patterns: [{ + type: 'bgp', + triples: [{ subject: entityVariable, - predicate, - object: c1, - }, - ], + predicate: c2, + object: c3, + }], + }], }, { type: 'filter', expression: { type: 'operation', - operator: '&&', - args: [ - { - type: 'operation', - operator: 'notexists', - args: [{ - type: 'group', - patterns: [ - { - type: 'bgp', - triples: [{ - subject: entityVariable, - predicate, - object: c1, - }], - }, - { - type: 'filter', - expression: { - type: 'operation', - operator: '=', - args: [ - c1, - DataFactory.literal('1', XSD.integer), - ], - }, - }, - ], - }], - }, - { - type: 'operation', - operator: 'exists', - args: [{ - type: 'graph', - name: entityVariable, - patterns: [{ - type: 'bgp', - triples: [{ - subject: entityVariable, - predicate: c2, - object: c3, - }], + operator: 'notexists', + args: [{ + type: 'group', + patterns: [ + { + type: 'bgp', + triples: [{ + subject: entityVariable, + predicate, + object: c1, }], - }], - }, - ], + }, + { + type: 'filter', + expression: { + type: 'operation', + operator: '=', + args: [ + c1, + DataFactory.literal('1', XSD.integer), + ], + }, + }, + ], + }], }, }, ], @@ -1303,6 +1244,98 @@ describe('A SparqlQueryBuilder', (): void => { }); }); + it('builds a query with an exists operator.', (): void => { + expect(builder.buildEntitySelectPatternsFromOptions( + entityVariable, + { + where: { + 'https://example.com/pred': Exists(), + }, + }, + )).toEqual({ + graphSelectionTriples: [], + where: [ + { + type: 'graph', + name: entityVariable, + patterns: [{ + type: 'bgp', + triples: [{ + subject: entityVariable, + predicate: c2, + object: c3, + }], + }], + }, + { + type: 'filter', + expression: { + type: 'operation', + operator: 'exists', + args: [ + { + type: 'bgp', + triples: [{ + subject: entityVariable, + predicate, + object: c1, + }], + }, + ], + }, + }, + ], + orders: [], + graphWhere: [], + }); + }); + + it('builds a query with a not exists operator.', (): void => { + expect(builder.buildEntitySelectPatternsFromOptions( + entityVariable, + { + where: { + 'https://example.com/pred': Not(Exists()), + }, + }, + )).toEqual({ + graphSelectionTriples: [], + where: [ + { + type: 'graph', + name: entityVariable, + patterns: [{ + type: 'bgp', + triples: [{ + subject: entityVariable, + predicate: c2, + object: c3, + }], + }], + }, + { + type: 'filter', + expression: { + type: 'operation', + operator: 'notexists', + args: [ + { + type: 'bgp', + triples: [{ + subject: entityVariable, + predicate, + object: c1, + }], + }, + ], + }, + }, + ], + orders: [], + graphWhere: [], + }); + }); + it('builds a query with a gt operator.', (): void => { expect(builder.buildEntitySelectPatternsFromOptions( entityVariable, @@ -1919,11 +1952,6 @@ describe('A SparqlQueryBuilder', (): void => { predicate: predicate2, object: c2, }, - { - subject: c1, - predicate: DataFactory.namedNode('https://example.com/name'), - object: c3, - }, ], }, {