Skip to content

Stored filter artifacts are restored unvalidated; FilterHandler has no way to parse an artifact #130

Description

@matej21

Summary

Filter artifacts are restored from storage without any check of their shape, and FilterHandler has no way to parse one. A stored record written by an older release, a hand-edited URL parameter, or a saved filter preset from a database all reach handler.toWhere() and isActive() as whatever JSON.parse returned.

Environment

  • @contember/bindx@0.1.52 / @contember/bindx-dataview@0.1.52
  • contember/bindx@main as of 78c1712

Reproduction

// stateStorage: 'url', storageKey: 'grid'; the URL carries ?grid:filters={"title":{}}
const filtering = useFilteringState({
	filters: new Map([['title', { handler: createTextFilterHandler('title') }]]),
	stateStorage: 'url',
	storageKey: 'grid',
})
filtering.resolvedWhere // the text handler reads `artifact.query.length` on `{}` and throws

createWebStorage / createUrlStorage (packages/bindx-dataview/src/stateStorage.ts) return JSON.parse(raw) as T, and useFilteringState (packages/bindx-dataview/src/useDataViewState.ts) uses the record as is.

An application that stores filter presets itself (restored through setAllArtifacts) has the same problem one level up: FilterArtifact is a closed union, so turning a parsed JSON object into a Record<string, FilterArtifact> needs either a cast (raw as unknown as Record<string, FilterArtifact>) or a re-implementation of every built-in artifact's shape outside bindx.

Expected behavior

  • A stored artifact that the handler does not recognize is treated as absent (the filter starts at its default), not passed to the handler.
  • An application can validate an artifact against the handler that will read it, without re-implementing bindx's artifact shapes.

Proposal

Add a parser to the handler contract:

interface FilterHandler<TArtifact = FilterArtifact> {
	toWhere(artifact: TArtifact): Record<string, unknown> | undefined
	isActive(artifact: TArtifact): boolean
	defaultArtifact(): TArtifact
	/** The artifact `raw` represents, or `undefined` when it is not one this handler reads. */
	parseArtifact?(raw: unknown): TArtifact | undefined
}

Open design questions for the maintainers: optional (non-breaking for custom handlers, which then keep today's trust) or required; and whether setAllArtifacts should take unparsed input or keep its typed signature with a separate parseArtifacts(record) helper on the filtering state.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions