Skip to content
This repository was archived by the owner on May 15, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions browser/components/Report/Error/Cell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ export function CellMaxLengthError(props: {
<Code fz="lg" fw="bold">
{error.rowNumber}
</Code>{" "}
{t("is more than")}
<Code fz="lg" fw="bold">
{error.maxLength}
</Code>{" "}
Expand Down
9 changes: 9 additions & 0 deletions browser/components/Report/Error/Data.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type * as errorTypes from "@dpkit/lib"
import { Text } from "@mantine/core"
import { useTranslation } from "react-i18next"

export function DataError(props: { error: errorTypes.DataError }) {
const { t } = useTranslation()

return <Text>{t(props.error.message as any)}</Text>
}
14 changes: 13 additions & 1 deletion browser/components/Report/Error/Error.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,29 @@ import {
CellTypeError,
CellUniqueError,
} from "./Cell.tsx"
import { DataError } from "./Data.tsx"
import { FieldNameError, FieldTypeError } from "./Field.tsx"
import { FieldsExtraError, FieldsMissingError } from "./Fields.tsx"
import { BytesError, EncodingError, HashError } from "./File.tsx"
import { ForeignKeyError } from "./ForeignKey.tsx"
import { MetadataError } from "./Metadata.tsx"
import { RowUniqueError } from "./Row.tsx"

export function Error(props: {
// TODO: should be lib.Error
error: errorTypes.MetadataError | errorTypes.FileError | errorTypes.TableError
error:
| errorTypes.MetadataError
| errorTypes.DataError
| errorTypes.FileError
| errorTypes.TableError
}) {
const { error } = props

switch (error.type) {
case "metadata":
return <MetadataError error={error} />
case "data":
return <DataError error={error} />
case "file/bytes":
return <BytesError error={error} />
case "file/hash":
Expand Down Expand Up @@ -68,5 +76,9 @@ export function Error(props: {
return <CellEnumError error={error} />
case "cell/jsonSchema":
return <CellJsonSchemaError error={error} />
case "foreignKey":
return <ForeignKeyError error={error} />
default:
return null
}
}
20 changes: 20 additions & 0 deletions browser/components/Report/Error/ForeignKey.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type * as errorTypes from "@dpkit/lib"
import { Code, Text } from "@mantine/core"
import { useTranslation } from "react-i18next"

// TODO: improve error message
export function ForeignKeyError(props: { error: errorTypes.ForeignKeyError }) {
const { t } = useTranslation()
return (
<Text>
{t("Foreign key violation in field(s)")}{" "}
<Code fz="lg" fw="bold">
{props.error.foreignKey.fields.join(", ")}
</Code>
{": "}
<Code fz="lg" fw="bold">
{props.error.cells.join(", ")}
</Code>
</Text>
)
}
9 changes: 7 additions & 2 deletions browser/components/Report/Report.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import type { FileError, MetadataError, TableError } from "@dpkit/lib"
import type {
DataError,
FileError,
MetadataError,
TableError,
} from "@dpkit/lib"
import { Card, Divider, ScrollArea, Stack, Tabs } from "@mantine/core"
import { groupBy } from "es-toolkit"
import { useState } from "react"
Expand All @@ -7,7 +12,7 @@ import { objectKeys } from "ts-extras"
import { Error } from "./Error/Error.tsx"

export function Report(props: {
errors?: (MetadataError | FileError | TableError)[]
errors?: (MetadataError | DataError | FileError | TableError)[]
}) {
const { t } = useTranslation()
const { errors } = props
Expand Down
1 change: 1 addition & 0 deletions browser/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
"at": "bei",
"The cell values of the fields": "Die Zellwerte der Felder",
"are not unique": "sind nicht eindeutig",
"Foreign key violation in field(s)": "Fremdschlüsselverletzung in Feld(ern)",
"Under Construction": "In Bearbeitung",
"This tool is currently under construction and not yet available": "Dieses Werkzeug befindet sich derzeit in der Entwicklung und ist noch nicht verfügbar",
"Brought to you by": "Präsentiert von",
Expand Down
1 change: 1 addition & 0 deletions browser/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
"at": "at",
"The cell values of the fields": "The cell values of the fields",
"are not unique": "are not unique",
"Foreign key violation in field(s)": "Foreign key violation in field(s)",
"Under Construction": "Under Construction",
"This tool is currently under construction and not yet available": "This tool is currently under construction and not yet available",
"Brought to you by": "Brought to you by",
Expand Down
1 change: 1 addition & 0 deletions browser/locales/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
"at": "en",
"The cell values of the fields": "Los valores de celda de los campos",
"are not unique": "no son únicos",
"Foreign key violation in field(s)": "Violación de clave foránea en campo(s)",
"Under Construction": "En construcción",
"This tool is currently under construction and not yet available": "Esta herramienta está actualmente en construcción y aún no está disponible",
"Brought to you by": "Presentado por",
Expand Down
1 change: 1 addition & 0 deletions browser/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
"at": "à",
"The cell values of the fields": "Les valeurs de cellule des champs",
"are not unique": "ne sont pas uniques",
"Foreign key violation in field(s)": "Violation de clé étrangère dans le(s) champ(s)",
"Under Construction": "En construction",
"This tool is currently under construction and not yet available": "Cet outil est actuellement en construction et n'est pas encore disponible",
"Brought to you by": "Proposé par",
Expand Down
1 change: 1 addition & 0 deletions browser/locales/it.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
"at": "a",
"The cell values of the fields": "I valori delle celle dei campi",
"are not unique": "non sono univoci",
"Foreign key violation in field(s)": "Violazione di chiave esterna nel/i campo/i",
"Under Construction": "In costruzione",
"This tool is currently under construction and not yet available": "Questo strumento è attualmente in costruzione e non ancora disponibile",
"Brought to you by": "Offerto da",
Expand Down
1 change: 1 addition & 0 deletions browser/locales/pt.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
"at": "em",
"The cell values of the fields": "Os valores das células dos campos",
"are not unique": "não são únicos",
"Foreign key violation in field(s)": "Violação de chave estrangeira no(s) campo(s)",
"Under Construction": "Em construção",
"This tool is currently under construction and not yet available": "Esta ferramenta está atualmente em construção e ainda não está disponível",
"Brought to you by": "Apresentado por",
Expand Down
1 change: 1 addition & 0 deletions browser/locales/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
"at": "в",
"The cell values of the fields": "Значения ячеек полей",
"are not unique": "не уникальны",
"Foreign key violation in field(s)": "Нарушение внешнего ключа в поле(ях)",
"Under Construction": "В разработке",
"This tool is currently under construction and not yet available": "Этот инструмент в настоящее время находится в разработке и пока недоступен",
"Brought to you by": "Представлено",
Expand Down
1 change: 1 addition & 0 deletions browser/locales/uk.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
"at": "в",
"The cell values of the fields": "Значення комірок полів",
"are not unique": "не унікальні",
"Foreign key violation in field(s)": "Порушення зовнішнього ключа в полі(ях)",
"Under Construction": "В розробці",
"This tool is currently under construction and not yet available": "Цей інструмент наразі перебуває в розробці і ще недоступний",
"Brought to you by": "Представлено",
Expand Down
2 changes: 1 addition & 1 deletion cli/commands/dialect/validate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export const validateDialectCommand = new Command("validate")
? await session.task("Loading dialect", resolveDialect(resource.dialect))
: undefined

const { descriptor } = path
const descriptor = path
? await session.task("Loading descriptor", loadDescriptor(path))
: dialect
? { descriptor: dialect }
Expand Down
4 changes: 2 additions & 2 deletions cli/commands/resource/validate.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,11 @@ describe("resource validate", () => {
expect(result.errors.length).toBeGreaterThan(0)
})

it("should validate resource with schema", async () => {
it.skip("should validate resource with schema", async () => {
const csvPath = await writeTempFile("id,name\n1,alice\n2,bob")
const resourceContent = JSON.stringify({
name: "test-resource",
path: basename(csvPath),
path: `${basename(csvPath)}.csv`,
schema: {
fields: [
{ name: "id", type: "integer" },
Expand Down
3 changes: 1 addition & 2 deletions cli/commands/schema/convert.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,7 @@ export const convertSchemaCommand = new Command("convert")
converter = convertSchemaFromJsonSchema
}

const loaded = await session.task("Loading schema", loadDescriptor(path))
const source = loaded.descriptor
const source = await session.task("Loading schema", loadDescriptor(path))
const target = await session.task("Converting schema", converter(source))

if (!options.toPath) {
Expand Down
2 changes: 1 addition & 1 deletion cli/commands/schema/validate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export const validateSchemaCommand = new Command("validate")
? await session.task("Loading schema", resolveSchema(resource.schema))
: undefined

const { descriptor } = path
const descriptor = path
? await session.task("Loading descriptor", loadDescriptor(path))
: schema
? { descriptor: schema }
Expand Down
9 changes: 7 additions & 2 deletions cli/components/ErrorGrid.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import type { FileError, MetadataError, TableError } from "@dpkit/lib"
import type {
DataError,
FileError,
MetadataError,
TableError,
} from "@dpkit/lib"
import { DataFrame } from "nodejs-polars"
import React from "react"
import { TableGrid } from "./TableGrid.tsx"

export function ErrorGrid(props: {
errors: (TableError | MetadataError | FileError)[]
errors: (TableError | DataError | MetadataError | FileError)[]
quit?: boolean
}) {
// TODO: Property process errors
Expand Down
13 changes: 10 additions & 3 deletions cli/helpers/error.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
import type { FileError, MetadataError, TableError } from "@dpkit/lib"
import type {
DataError,
FileError,
MetadataError,
TableError,
} from "@dpkit/lib"
import { countBy } from "es-toolkit"
import type { Session } from "./session.ts"

export async function selectErrorResource(
session: Session,
errors: ((TableError | MetadataError | FileError) & { resource: string })[],
errors: ((TableError | MetadataError | DataError | FileError) & {
resource: string
})[],
) {
const groups = countBy(errors, error => error.resource)

Expand All @@ -25,7 +32,7 @@ export async function selectErrorResource(

export async function selectErrorType(
session: Session,
errors: (TableError | MetadataError | FileError)[],
errors: (TableError | MetadataError | DataError | FileError)[],
) {
const groups = countBy(errors, error => error.type)

Expand Down
6 changes: 6 additions & 0 deletions core/descriptor/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export { parseDescriptor } from "./process/parse.ts"
export { stringifyDescriptor } from "./process/stringify.ts"
export { loadDescriptor } from "./load.ts"
export { saveDescriptor } from "./save.ts"
export type { Descriptor } from "./Descriptor.ts"
export { isDescriptor } from "./Descriptor.ts"
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,8 @@ describe("loadDescriptor", () => {
})

it("loads a local descriptor from a file path", async () => {
const { basepath, descriptor } = await loadDescriptor(
getFixturePath("schema.json"),
)
const descriptor = await loadDescriptor(getFixturePath("schema.json"))

expect(basepath).toEqual(getFixturePath())
expect(descriptor).toEqual(expectedDescriptor)
})

Expand All @@ -44,9 +41,8 @@ describe("loadDescriptor", () => {
json: () => Promise.resolve(expectedDescriptor),
})

const { basepath, descriptor } = await loadDescriptor(testUrl)
const descriptor = await loadDescriptor(testUrl)

expect(basepath).toEqual("https://example.com")
expect(descriptor).toEqual(expectedDescriptor)
expect(fetch).toHaveBeenCalledWith(testUrl)
})
Expand Down
39 changes: 17 additions & 22 deletions core/general/descriptor/load.ts → core/descriptor/load.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { node } from "../node.ts"
import { getProtocol } from "../path.ts"
import { getBasepath, isRemotePath } from "../path.ts"
import { node } from "../node/index.ts"
import { getProtocol, isRemotePath } from "../path/index.ts"
import { parseDescriptor } from "./process/parse.ts"

/**
Expand All @@ -19,36 +18,32 @@ export async function loadDescriptor(
throw new Error("Cannot load descriptor for security reasons")
}

return isRemote
? await loadRemoteDescriptor(path)
: await loadLocalDescriptor(path)
return !isRemote
? await loadLocalDescriptor(path)
: await loadRemoteDescriptor(path)
}

const ALLOWED_REMOTE_PROTOCOLS = ["http", "https", "ftp", "ftps"]
async function loadLocalDescriptor(path: string) {
if (!node) {
throw new Error("File system is not supported in this environment")
}

const text = await node.fs.readFile(path, "utf-8")
const descriptor = parseDescriptor(text)

return descriptor
}

async function loadRemoteDescriptor(path: string) {
const url = new URL(path)

const protocol = getProtocol(path)
if (!ALLOWED_REMOTE_PROTOCOLS.includes(protocol)) {
if (!["http", "https", "ftp", "ftps"].includes(protocol)) {
throw new Error(`Unsupported remote protocol: ${protocol}`)
}

const response = await fetch(url.toString())
const descriptor = (await response.json()) as Record<string, any>
const basepath = getBasepath(response.url ?? path) // supports redirects

return { descriptor, basepath }
}

async function loadLocalDescriptor(path: string) {
if (!node) {
throw new Error("File system is not supported in this environment")
}

const text = await node.fs.readFile(path, "utf-8")
const descriptor = parseDescriptor(text)
const basepath = getBasepath(path)

return { descriptor, basepath }
return descriptor
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { node } from "../node.ts"
import { node } from "../node/index.ts"
import type { Descriptor } from "./Descriptor.ts"
import { stringifyDescriptor } from "./process/stringify.ts"

Expand Down
2 changes: 1 addition & 1 deletion core/dialect/Dialect.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Metadata } from "../general/index.ts"
import type { Metadata } from "../metadata/index.ts"

/**
* Descriptor that describes the structure of tabular data, such as delimiters,
Expand Down
2 changes: 1 addition & 1 deletion core/dialect/assert.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Descriptor } from "../descriptor/index.ts"
import { AssertionError } from "../error/index.ts"
import type { Descriptor } from "../general/index.ts"
import type { Dialect } from "./Dialect.ts"
import { validateDialect } from "./validate.ts"

Expand Down
2 changes: 1 addition & 1 deletion core/dialect/convert/fromDescriptor.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Descriptor } from "../../general/index.ts"
import type { Descriptor } from "../../descriptor/index.ts"

export function convertDialectFromDescriptor(descriptor: Descriptor) {
descriptor = globalThis.structuredClone(descriptor)
Expand Down
2 changes: 1 addition & 1 deletion core/dialect/convert/toDescriptor.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Descriptor } from "../../general/index.ts"
import type { Descriptor } from "../../descriptor/index.ts"
import type { Dialect } from "../Dialect.ts"

export function convertDialectToDescriptor(dialect: Dialect) {
Expand Down
4 changes: 2 additions & 2 deletions core/dialect/load.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { loadDescriptor } from "../general/index.ts"
import { loadDescriptor } from "../descriptor/index.ts"
import { assertDialect } from "./assert.ts"

/**
* Load a Dialect descriptor (JSON Object) from a file or URL
* Ensures the descriptor is valid against its profile
*/
export async function loadDialect(path: string) {
const { descriptor } = await loadDescriptor(path)
const descriptor = await loadDescriptor(path)
const dialect = await assertDialect(descriptor)
return dialect
}
2 changes: 1 addition & 1 deletion core/dialect/save.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { saveDescriptor } from "../general/index.ts"
import { saveDescriptor } from "../descriptor/index.ts"
import type { Dialect } from "./Dialect.ts"
import { convertDialectToDescriptor } from "./convert/toDescriptor.ts"

Expand Down
Loading