Skip to content

Commit

Permalink
Excess intersections
Browse files Browse the repository at this point in the history
  • Loading branch information
cnasikas committed May 4, 2022
1 parent e2b8cf4 commit de4a093
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 41 deletions.
94 changes: 55 additions & 39 deletions x-pack/plugins/cases/common/api/runtime_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,42 +9,18 @@ import { either, fold } from 'fp-ts/lib/Either';
import { identity } from 'fp-ts/lib/function';
import { pipe } from 'fp-ts/lib/pipeable';
import * as rt from 'io-ts';
import { isObject } from 'lodash/fp';
import { formatErrors } from '@kbn/securitysolution-io-ts-utils';

type ErrorFactory = (message: string) => Error;

/**
* @deprecated Use packages/kbn-securitysolution-io-ts-utils/src/format_errors/index.ts
* Bug fix for the TODO is in the format_errors package
*/
export const formatErrors = (errors: rt.Errors): string[] => {
const err = errors.map((error) => {
if (error.message != null) {
return error.message;
} else {
const keyContext = error.context
.filter(
(entry) => entry.key != null && !Number.isInteger(+entry.key) && entry.key.trim() !== ''
)
.map((entry) => entry.key)
.join(',');

const nameContext = error.context.find((entry) => {
// TODO: Put in fix for optional chaining https://github.com/cypress-io/cypress/issues/9298
if (entry.type && entry.type.name) {
return entry.type.name.length > 0;
}
return false;
});
const suppliedValue =
keyContext !== '' ? keyContext : nameContext != null ? nameContext.type.name : '';
const value = isObject(error.value) ? JSON.stringify(error.value) : error.value;
return `Invalid value "${value}" supplied to "${suppliedValue}"`;
}
});

return [...new Set(err)];
};
export type GenericIntersectionC =
// eslint-disable-next-line @typescript-eslint/no-explicit-any
| rt.IntersectionC<[any, any]>
// eslint-disable-next-line @typescript-eslint/no-explicit-any
| rt.IntersectionC<[any, any, any]>
// eslint-disable-next-line @typescript-eslint/no-explicit-any
| rt.IntersectionC<[any, any, any, any]>
// eslint-disable-next-line @typescript-eslint/no-explicit-any
| rt.IntersectionC<[any, any, any, any, any]>;

export const createPlainError = (message: string) => new Error(message);

Expand All @@ -57,6 +33,40 @@ export const decodeOrThrow =
(inputValue: I) =>
pipe(runtimeType.decode(inputValue), fold(throwErrors(createError), identity));

const getProps = (
codec:
| rt.HasProps
// eslint-disable-next-line @typescript-eslint/no-explicit-any
| rt.RecordC<rt.StringC, any>
| GenericIntersectionC
): rt.Props | null => {
if (codec == null) {
return null;
}
switch (codec._tag) {
case 'DictionaryType':
if (codec.codomain.props != null) {
return codec.codomain.props;
}
const dTypes: rt.HasProps[] = codec.codomain.types;
return dTypes.reduce<rt.Props>((props, type) => Object.assign(props, getProps(type)), {});
case 'RefinementType':
case 'ReadonlyType':
return getProps(codec.type);
case 'InterfaceType':
case 'StrictType':
case 'PartialType':
return codec.props;
case 'IntersectionType':
const iTypes = codec.types as rt.HasProps[];
return iTypes.reduce<rt.Props>((props, type) => {
return Object.assign(props, getProps(type) as rt.Props);
}, {} as rt.Props) as rt.Props;
default:
return null;
}
};

const getExcessProps = (props: rt.Props, r: Record<string, unknown>): string[] => {
const ex: string[] = [];
for (const k of Object.keys(r)) {
Expand All @@ -67,15 +77,21 @@ const getExcessProps = (props: rt.Props, r: Record<string, unknown>): string[] =
return ex;
};

export function excess<C extends rt.InterfaceType<rt.Props> | rt.PartialType<rt.Props>>(
codec: C
): C {
export function excess<
C extends rt.InterfaceType<rt.Props> | GenericIntersectionC | rt.PartialType<rt.Props>
>(codec: C): C {
const codecProps = getProps(codec);

const r = new rt.InterfaceType(
codec.name,
codec.is,
(i, c) =>
either.chain(rt.UnknownRecord.validate(i, c), (s: Record<string, unknown>) => {
const ex = getExcessProps(codec.props, s);
if (codecProps == null) {
return rt.failure(i, c, 'unknown codec');
}

const ex = getExcessProps(codecProps, s);
return ex.length > 0
? rt.failure(
i,
Expand All @@ -87,7 +103,7 @@ export function excess<C extends rt.InterfaceType<rt.Props> | rt.PartialType<rt.
: codec.validate(i, c);
}),
codec.encode,
codec.props
codecProps
);
return r as C;
}
3 changes: 2 additions & 1 deletion x-pack/plugins/cases/server/client/cases/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
CasePostRequest,
ActionTypes,
CasePostRequestRt,
excess,
} from '../../../common/api';
import { MAX_TITLE_LENGTH } from '../../../common/constants';
import { isInvalidTag } from '../../../common/utils/validators';
Expand Down Expand Up @@ -47,7 +48,7 @@ export const create = async (
} = clientArgs;

const query = pipe(
CasePostRequestRt.decode({
excess(CasePostRequestRt).decode({
...data,
}),
fold(throwErrors(Boom.badRequest), identity)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ export default ({ getService }: FtrProviderContext): void => {
.expect(400);
});

it.skip('400s if you passing status for a new case', async () => {
it('400s if you passing status for a new case', async () => {
const req = getPostCaseRequest();

await supertest
Expand Down

0 comments on commit de4a093

Please sign in to comment.