Skip to content

Commit

Permalink
fix: correctly selects properties of primitives
Browse files Browse the repository at this point in the history
  • Loading branch information
fedeci committed Feb 16, 2024
1 parent ffd7994 commit bd17cea
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 15 deletions.
19 changes: 19 additions & 0 deletions src/express-validator.spec.ts
Expand Up @@ -165,3 +165,22 @@ describe('ExpressValidator', () => {
});
});
});

describe('High level tests', () => {
const { body } = new ExpressValidator();
it('should error when .exists() is used on a nested field of a primitive', async () => {
const req = { body: { foo: "hello" } };
const result = await body("foo.nop").exists().run(req);
expect(result.isEmpty()).toEqual(false);
})
it('should not error when .not().exists() is used on a nested field of a primitive', async () => {
const req = { body: { foo: 'hello' } };
const result = await body('foo.nop').not().exists().run(req);
expect(result.isEmpty()).toEqual(true);
});
it('should not error when .exists() is used on a nested field of a primitive using wildcard', async () => {
const req = { body: { foo: 'hello' } };
const result = await body('foo.*.nop').exists().run(req);
expect(result.isEmpty()).toEqual(true);
});
})
35 changes: 26 additions & 9 deletions src/field-selection.spec.ts
Expand Up @@ -19,14 +19,16 @@ describe('selectFields()', () => {
const instances = selectFields(req, ['foo', 'baz'], ['cookies']);

expect(instances).toHaveLength(2);
expect(instances[0]).toMatchObject({
expect(instances[0]).toEqual({
location: 'cookies',
path: 'foo',
originalPath: 'foo',
value: 'bar',
});
expect(instances[1]).toMatchObject({
expect(instances[1]).toEqual({
location: 'cookies',
path: 'baz',
originalPath: 'baz',
value: 'qux',
});
});
Expand Down Expand Up @@ -113,14 +115,19 @@ describe('selectFields()', () => {
const req = {
query: { foo: ['bar', 'baz'] },
};
const instances = selectFields(req, ['foo[1]'], ['query']);
const instances = selectFields(req, ['foo[1]', 'foo[2]'], ['query']);

expect(instances).toHaveLength(1);
expect(instances).toHaveLength(2);
expect(instances[0]).toMatchObject({
location: 'query',
path: 'foo[1]',
value: 'baz',
});
expect(instances[1]).toMatchObject({
location: 'query',
path: 'foo[2]',
value: undefined,
});
});

it('selects from headers using lowercase', () => {
Expand Down Expand Up @@ -154,7 +161,7 @@ describe('selectFields()', () => {
});

it('selects inexistent properties', () => {
const instances = selectFields({}, ['foo.bar.baz'], ['cookies']);
const instances = selectFields({ cookies: { } }, ['foo.bar.baz'], ['cookies']);

expect(instances).toHaveLength(1);
expect(instances[0]).toEqual({
Expand All @@ -165,14 +172,24 @@ describe('selectFields()', () => {
});
});

it('does not select properties of primitives', () => {
it('selects properties of primitives', () => {
const req = {
body: { foo: 1 },
};
const instances = selectFields(req, ['foo.toFixed'], ['body']);
expect(instances).toHaveLength(0);
});
const instances = selectFields(req, ['foo.toFixed', 'foo.nop'], ['body']);

expect(instances).toHaveLength(2);
expect(instances[0]).toMatchObject({
location: 'body',
path: 'foo.toFixed',
value: expect.any(Function),
});
expect(instances[1]).toMatchObject({
location: 'body',
path: 'foo.nop',
value: undefined,
});
});
it('deduplicates field instances', () => {
const req = {
body: {
Expand Down
18 changes: 12 additions & 6 deletions src/field-selection.ts
Expand Up @@ -52,13 +52,19 @@ function expandPath(object: any, path: string | string[], currPath: readonly str
const rest = segments.slice(1);

if (object != null && !_.isObjectLike(object)) {
if (key === '**' && !rest.length) {
// globstar leaves are always selected
return [reconstructFieldPath(currPath)];
if (key === '**') {
if (!rest.length) {
// globstar leaves are always selected
return [reconstructFieldPath(currPath)];
}
return []
}

// there still are paths to traverse, but value is a primitive, stop
return [];
if (key === "*") {
// wildcard position does not exist
return []
}
// value is a primitive, there are still still paths to traverse that will be likely undefined, we return the entire path
return [reconstructFieldPath([...currPath, ...segments])];
}

// Use a non-null value so that inexistent fields are still selected
Expand Down

0 comments on commit bd17cea

Please sign in to comment.