diff --git a/.changeset/unlucky-lobsters-jam.md b/.changeset/unlucky-lobsters-jam.md new file mode 100644 index 000000000..92d168c3d --- /dev/null +++ b/.changeset/unlucky-lobsters-jam.md @@ -0,0 +1,6 @@ +--- +"@cube-creator/cli": patch +"@cube-creator/ui": patch +--- + +Improve error messages and prevent mixing language and datatype diff --git a/apis/core/bootstrap/shapes/column-mapping.ts b/apis/core/bootstrap/shapes/column-mapping.ts index f324269d9..c65198ccd 100644 --- a/apis/core/bootstrap/shapes/column-mapping.ts +++ b/apis/core/bootstrap/shapes/column-mapping.ts @@ -67,6 +67,16 @@ ${literalShapeId} { ${sh.maxCount} 1 ; ${sh.order} 60 ; ] ; + + ${sh.message} "Errors" ; + ${sh.node} [ + ${sh.message} "Either set Language or Data type, not both" ; + ${dash.hidden} true ; + ${sh.or} ( + [ ${sh.path} ${cc.datatype} ; ${sh.hasValue} ${xsd.string} ] + [ ${sh.path} [ ${sh.alternativePath} (${cc.language} ${cc.datatype})] ; ${sh.maxCount} 1 ] + ) + ] . ${datatypes.map(([id, labels]) => labels.map(label => turtle`${id} ${rdfs.label} "${label}" .`))} diff --git a/apis/core/lib/domain/column-mapping/create.ts b/apis/core/lib/domain/column-mapping/create.ts index 844fe3048..4b95996ff 100644 --- a/apis/core/lib/domain/column-mapping/create.ts +++ b/apis/core/lib/domain/column-mapping/create.ts @@ -80,12 +80,15 @@ async function createLiteralColumnMapping({ targetProperty, table, source, resou throw new NotFoundError(columnId) } + const language = resource.out(cc.language).value + const datatype = language ? rdf.langString : resource.out(cc.datatype).term as NamedNode + return table.addLiteralColumnMapping({ store, sourceColumn: column, targetProperty, - datatype: resource.out(cc.datatype).term as NamedNode, - language: resource.out(cc.language).value, + datatype, + language, defaultValue: resource.out(cc.defaultValue).term, dimensionType: resource.out(cc.dimensionType).term, }) diff --git a/apis/core/test/domain/column-mapping/create.test.ts b/apis/core/test/domain/column-mapping/create.test.ts index 2eb1a2241..d9021914e 100644 --- a/apis/core/test/domain/column-mapping/create.test.ts +++ b/apis/core/test/domain/column-mapping/create.test.ts @@ -98,7 +98,7 @@ describe('domain/column-mapping/create', () => { .node($rdf.namedNode('')) .addOut(cc.sourceColumn, $rdf.namedNode('my-column')) .addOut(cc.targetProperty, $rdf.namedNode('test')) - .addOut(cc.datatype, xsd.integer) + .addOut(cc.datatype, xsd.string) // becomes rdf.langString because of language .addOut(cc.language, $rdf.literal('fr')) .addOut(cc.defaultValue, $rdf.literal('test')) @@ -121,7 +121,7 @@ describe('domain/column-mapping/create', () => { minCount: 1, }, { path: cc.datatype, - hasValue: xsd.integer, + hasValue: rdf.langString, minCount: 1, }, { path: cc.language, diff --git a/cli/lib/validation.ts b/cli/lib/validation.ts index de6084847..3111c7a16 100644 --- a/cli/lib/validation.ts +++ b/cli/lib/validation.ts @@ -1,11 +1,13 @@ -import type { NamedNode, Quad } from '@rdfjs/types' +import type { Literal, NamedNode, Quad } from '@rdfjs/types' import { turtle } from '@tpluscode/rdf-string' import type { Context } from 'barnard59-core' import { validateQuad } from 'rdf-validate-datatype' +import TermMap from '@rdfjs/term-map' +import { xsd } from '@tpluscode/rdf-ns-builders' export function validate(this: Context, quad: Quad): Quad { if (!validateQuad(quad)) { - throw new Error(`Quad object has invalid datatype:\n\t${quadToString(quad)}`) + throw new Error(`${errorMessage(quad)}\n\n${quadToString(quad)}`) } return quad @@ -17,3 +19,44 @@ function quadToString(quad: Quad): string { graph: quad.graph as NamedNode, }) } + +const messages = new TermMap([ + [xsd.boolean, 'Boolean values should be formatted as "true" and "false" (or "0" and "1"). See also https://www.w3.org/TR/xmlschema-2/#boolean.'], + [xsd.date, 'Dates should be formatted as YYYY-MM-DD as specified by ISO 8601. See also https://www.w3.org/TR/xmlschema-2/#date.'], + [xsd.dateTime, 'Values should be formatted as YYYY-MM-DDThh:mm:ss. See also https://www.w3.org/TR/xmlschema-2/#dateTime.'], + [xsd.gDay, 'Days should be formatted as DD. See also https://www.w3.org/TR/xmlschema-2/#gDay.'], + [xsd.decimal, 'Decimals should be formatted as digits, possibly with a period to separate the fractional part. An optional leading sign is allowed. See also https://www.w3.org/TR/xmlschema-2/#decimal.'], + [xsd.integer, 'Integers should be formatted as digits, with an optional leading sign. See also https://www.w3.org/TR/xmlschema-2/#integer.'], + [xsd.gMonth, 'Months should be formatted as MM. See also https://www.w3.org/TR/xmlschema-2/#gMonth.'], + [xsd.string, 'See also https://www.w3.org/TR/xmlschema-2/#string.'], + [xsd.time, 'Values should be formatted as hh:mm:ss. See also https://www.w3.org/TR/xmlschema-2/#time.'], + [xsd.gYear, 'Years should be formatted as YYYY. See also https://www.w3.org/TR/xmlschema-2/#gYear.'], + [xsd.gYearMonth, 'Values should be formatted as YYYY-MM. See also https://www.w3.org/TR/xmlschema-2/#gYearMonth.'], +]) + +const typeName = new TermMap([ + [xsd.boolean, 'boolean'], + [xsd.date, 'date'], + [xsd.dateTime, 'dateTime'], + [xsd.gDay, 'day'], + [xsd.decimal, 'decimal'], + [xsd.integer, 'integer'], + [xsd.gMonth, 'month'], + [xsd.string, 'string'], + [xsd.time, 'time'], + [xsd.gYear, 'year'], + [xsd.gYearMonth, 'year+month'], +]) + +function errorMessage(quad: Quad): string { + const literal = quad.object as Literal + if (!literal.datatype) { + return `Invalid datatype for non-literal ${literal.value}` + } + const message = messages.get(literal.datatype) + if (message) { + return `the value "${literal.value}" in your CSV file does not conform to the selected datatype "${typeName.get(literal.datatype)}".\n${message}` + } + + return `literal "${literal.value}" is not a valid value for datatype <${literal.datatype.value}>.` +} diff --git a/e2e-tests/column-mapping/create.hydra b/e2e-tests/column-mapping/create.hydra index 166c44423..03896045d 100644 --- a/e2e-tests/column-mapping/create.hydra +++ b/e2e-tests/column-mapping/create.hydra @@ -27,7 +27,6 @@ With Class api:Table { cc:sourceColumn ; cc:targetProperty "dimension/year-built" ; cc:datatype xsd:date ; - cc:language "en" ; cc:defaultValue "2000-01-01" . ``` } => { @@ -42,7 +41,6 @@ With Class api:Table { Expect Property cc:datatype { Expect Id xsd:date } - Expect Property cc:language "en" Expect Property cc:defaultValue "2000-01-01" } @@ -58,7 +56,6 @@ With Class api:Table { cc:sourceColumn ; cc:targetProperty "dimension/year-built" ; cc:datatype xsd:date ; - cc:language "en" ; cc:defaultValue "2000-01-01" . ``` } => { diff --git a/e2e-tests/column-mapping/update-literal.hydra b/e2e-tests/column-mapping/update-literal.hydra index 793e7e3eb..0b27e1473 100644 --- a/e2e-tests/column-mapping/update-literal.hydra +++ b/e2e-tests/column-mapping/update-literal.hydra @@ -28,7 +28,7 @@ With Class cc:LiteralColumnMapping { a cc:ColumnMapping, cc:LiteralColumnMapping ; cc:sourceColumn ; cc:targetProperty schema:identifier ; - cc:datatype xsd:integer ; + cc:datatype xsd:string ; cc:language "en" ; . ``` @@ -38,7 +38,7 @@ With Class cc:LiteralColumnMapping { Expect Id } Expect Property cc:datatype { - Expect Id xsd:integer + Expect Id xsd:string } Expect Property cc:language "en" }