Skip to content

Commit

Permalink
@kbn/io-ts-utils: strictKeys: validate objects in arrays (#191607)
Browse files Browse the repository at this point in the history
Makes sure keys from plain objects in arrays are validated as well.

---------

Co-authored-by: Søren Louv-Jansen <[email protected]>
Co-authored-by: Carlos Crespo <[email protected]>
  • Loading branch information
3 people authored Aug 30, 2024
1 parent 1db47f0 commit 8ade3da
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 6 deletions.
33 changes: 32 additions & 1 deletion packages/kbn-io-ts-utils/src/strict_keys_rt/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,38 @@ describe('strictKeysRt', () => {
{
type: t.array(t.type({ foo: t.string })),
passes: [[{ foo: 'bar' }], [{ foo: 'baz' }, { foo: 'bar' }]],
fails: [],
fails: [{ foo: 'bar', bar: 'foo' }],
},
{
type: t.type({
nestedArray: t.array(
t.type({
bar: t.string,
})
),
}),
passes: [
{
nestedArray: [],
},
{
nestedArray: [
{
bar: 'foo',
},
],
},
],
fails: [
{
nestedArray: [
{
bar: 'foo',
foo: 'bar',
},
],
},
],
},
];

Expand Down
30 changes: 25 additions & 5 deletions packages/kbn-io-ts-utils/src/strict_keys_rt/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import * as t from 'io-ts';
import { either, isRight } from 'fp-ts/lib/Either';
import { difference, isPlainObject, forEach } from 'lodash';
import { difference, isPlainObject, forEach, isArray, castArray } from 'lodash';
import { MergeType } from '../merge_rt';

/*
Expand Down Expand Up @@ -100,11 +100,31 @@ function getHandledKeys<T extends Record<string, unknown>>(
keys.handled.add(ownPrefix);
}

const processObject = (typeForObject: t.Mixed, objectToProcess: Record<string, unknown>) => {
const nextKeys = getHandledKeys(typeForObject, objectToProcess, ownPrefix);
nextKeys.all.forEach((k) => keys.all.add(k));
nextKeys.handled.forEach((k) => keys.handled.add(k));
};

if (isPlainObject(value)) {
handlingTypes.forEach((i) => {
const nextKeys = getHandledKeys(i, value as Record<string, unknown>, ownPrefix);
nextKeys.all.forEach((k) => keys.all.add(k));
nextKeys.handled.forEach((k) => keys.handled.add(k));
handlingTypes.forEach((typeAtIndex) => {
processObject(typeAtIndex, value as Record<string, unknown>);
});
}

if (isArray(value)) {
handlingTypes.forEach((typeAtIndex) => {
if (!isParsableType(typeAtIndex) || typeAtIndex._tag !== 'ArrayType') {
return;
}

const innerType = typeAtIndex.type;

castArray(value).forEach((valueAtIndex) => {
if (isPlainObject(valueAtIndex)) {
processObject(innerType, valueAtIndex as Record<string, unknown>);
}
});
});
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ export enum KnowledgeBaseType {
}

export interface ObservabilityAIAssistantScreenContextRequest {
starterPrompts?: StarterPrompt[];
screenDescription?: string;
data?: Array<{
name: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
type Message,
MessageRole,
type ObservabilityAIAssistantScreenContextRequest,
type StarterPrompt,
} from '../../common/types';

const serializeableRt = t.any;
Expand Down Expand Up @@ -129,6 +130,12 @@ export const functionRt = t.intersection([
}),
]);

export const starterPromptRt: t.Type<StarterPrompt> = t.type({
title: t.string,
prompt: t.string,
icon: t.any,
});

export const screenContextRt: t.Type<ObservabilityAIAssistantScreenContextRequest> = t.partial({
description: t.string,
data: t.array(
Expand All @@ -139,4 +146,6 @@ export const screenContextRt: t.Type<ObservabilityAIAssistantScreenContextReques
})
),
actions: t.array(functionRt),
screenDescription: t.string,
starterPrompts: t.array(starterPromptRt),
});

0 comments on commit 8ade3da

Please sign in to comment.