Skip to content

Commit

Permalink
Merge pull request #55 from ego-io/feature/string-specification
Browse files Browse the repository at this point in the history
  • Loading branch information
nichenqin committed Dec 14, 2022
2 parents 765df9a + add050f commit cf4095d
Show file tree
Hide file tree
Showing 9 changed files with 168 additions and 111 deletions.
1 change: 0 additions & 1 deletion apps/backend/webpack.config.prod.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const nodeExternals = require('webpack-node-externals');
const { RunScriptWebpackPlugin } = require('run-script-webpack-plugin');

module.exports = function (options, webpack) {
return {
Expand Down
2 changes: 1 addition & 1 deletion apps/web/components/filters-editor/field-filter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const FieldFilter: React.FC<IProps> = ({ schema, value, onChange, onRemov

useEffect(() => {
if (selectedField && operator) {
onChange(selectedField.createFilter(operator, fieldValue as any), index)
onChange(selectedField.createFilter(operator as any, fieldValue as any), index)
} else {
onChange(null, index)
}
Expand Down
4 changes: 4 additions & 0 deletions apps/web/components/filters-editor/operator-selector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ export const OperatorSelector: React.FC<IProps> = ({ value, field, onChange }) =
data = [
{ value: '$eq', label: 'equal' },
{ value: '$neq', label: 'not equal' },
{ value: '$contains', label: 'contains' },
{ value: '$starts_with', label: 'startsWith' },
{ value: '$ends_with', label: 'endsWith' },
{ value: '$regex', label: 'regex' },
]
} else if (field instanceof NumberField) {
data = [
Expand Down
84 changes: 49 additions & 35 deletions packages/core/filter/__snapshots__/filter.test.ts.snap
Original file line number Diff line number Diff line change
@@ -1,71 +1,85 @@
// Vitest Snapshot v1

exports[`should convert root filter to record specification 1`] = `
exports[`should create root filter 1`] = `
OptionType {
Symbol(T): true,
Symbol(Val): StringEqual {
"name": "name",
"value": "value",
"value": "hello",
},
}
`;

exports[`should convert root filter to record specification 2`] = `
exports[`should create root filter 2`] = `
OptionType {
Symbol(T): true,
Symbol(Val): And {
"left": StringEqual {
"name": "field1",
"value": "1",
},
"right": Not {
"spec": StringEqual {
"name": "field2",
"value": "2",
},
},
Symbol(Val): StringContain {
"name": "name",
"value": "hello",
},
}
`;

exports[`should convert root filter to record specification 3`] = `
exports[`should create root filter 3`] = `
OptionType {
Symbol(T): true,
Symbol(Val): Not {
"spec": StringEqual {
"name": "field2",
"value": "2",
},
Symbol(Val): StringStartsWith {
"name": "name",
"value": "starts with",
},
}
`;

exports[`should convert root filter to record specification 4`] = `
exports[`should create root filter 4`] = `
OptionType {
Symbol(T): true,
Symbol(Val): Or {
"left": StringEqual {
"name": "field1",
"value": "1",
},
"right": Not {
"spec": StringEqual {
"name": "field2",
"value": "2",
},
Symbol(Val): StringEndsWith {
"name": "name",
"value": "ends with",
},
}
`;

exports[`should create root filter 5`] = `
OptionType {
Symbol(T): true,
Symbol(Val): Not {
"spec": NumberEqual {
"name": "name",
"value": 1,
},
},
}
`;

exports[`should convert root filter to record specification 5`] = `
exports[`should create root filter 6`] = `
OptionType {
Symbol(T): true,
Symbol(Val): Not {
"spec": StringEqual {
"name": "field1",
"value": "1",
"spec": NumberEqual {
"name": "name.nested",
"value": 1,
},
},
}
`;

exports[`should create root filter 7`] = `
OptionType {
Symbol(T): true,
Symbol(Val): StringEqual {
"name": "name",
"value": "hello",
},
}
`;

exports[`should create root filter 8`] = `
OptionType {
Symbol(T): true,
Symbol(Val): StringEqual {
"name": "name",
"value": "hello",
},
}
`;
86 changes: 18 additions & 68 deletions packages/core/filter/filter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,24 @@ test.each<IRootFilter>([
path: 'name',
value: 'hello',
},
{
type: 'string',
operator: '$contains',
path: 'name',
value: 'hello',
},
{
type: 'string',
operator: '$starts_with',
path: 'name',
value: 'starts with',
},
{
type: 'string',
operator: '$ends_with',
path: 'name',
value: 'ends with',
},
{
type: 'number',
operator: '$neq',
Expand Down Expand Up @@ -48,75 +66,7 @@ test.each<IRootFilter>([
])('should create root filter', (filter) => {
const parsed = rootFilter.parse(filter)
expect(parsed).toEqual(filter)
})

test.each<IRootFilter>([
{
type: 'string',
value: 'value',
operator: '$eq',
path: 'name',
},
[
{
type: 'string',
value: '1',
operator: '$eq',
path: 'field1',
},
{
type: 'string',
value: '2',
operator: '$neq',
path: 'field2',
},
],
[
{
conjunction: '$or',
children: [
{
type: 'string',
value: '2',
operator: '$neq',
path: 'field2',
},
],
},
],
[
{
conjunction: '$or',
children: [
{
type: 'string',
value: '1',
operator: '$eq',
path: 'field1',
},
{
type: 'string',
value: '2',
operator: '$neq',
path: 'field2',
},
],
},
],
[
{
conjunction: '$not',
children: [
{
type: 'string',
value: '1',
operator: '$eq',
path: 'field1',
},
],
},
],
])('should convert root filter to record specification', (filter) => {
const spec = convertFilterSpec(filter)
expect(spec).toMatchSnapshot()
})
21 changes: 18 additions & 3 deletions packages/core/filter/filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,28 @@ import type { CompositeSpecification } from '@egodb/domain'
import type { Option } from 'oxide.ts'
import { None, Some } from 'oxide.ts'
import { z } from 'zod'
import { NumberEqual, StringEqual } from '../record'
import { NumberEqual, StringContain, StringEndsWith, StringEqual, StringStartsWith } from '../record'

const $eq = z.literal('$eq')
const $neq = z.literal('$neq')
const $contains = z.literal('$contains')
const $starts_with = z.literal('$starts_with')
const $ends_with = z.literal('$ends_with')
const $regex = z.literal('$regex')

const baseFilter = z.object({
path: z.string().min(1),
})

const stringFilterOperators = z.union([$eq, $neq])
const stringFilterOperators = z.union([$eq, $neq, $contains, $starts_with, $ends_with, $regex])
const stringFilter = z
.object({
type: z.literal('string'),
operator: stringFilterOperators,
value: z.string().nullable(),
})
.merge(baseFilter)

export type IStringFilter = z.infer<typeof stringFilter>
export type IStringFilterOperator = z.infer<typeof stringFilterOperators>

Expand Down Expand Up @@ -86,8 +91,18 @@ const convertFilter = (filter: IFilter): Option<CompositeSpecification> => {
case '$eq': {
return Some(new StringEqual(filter.path, filter.value))
}
case '$neq':
case '$neq': {
return Some(new StringEqual(filter.path, filter.value).not())
}
case '$contains': {
return Some(new StringContain(filter.path, filter.value))
}
case '$starts_with': {
return Some(new StringStartsWith(filter.path, filter.value))
}
case '$ends_with': {
return Some(new StringEndsWith(filter.path, filter.value))
}

default:
return None
Expand Down
5 changes: 4 additions & 1 deletion packages/core/record/specifications/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { type Record } from '../record'
import type { NumberEqual } from './number.specification'
import type { WithRecordId } from './record-id.specifaction'
import type { WithRecordTableId } from './record-table-id.specification'
import type { StringContain, StringEqual } from './string.specification'
import type { StringContain, StringEndsWith, StringEqual, StringRegex, StringStartsWith } from './string.specification'

interface IRecordSpecVisitor {
idEqual(s: WithRecordId): void
Expand All @@ -14,6 +14,9 @@ interface IRecordSpecVisitor {
interface IRecordValueVisitor {
stringEqual(s: StringEqual): void
stringContain(s: StringContain): void
stringStartsWith(s: StringStartsWith): void
stringEndsWith(s: StringEndsWith): void
stringRegex(s: StringRegex): void

numberEqual(s: NumberEqual): void
}
Expand Down
48 changes: 48 additions & 0 deletions packages/core/record/specifications/string.specification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,51 @@ export class StringContain extends RecordValueSpecifcationBase<string> {
return Ok(undefined)
}
}

export class StringStartsWith extends RecordValueSpecifcationBase<string> {
/**
* check whether string starts with given value
* @param r - record
* @returns boolean
*/
isSatisfiedBy(r: Record): boolean {
return r.values.getStringValue(this.name).mapOr(false, (value) => value.startsWith(this.value))
}

accept(v: IRecordVisitor): Result<void, string> {
v.stringStartsWith(this)
return Ok(undefined)
}
}

export class StringEndsWith extends RecordValueSpecifcationBase<string> {
/**
* check whether string ends with given value
* @param r - record
* @returns boolean
*/
isSatisfiedBy(r: Record): boolean {
return r.values.getStringValue(this.name).mapOr(false, (value) => value.endsWith(this.value))
}

accept(v: IRecordVisitor): Result<void, string> {
v.stringEndsWith(this)
return Ok(undefined)
}
}

export class StringRegex extends RecordValueSpecifcationBase<string> {
/**
* check whether string match given regex
* @param r - record
* @returns boolean
*/
isSatisfiedBy(r: Record): boolean {
return r.values.getStringValue(this.name).mapOr(false, (value) => new RegExp(this.value).test(value))
}

accept(v: IRecordVisitor): Result<void, string> {
v.stringRegex(this)
return Ok(undefined)
}
}
Loading

0 comments on commit cf4095d

Please sign in to comment.